Skip to content
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
9 changes: 9 additions & 0 deletions src/agent/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/agent/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"atexit",
"cobertura",
"coverage",
"coverage-legacy",
"debuggable-module",
Expand Down
9 changes: 9 additions & 0 deletions src/agent/cobertura/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "cobertura"
version = "0.1.0"
edition = "2021"
license = "MIT"

[dependencies]
anyhow = "1.0"
quick-xml = "0.26.0"
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::field_reassign_with_default)]

use std::collections::{BTreeMap, BTreeSet};
use std::io::{Cursor, Write};

use coverage::source::SourceCoverage;
use debuggable_module::path::FilePath;
use quick_xml::{Result, Writer};

impl CoberturaCoverage {
Expand Down Expand Up @@ -406,87 +402,3 @@ impl WriteXml for Condition {
Ok(())
}
}

// Dir -> Set<FilePath>
type FileMap<'a> = BTreeMap<&'a str, BTreeSet<&'a FilePath>>;

impl From<SourceCoverage> for CoberturaCoverage {
fn from(source: SourceCoverage) -> Self {
// The Cobertura data model is organized around `classes` and `methods` contained
// in `packages`. Our source coverage has no language-level assumptions.
//
// To obtain legible HTML reports using ReportGenerator, will we use `<package>`
// elements to group files by their parent directory. Each measured source file
// will be represented a `<class>`. The and the measured source file's lines will
// become `<line>` elements of the (synthetic) class.
//
// Note: ReportGenerator automatically computes and rolls up aggregated coverage
// stats. We do _not_ need to manually compute any `line-rate` attributes. The
// presence of these attributes is required by the Cobertura schema, but even if
// they are set to 0 (as in our `Default` impls), ReportGenerator ignores them.

// Source files grouped by directory.
let mut file_map = FileMap::default();

for file_path in source.files.keys() {
let dir = file_path.directory();
let files = file_map.entry(dir).or_default();
files.insert(file_path);
}

// Iterate through the grouped files, accumulating `<package>` elements.
let mut packages = vec![];
let mut sources = vec![];

for (directory, files) in file_map {
// Make a `<package>` to represent the directory.
//
// We will add a `<class>` for each contained file.
let mut package = Package::default();
package.name = directory.to_owned();

let mut classes = vec![];

for file_path in files {
// Add the file to the `<sources>` manifest element.
let src = Source {
path: file_path.to_string(),
};
sources.push(src);

let mut lines = vec![];

// Can't panic, by construction.
let file_coverage = &source.files[file_path];

for (line, count) in &file_coverage.lines {
let number = u64::from(line.number());
let hits = u64::from(count.0);

let mut line = Line::default();
line.number = number;
line.hits = hits;

lines.push(line);
}

let mut class = Class::default();
class.name = file_path.file_name().to_owned();
class.filename = file_path.to_string();
class.lines = Lines { lines };

classes.push(class);
}

package.classes = Classes { classes };

packages.push(package);
}

let mut xml = CoberturaCoverage::default();
xml.sources = Some(Sources { sources });
xml.packages = Packages { packages };

xml
}
}
9 changes: 7 additions & 2 deletions src/agent/coverage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ license = "MIT"

[dependencies]
anyhow = "1.0"
cobertura = { path = "../cobertura" }
debuggable-module = { path = "../debuggable-module" }
iced-x86 = "1.17"
log = "0.4.17"
regex = "1.0"
symbolic = { version = "10.1", features = ["debuginfo", "demangle", "symcache"] }
symbolic = { version = "10.1", features = [
"debuginfo",
"demangle",
"symcache",
] }
thiserror = "1.0"

[target.'cfg(target_os = "windows")'.dependencies]
Expand All @@ -20,7 +25,7 @@ debugger = { path = "../debugger" }
pete = "0.9"
# For procfs, opt out of the `chrono` freature; it pulls in an old version
# of `time`. We do not use the methods that the `chrono` feature enables.
procfs = { version = "0.12", default-features = false, features=["flate2"] }
procfs = { version = "0.12", default-features = false, features = ["flate2"] }

[dev-dependencies]
clap = { version = "4.0", features = ["derive"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

use anyhow::Result;

use cobertura::CoberturaCoverage;
use coverage::source::{Count, FileCoverage, Line, SourceCoverage};
use debuggable_module::path::FilePath;
use onefuzz_file_format::coverage::cobertura::CoberturaCoverage;

fn main() -> Result<()> {
let modoff = vec![
Expand Down
29 changes: 23 additions & 6 deletions src/agent/coverage/examples/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::time::Duration;

use anyhow::Result;
use clap::Parser;
use cobertura::CoberturaCoverage;
use coverage::allowlist::{AllowList, TargetAllowList};
use coverage::binary::BinaryCoverage;
use coverage::record::CoverageRecorder;
Expand All @@ -19,15 +20,22 @@ struct Args {
#[arg(short, long)]
timeout: Option<u64>,

#[arg(short, long)]
source: bool,
#[arg(short, long, value_enum, default_value_t = OutputFormat::ModOff)]
output: OutputFormat,

#[arg(long)]
dump_stdio: bool,

command: Vec<String>,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
enum OutputFormat {
ModOff,
Source,
Cobertura,
}

const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);

fn main() -> Result<()> {
Expand Down Expand Up @@ -79,10 +87,10 @@ fn main() -> Result<()> {
println!();
}

if args.source {
dump_source_line(&recorded.coverage)?;
} else {
dump_modoff(&recorded.coverage)?;
match args.output {
OutputFormat::ModOff => dump_modoff(&recorded.coverage)?,
OutputFormat::Source => dump_source_line(&recorded.coverage)?,
OutputFormat::Cobertura => dump_cobertura(&recorded.coverage)?,
}

Ok(())
Expand Down Expand Up @@ -111,3 +119,12 @@ fn dump_source_line(binary: &BinaryCoverage) -> Result<()> {

Ok(())
}

fn dump_cobertura(binary: &BinaryCoverage) -> Result<()> {
let source = coverage::source::binary_to_source_coverage(binary)?;
let cobertura: CoberturaCoverage = source.into();

println!("{}", cobertura.to_string()?);

Ok(())
}
101 changes: 101 additions & 0 deletions src/agent/coverage/src/cobertura.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::collections::{BTreeMap, BTreeSet};

use cobertura::{
Class, Classes, CoberturaCoverage, Line, Lines, Package, Packages, Source, Sources,
};
use debuggable_module::path::FilePath;

use crate::source::SourceCoverage;

// Dir -> Set<FilePath>
type FileMap<'a> = BTreeMap<&'a str, BTreeSet<&'a FilePath>>;

impl From<SourceCoverage> for CoberturaCoverage {
fn from(source: SourceCoverage) -> Self {
// The Cobertura data model is organized around `classes` and `methods` contained
// in `packages`. Our source coverage has no language-level assumptions.
//
// To obtain legible HTML reports using ReportGenerator, will we use `<package>`
// elements to group files by their parent directory. Each measured source file
// will be represented a `<class>`. The and the measured source file's lines will
// become `<line>` elements of the (synthetic) class.
//
// Note: ReportGenerator automatically computes and rolls up aggregated coverage
// stats. We do _not_ need to manually compute any `line-rate` attributes. The
// presence of these attributes is required by the Cobertura schema, but even if
// they are set to 0 (as in our `Default` impls), ReportGenerator ignores them.

// Source files grouped by directory.
let mut file_map = FileMap::default();

for file_path in source.files.keys() {
let dir = file_path.directory();
let files = file_map.entry(dir).or_default();
files.insert(file_path);
}

// Iterate through the grouped files, accumulating `<package>` elements.
let mut packages = vec![];
let mut sources = vec![];

for (directory, files) in file_map {
// Make a `<package>` to represent the directory.
//
// We will add a `<class>` for each contained file.
let mut package = Package {
name: directory.to_owned(),
..Package::default()
};

let mut classes = vec![];

for file_path in files {
// Add the file to the `<sources>` manifest element.
let src = Source {
path: file_path.to_string(),
};
sources.push(src);

let mut lines = vec![];

// Can't panic, by construction.
let file_coverage = &source.files[file_path];

for (line, count) in &file_coverage.lines {
let number = u64::from(line.number());
let hits = u64::from(count.0);

let line = Line {
number,
hits,
..Line::default()
};

lines.push(line);
}

let class = Class {
name: file_path.file_name().to_owned(),
filename: file_path.to_string(),
lines: Lines { lines },
..Class::default()
};

classes.push(class);
}

package.classes = Classes { classes };

packages.push(package);
}

CoberturaCoverage {
sources: Some(Sources { sources }),
packages: Packages { packages },
..CoberturaCoverage::default()
}
}
}
1 change: 1 addition & 0 deletions src/agent/coverage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern crate log;

pub mod allowlist;
pub mod binary;
pub mod cobertura;
pub mod record;
pub mod source;
mod timer;
Expand Down
1 change: 0 additions & 1 deletion src/agent/onefuzz-file-format/src/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
// Licensed under the MIT License.

pub mod binary;
pub mod cobertura;
pub mod source;