mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-14 11:08:06 +00:00
Implement collection of source line coverage (#1518)
Implement source line coverage recording for both Linux and Windows.
This commit is contained in:
288
src/agent/Cargo.lock
generated
288
src/agent/Cargo.lock
generated
@ -71,6 +71,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.6.1"
|
||||
@ -213,6 +219,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brownstone"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "030ea61398f34f1395ccbeb046fb68c87b631d1f34567fed0f0f11fa35d18d8d"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.7.1"
|
||||
@ -240,6 +255,27 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cache-padded"
|
||||
version = "1.1.1"
|
||||
@ -368,6 +404,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"structopt",
|
||||
"symbolic",
|
||||
"uuid",
|
||||
"win-util",
|
||||
"winapi 0.3.9",
|
||||
@ -563,6 +600,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91cf5a8c2f2097e2a32627123508635d47ce10563d999ec1a95addf08b502ba"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
@ -591,6 +637,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dmsort"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4699f5cb7678f099b747ffdc1e6ce6cdc42579e29d28315aa715b9fd10324fc"
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
@ -615,6 +667,16 @@ version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "elementtree"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c5d32d0ab83734d2d7452047ef901c105991044b7b07da30fe82371a149a25"
|
||||
dependencies = [
|
||||
"string_cache",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.28"
|
||||
@ -974,6 +1036,10 @@ name = "gimli"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
@ -1160,6 +1226,12 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
@ -1171,6 +1243,12 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indent_write"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
@ -1255,6 +1333,12 @@ version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "joinery"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
@ -1286,13 +1370,19 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"arrayvec 0.5.2",
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"ryu",
|
||||
@ -1379,6 +1469,16 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memmap"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.0"
|
||||
@ -1513,6 +1613,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.0"
|
||||
@ -1560,6 +1666,19 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom-supreme"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aadc66631948f6b65da03be4c4cd8bd104d481697ecbb9bbd65719b1ec60bc9f"
|
||||
dependencies = [
|
||||
"brownstone",
|
||||
"indent_write",
|
||||
"joinery",
|
||||
"memchr",
|
||||
"nom 7.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "4.0.17"
|
||||
@ -1871,6 +1990,15 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.8"
|
||||
@ -1921,6 +2049,12 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.0.0"
|
||||
@ -2472,6 +2606,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.4"
|
||||
@ -2565,6 +2705,12 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stacktrace-parser"
|
||||
version = "0.1.0"
|
||||
@ -2609,6 +2755,20 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@ -2658,6 +2818,86 @@ dependencies = [
|
||||
"syn 1.0.76",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symbolic"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf0b3be4c272aaef995fca595a89355caf49993b84a964013e4a94447f13c779"
|
||||
dependencies = [
|
||||
"symbolic-common",
|
||||
"symbolic-debuginfo",
|
||||
"symbolic-demangle",
|
||||
"symbolic-symcache",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symbolic-common"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc8618f0f31ed048f8e66aa2caecedfbdbbca962ff9ad87107ba4171de0742b"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"memmap",
|
||||
"stable_deref_trait",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symbolic-debuginfo"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34b5f005e9c87aa9c60a712102f80dca51cd9edf1fa04d5c7392b936c45d06"
|
||||
dependencies = [
|
||||
"dmsort",
|
||||
"elementtree",
|
||||
"fallible-iterator",
|
||||
"flate2",
|
||||
"gimli",
|
||||
"goblin",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"nom 7.1.0",
|
||||
"nom-supreme",
|
||||
"parking_lot",
|
||||
"pdb",
|
||||
"regex",
|
||||
"scroll",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"symbolic-common",
|
||||
"thiserror",
|
||||
"walrus",
|
||||
"wasmparser",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symbolic-demangle"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be790f170c892899802aa1d382b7b5b65baf692b1357864c74e92bbbbdabfbe"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cpp_demangle",
|
||||
"msvc-demangler",
|
||||
"rustc-demangle",
|
||||
"symbolic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symbolic-symcache"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35cdeb276d2476e76b3ce365d3e812a0df5ab04ef4c2781b6f4a962e22337c6"
|
||||
dependencies = [
|
||||
"dmsort",
|
||||
"fnv",
|
||||
"symbolic-common",
|
||||
"symbolic-debuginfo",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
@ -3034,6 +3274,32 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walrus"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"leb128",
|
||||
"log",
|
||||
"walrus-macro",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walrus-macro"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2 1.0.29",
|
||||
"quote 1.0.9",
|
||||
"syn 1.0.76",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
@ -3122,6 +3388,12 @@ version = "0.2.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.77.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.55"
|
||||
@ -3290,3 +3562,17 @@ name = "z3-sys"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afa18ba5fbd4933e41ffb440c3fd91f91fe9cdb7310cce3ddfb6648563811de0"
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bzip2",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
@ -24,6 +24,7 @@ msvc-demangler = "0.9"
|
||||
regex = "1.4"
|
||||
rustc-demangle = "0.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
symbolic = { version = "8.5", features = ["debuginfo", "demangle", "symcache"] }
|
||||
uuid = { version = "0.8", features = ["guid"] }
|
||||
win-util = { path = "../win-util" }
|
||||
|
||||
|
145
src/agent/coverage/examples/src-cov.rs
Normal file
145
src/agent/coverage/examples/src-cov.rs
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{process::Command, process::Stdio};
|
||||
|
||||
use anyhow::Result;
|
||||
use coverage::block::CommandBlockCov as Coverage;
|
||||
use coverage::cache::ModuleCache;
|
||||
use coverage::code::CmdFilter;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Debug, PartialEq, StructOpt)]
|
||||
struct Opt {
|
||||
#[structopt(short, long, min_values = 1)]
|
||||
inputs: Vec<PathBuf>,
|
||||
|
||||
#[structopt(short, long)]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
#[structopt(min_values = 2)]
|
||||
cmd: Vec<String>,
|
||||
|
||||
#[structopt(short, long, long_help = "Timeout in ms", default_value = "5000")]
|
||||
timeout: u64,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let opt = Opt::from_args();
|
||||
let filter = CmdFilter::default();
|
||||
|
||||
let mut cache = ModuleCache::default();
|
||||
let mut total = Coverage::default();
|
||||
let timeout = Duration::from_millis(opt.timeout);
|
||||
|
||||
if let Some(dir) = &opt.dir {
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
let input = entry?.path();
|
||||
|
||||
println!("testing input: {}", input.display());
|
||||
|
||||
let cmd = input_command(&opt.cmd, &input);
|
||||
let coverage = record(&mut cache, filter.clone(), cmd, timeout)?;
|
||||
|
||||
total.merge_max(&coverage);
|
||||
}
|
||||
}
|
||||
|
||||
for input in &opt.inputs {
|
||||
println!("testing input: {}", input.display());
|
||||
|
||||
let cmd = input_command(&opt.cmd, input);
|
||||
let coverage = record(&mut cache, filter.clone(), cmd, timeout)?;
|
||||
|
||||
total.merge_max(&coverage);
|
||||
}
|
||||
|
||||
let mut debug_info = coverage::debuginfo::DebugInfo::default();
|
||||
let src_coverage = total.source_coverage(&mut debug_info)?;
|
||||
|
||||
for file_coverage in src_coverage.files {
|
||||
for location in &file_coverage.locations {
|
||||
println!(
|
||||
"{} {}:{}",
|
||||
location.count, file_coverage.file, location.line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn input_command(argv: &[String], input: &Path) -> Command {
|
||||
let mut cmd = Command::new(&argv[0]);
|
||||
cmd.stdin(Stdio::null());
|
||||
cmd.stderr(Stdio::null());
|
||||
cmd.stdout(Stdio::null());
|
||||
|
||||
let args: Vec<_> = argv[1..]
|
||||
.iter()
|
||||
.map(|a| {
|
||||
if &*a == "@@" {
|
||||
input.display().to_string()
|
||||
} else {
|
||||
a.to_string()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
cmd.args(&args);
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn record(
|
||||
_cache: &mut ModuleCache,
|
||||
_filter: CmdFilter,
|
||||
_cmd: Command,
|
||||
_timeout: Duration,
|
||||
) -> Result<Coverage> {
|
||||
unimplemented!("coverage recording is not supported on macOS");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn record(
|
||||
cache: &mut ModuleCache,
|
||||
filter: CmdFilter,
|
||||
cmd: Command,
|
||||
timeout: Duration,
|
||||
) -> Result<Coverage> {
|
||||
use coverage::block::linux::Recorder;
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let coverage = Recorder::record(cmd, timeout, cache, filter.clone())?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
log::info!("recorded in {:?}", elapsed);
|
||||
|
||||
Ok(coverage)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn record(
|
||||
cache: &mut ModuleCache,
|
||||
filter: CmdFilter,
|
||||
cmd: Command,
|
||||
timeout: Duration,
|
||||
) -> Result<Coverage> {
|
||||
use coverage::block::windows::{Recorder, RecorderEventHandler};
|
||||
|
||||
let mut recorder = Recorder::new(cache, filter);
|
||||
let mut handler = RecorderEventHandler::new(&mut recorder, timeout);
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
handler.run(cmd)?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
log::info!("recorded in {:?}", elapsed);
|
||||
|
||||
Ok(recorder.into_coverage())
|
||||
}
|
@ -17,7 +17,9 @@ use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::code::ModulePath;
|
||||
use crate::debuginfo::DebugInfo;
|
||||
use crate::report::{CoverageReport, CoverageReportEntry};
|
||||
use crate::source::SourceCoverage;
|
||||
|
||||
/// Block coverage for a command invocation.
|
||||
///
|
||||
@ -104,6 +106,70 @@ impl CommandBlockCov {
|
||||
pub fn try_from_report(report: BlockCoverageReport) -> Result<Self> {
|
||||
Self::try_from(report)
|
||||
}
|
||||
|
||||
/// Translate binary block coverage to source line coverage, using a caching
|
||||
/// debug info provider.
|
||||
pub fn source_coverage(&self, debuginfo: &mut DebugInfo) -> Result<SourceCoverage> {
|
||||
use crate::source::{SourceCoverageLocation as Location, *};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Temporary map to collect line coverage results without duplication.
|
||||
// Will be converted after processing block coverage.
|
||||
//
|
||||
// Maps: source_file_path -> (line -> count)
|
||||
let mut files: HashMap<String, HashMap<u32, u32>> = HashMap::default();
|
||||
|
||||
for (module, coverage) in &self.modules {
|
||||
let loaded = debuginfo.load_module(module.path().to_owned())?;
|
||||
|
||||
if !loaded {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mod_info = debuginfo.get(&module.path());
|
||||
|
||||
if let Some(mod_info) = mod_info {
|
||||
for (offset, block) in &coverage.blocks {
|
||||
let lines = mod_info.source.lookup(u64::from(*offset))?;
|
||||
|
||||
for line_info in lines {
|
||||
let line_info = line_info?;
|
||||
let file = line_info.path().to_owned();
|
||||
let line = line_info.line();
|
||||
|
||||
let file_entry = files.entry(file).or_default();
|
||||
let line_entry = file_entry.entry(line).or_insert(0);
|
||||
|
||||
// Will always be 0 or 1.
|
||||
*line_entry = u32::max(*line_entry, block.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut src = SourceCoverage::default();
|
||||
|
||||
for (file, lines) in files {
|
||||
let mut locations = vec![];
|
||||
|
||||
for (line, count) in lines {
|
||||
// Valid lines are always 1-indexed.
|
||||
if line > 0 {
|
||||
let location = Location::new(line, None, count)?;
|
||||
locations.push(location)
|
||||
}
|
||||
}
|
||||
|
||||
locations.sort_unstable_by_key(|l| l.line);
|
||||
|
||||
let file_coverage = SourceFileCoverage { file, locations };
|
||||
src.files.push(file_coverage);
|
||||
}
|
||||
|
||||
src.files.sort_unstable_by_key(|f| f.file.clone());
|
||||
|
||||
Ok(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommandBlockCov> for BlockCoverageReport {
|
||||
|
118
src/agent/coverage/src/debuginfo.rs
Normal file
118
src/agent/coverage/src/debuginfo.rs
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use symbolic::{
|
||||
debuginfo::{pe, Object, ObjectDebugSession},
|
||||
symcache::{SymCache, SymCacheWriter},
|
||||
};
|
||||
|
||||
/// Caching provider of debug info for executable code modules.
|
||||
#[derive(Default)]
|
||||
pub struct DebugInfo {
|
||||
// Cached debug info, keyed by module path.
|
||||
modules: HashMap<PathBuf, ModuleDebugInfo>,
|
||||
|
||||
// Set of module paths known to lack debug info.
|
||||
no_debug_info: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl DebugInfo {
|
||||
/// Try to load debug info for a module.
|
||||
///
|
||||
/// If debug info was founded and loaded (now or previously), returns
|
||||
/// `true`. If the module does not have debug info, returns `false`.
|
||||
pub fn load_module(&mut self, module: PathBuf) -> Result<bool> {
|
||||
if self.no_debug_info.contains(&module) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if self.modules.get(&module).is_some() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let info = match ModuleDebugInfo::load(&module)? {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
self.no_debug_info.insert(module);
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
self.modules.insert(module, info);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Fetch debug info for `module`, if loaded.
|
||||
///
|
||||
/// Does not attempt to load debug info for the module.
|
||||
pub fn get(&self, module: impl AsRef<Path>) -> Option<&ModuleDebugInfo> {
|
||||
self.modules.get(module.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug info for a single executable module.
|
||||
pub struct ModuleDebugInfo {
|
||||
/// Backing debug info file data for the module.
|
||||
///
|
||||
/// May not include the actual executable code.
|
||||
pub object: Object<'static>,
|
||||
|
||||
/// Interface and caching structures for general debug info querying.
|
||||
pub session: ObjectDebugSession<'static>,
|
||||
|
||||
/// Cache which allows efficient source line lookups.
|
||||
pub source: SymCache<'static>,
|
||||
}
|
||||
|
||||
impl ModuleDebugInfo {
|
||||
/// Load debug info for a module.
|
||||
///
|
||||
/// Returns `None` when the module was found and loadable, but no matching
|
||||
/// debug info could be found.
|
||||
///
|
||||
/// Leaks module and symbol data.
|
||||
fn load(module: &Path) -> Result<Option<Self>> {
|
||||
let mut data = fs::read(&module)?.into_boxed_slice();
|
||||
|
||||
// If our module is a PE file, the debug info will be in the PDB.
|
||||
//
|
||||
// We will need a similar check to support split DWARF.
|
||||
let is_pe = pe::PeObject::test(&data);
|
||||
if is_pe {
|
||||
// Assume a sibling PDB.
|
||||
//
|
||||
// TODO: Find PDB using `dbghelp::`SymGetSymbolFile()`.
|
||||
let pdb = module.with_extension("pdb");
|
||||
data = fs::read(&pdb)?.into_boxed_slice();
|
||||
}
|
||||
|
||||
// Now we're sure we want this data, so leak it.
|
||||
let data = Box::leak(data);
|
||||
|
||||
let object = Object::parse(data)?;
|
||||
|
||||
if !object.has_debug_info() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let session = object.debug_session()?;
|
||||
|
||||
let cursor = io::Cursor::new(vec![]);
|
||||
let cursor = SymCacheWriter::write_object(&object, cursor)?;
|
||||
let cache_data = Box::leak(cursor.into_inner().into_boxed_slice());
|
||||
let source = SymCache::parse(cache_data)?;
|
||||
|
||||
Ok(Some(Self {
|
||||
object,
|
||||
session,
|
||||
source,
|
||||
}))
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ pub mod elf;
|
||||
pub mod block;
|
||||
pub mod cache;
|
||||
pub mod code;
|
||||
pub mod debuginfo;
|
||||
pub mod demangle;
|
||||
pub mod report;
|
||||
pub mod sancov;
|
||||
|
@ -39,9 +39,10 @@ cargo fmt -- --check
|
||||
# RUSTSEC-2020-0036: a dependency `failure` (pulled from proc-maps) is deprecated
|
||||
# RUSTSEC-2019-0036: a dependency `failure` (pulled from proc-maps) has type confusion vulnerability
|
||||
# RUSTSEC-2021-0065: a dependency `anymap` is no longer maintained
|
||||
# RUSTSEC-2020-0077: `memmap` dependency unmaintained, via `symbolic` (see: `getsentry/symbolic#304`)
|
||||
# RUSTSEC-2020-0159: potential segfault in `time`, not yet patched (#1366)
|
||||
# RUSTSEC-2020-0071: potential segfault in `chrono`, not yet patched (#1366)
|
||||
cargo audit --deny warnings --deny unmaintained --deny unsound --deny yanked --ignore RUSTSEC-2020-0016 --ignore RUSTSEC-2020-0036 --ignore RUSTSEC-2019-0036 --ignore RUSTSEC-2021-0065 --ignore RUSTSEC-2020-0159 --ignore RUSTSEC-2020-0071
|
||||
cargo audit --deny warnings --deny unmaintained --deny unsound --deny yanked --ignore RUSTSEC-2020-0016 --ignore RUSTSEC-2020-0036 --ignore RUSTSEC-2019-0036 --ignore RUSTSEC-2021-0065 --ignore RUSTSEC-2020-0159 --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2020-0077
|
||||
cargo-license -j > data/licenses.json
|
||||
cargo build --release --locked
|
||||
cargo clippy --release -- -D warnings
|
||||
|
Reference in New Issue
Block a user