Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/agent/coverage/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use crate::binary::BinaryCoverage;

pub use crate::binary::Count;

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SourceCoverage {
pub files: BTreeMap<FilePath, FileCoverage>,
}

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileCoverage {
pub lines: BTreeMap<Line, Count>,
}
Expand Down
38 changes: 33 additions & 5 deletions src/agent/onefuzz-file-format/src/coverage/source/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ use debuggable_module::path::FilePath;
use serde::{Deserialize, Serialize};

pub type SourceFile = String;
pub type LineNumber = u32;
pub type HitCount = u32;
pub use line_number::LineNumber;

#[derive(Deserialize, Serialize)]
pub struct SourceCoverageJson {
#[serde(flatten)]
pub modules: BTreeMap<SourceFile, BTreeMap<LineNumber, HitCount>>,
pub files: BTreeMap<SourceFile, FileCoverageJson>,
}

#[derive(Deserialize, Serialize)]
pub struct FileCoverageJson {
pub lines: BTreeMap<LineNumber, HitCount>,
}

impl TryFrom<SourceCoverageJson> for SourceCoverage {
Expand All @@ -24,13 +29,13 @@ impl TryFrom<SourceCoverageJson> for SourceCoverage {
fn try_from(json: SourceCoverageJson) -> Result<Self> {
let mut source = SourceCoverage::default();

for (file_path, lines) in json.modules {
for (file_path, file_json) in json.files {
let file_path = FilePath::new(file_path)?;

let mut file = FileCoverage::default();

for (line, count) in lines {
let line = Line::new(line)?;
for (line_number, count) in file_json.lines {
let line = Line::new(line_number.0)?;
let count = Count(count);
file.lines.insert(line, count);
}
Expand All @@ -41,3 +46,26 @@ impl TryFrom<SourceCoverageJson> for SourceCoverage {
Ok(source)
}
}

mod line_number {
use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct LineNumber(#[serde(with = "self")] pub u32);

pub fn serialize<S>(val: &u32, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = format!("{}", val);
serializer.serialize_str(&s)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
18 changes: 18 additions & 0 deletions src/agent/onefuzz-file-format/tests/files/source-coverage.v0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"file": "src/bin/main.c",
"locations": [
{ "line": 4, "column": 1, "count": 1 },
{ "line": 9, "column": 2, "count": 0 },
{ "line": 12, "column": 3, "count": 5 }
]
},
{
"file": "src/lib/common.c",
"locations": [
{ "line": 5, "column": null, "count": 0 },
{ "line": 5, "column": null, "count": 1 },
{ "line": 8, "column": null, "count": 0 }
]
}
]
15 changes: 15 additions & 0 deletions src/agent/onefuzz-file-format/tests/files/source-coverage.v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"src/bin/main.c": {
"lines": {
"4": 1,
"9": 0,
"12": 5
}
},
"src/lib/common.c": {
"lines": {
"5": 1,
"8": 0
}
}
}
44 changes: 44 additions & 0 deletions src/agent/onefuzz-file-format/tests/source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use pretty_assertions::assert_eq;

use anyhow::Result;
use coverage::source::{Count, Line, SourceCoverage};
use debuggable_module::path::FilePath;
use onefuzz_file_format::coverage::source::{v0, v1};

fn expected_source_coverage() -> Result<SourceCoverage> {
let main_path = FilePath::new("src/bin/main.c")?;
let common_path = FilePath::new("src/lib/common.c")?;

let mut source = SourceCoverage::default();

let main = source.files.entry(main_path).or_default();
main.lines.insert(Line::new(4)?, Count(1));
main.lines.insert(Line::new(9)?, Count(0));
main.lines.insert(Line::new(12)?, Count(5));

let common = source.files.entry(common_path).or_default();
common.lines.insert(Line::new(5)?, Count(1));
common.lines.insert(Line::new(8)?, Count(0));

Ok(source)
}

#[test]
fn test_source_coverage_formats() -> Result<()> {
let expected = expected_source_coverage()?;

let v0_text = include_str!("files/source-coverage.v0.json");
let v0_json: v0::SourceCoverageJson = serde_json::from_str(v0_text)?;
let from_v0 = SourceCoverage::try_from(v0_json)?;
assert_eq!(from_v0, expected);

let v1_text = include_str!("files/source-coverage.v1.json");
let v1_json: v1::SourceCoverageJson = serde_json::from_str(v1_text)?;
let from_v1 = SourceCoverage::try_from(v1_json)?;
assert_eq!(from_v1, expected);

Ok(())
}