Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Add snapshot test support for coverage implementation #3368

Merged
merged 12 commits into from
Aug 8, 2023
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
5 changes: 5 additions & 0 deletions src/agent/Cargo.lock

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

8 changes: 8 additions & 0 deletions src/agent/coverage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ version = "0.1.0"
edition = "2021"
license = "MIT"

[features]
slow-tests = []

[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
cobertura = { path = "../cobertura" }
Expand Down Expand Up @@ -32,3 +35,8 @@ procfs = { version = "0.12", default-features = false, features = ["flate2"] }
clap = { version = "4.3", features = ["derive"] }
env_logger = "0.10.0"
pretty_assertions = "1.4.0"
insta = { version = "1.31.0", features = ["glob"] }
coverage = { path = "../coverage" }
cc = "1.0"
tempfile = "3.7.0"
dunce = "1.0"
32 changes: 17 additions & 15 deletions src/agent/coverage/examples/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::process::Command;
use std::sync::Arc;
use std::time::Duration;

use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use clap::Parser;
use cobertura::CoberturaCoverage;
use coverage::allowlist::{AllowList, TargetAllowList};
use coverage::allowlist::AllowList;
use coverage::binary::{BinaryCoverage, DebugInfoCache};
use coverage::record::{CoverageRecorder, Recorded};
use debuggable_module::load_module::LoadModule;
Expand Down Expand Up @@ -57,19 +57,21 @@ fn main() -> Result<()> {
.map(Duration::from_millis)
.unwrap_or(DEFAULT_TIMEOUT);

let mut allowlist = TargetAllowList::default();
let module_allowlist = args
.module_allowlist
.map(AllowList::load)
.unwrap_or_else(|| Ok(AllowList::default()))
.context("loading module allowlist")?;

if let Some(path) = &args.module_allowlist {
allowlist.modules = AllowList::load(path)?;
}

if let Some(path) = &args.source_allowlist {
allowlist.source_files = AllowList::load(path)?;
}
let source_allowlist = args
.source_allowlist
.map(AllowList::load)
.unwrap_or_else(|| Ok(AllowList::default()))
.context("loading source allowlist")?;

let mut coverage = BinaryCoverage::default();
let loader = Arc::new(Loader::new());
let cache = Arc::new(DebugInfoCache::new(allowlist.source_files.clone()));
let cache = Arc::new(DebugInfoCache::new(source_allowlist.clone()));

let t = std::time::Instant::now();
precache_target(&args.command[0], &loader, &cache)?;
Expand All @@ -84,7 +86,7 @@ fn main() -> Result<()> {

let t = std::time::Instant::now();
let recorded = CoverageRecorder::new(cmd)
.allowlist(allowlist.clone())
.module_allowlist(module_allowlist.clone())
.loader(loader.clone())
.debuginfo_cache(cache.clone())
.timeout(timeout)
Expand All @@ -102,7 +104,7 @@ fn main() -> Result<()> {

let t = std::time::Instant::now();
let recorded = CoverageRecorder::new(cmd)
.allowlist(allowlist.clone())
.module_allowlist(module_allowlist)
.loader(loader)
.debuginfo_cache(cache)
.timeout(timeout)
Expand All @@ -118,8 +120,8 @@ fn main() -> Result<()> {

match args.output {
OutputFormat::ModOff => dump_modoff(&coverage)?,
OutputFormat::Source => dump_source_line(&coverage, allowlist.source_files)?,
OutputFormat::Cobertura => dump_cobertura(&coverage, allowlist.source_files)?,
OutputFormat::Source => dump_source_line(&coverage, source_allowlist)?,
OutputFormat::Cobertura => dump_cobertura(&coverage, source_allowlist)?,
}

Ok(())
Expand Down
25 changes: 0 additions & 25 deletions src/agent/coverage/src/allowlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,6 @@ use anyhow::Result;
use regex::{Regex, RegexSet};
use std::path::Path;

#[derive(Clone, Debug, Default)]
pub struct TargetAllowList {
pub modules: AllowList,
pub source_files: AllowList,
}

impl TargetAllowList {
pub fn new(modules: AllowList, source_files: AllowList) -> Self {
Self {
modules,
source_files,
}
}

#[allow(clippy::field_reassign_with_default)]
pub fn extend(&self, other: &Self) -> Self {
let mut new = Self::default();

new.modules = self.modules.extend(&other.modules);
new.source_files = self.source_files.extend(&other.source_files);

new
}
}

#[derive(Clone, Debug)]
pub struct AllowList {
allow: RegexSet,
Expand Down
8 changes: 4 additions & 4 deletions src/agent/coverage/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use debuggable_module::{block, path::FilePath, Offset};
use symbolic::debuginfo::Object;
use symbolic::symcache::{SymCache, SymCacheConverter};

use crate::allowlist::{AllowList, TargetAllowList};
use crate::allowlist::AllowList;

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BinaryCoverage {
Expand Down Expand Up @@ -214,7 +214,7 @@ impl CachedDebugInfo {

pub fn find_coverage_sites(
module: &dyn Module,
allowlist: &TargetAllowList,
source_allowlist: &AllowList,
) -> Result<ModuleBinaryCoverage> {
let debuginfo = module.debuginfo()?;

Expand All @@ -232,7 +232,7 @@ pub fn find_coverage_sites(
for function in debuginfo.functions() {
if let Some(location) = symcache.lookup(function.offset.0).next() {
if let Some(file) = location.file() {
if !allowlist.source_files.is_allowed(file.full_path()) {
if !source_allowlist.is_allowed(file.full_path()) {
debug!(
"skipping sweep of `{}:{}` due to excluded source path `{}`",
module.executable_path(),
Expand All @@ -253,7 +253,7 @@ pub fn find_coverage_sites(

// Apply allowlists per block, to account for inlining. The `location` values
// here describe the top of the inline-inclusive call stack.
if !allowlist.source_files.is_allowed(path) {
if !source_allowlist.is_allowed(path) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion src/agent/coverage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub mod source;
mod timer;

#[doc(inline)]
pub use allowlist::{AllowList, TargetAllowList};
pub use allowlist::AllowList;

#[doc(inline)]
pub use record::{CoverageRecorder, Recorded};
19 changes: 9 additions & 10 deletions src/agent/coverage/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use std::time::Duration;
use anyhow::Result;
use debuggable_module::loader::Loader;

use crate::allowlist::TargetAllowList;
use crate::binary::{BinaryCoverage, DebugInfoCache};
use crate::AllowList;

#[cfg(target_os = "linux")]
pub mod linux;
Expand All @@ -18,7 +18,7 @@ pub mod linux;
pub mod windows;

pub struct CoverageRecorder {
allowlist: TargetAllowList,
module_allowlist: AllowList,
cache: Arc<DebugInfoCache>,
cmd: Command,
loader: Arc<Loader>,
Expand All @@ -30,22 +30,20 @@ impl CoverageRecorder {
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());

let allowlist = TargetAllowList::default();
let cache = Arc::new(DebugInfoCache::new(allowlist.source_files.clone()));
let loader = Arc::new(Loader::new());
let timeout = Duration::from_secs(5);

Self {
allowlist,
cache,
module_allowlist: AllowList::default(),
cache: Arc::new(DebugInfoCache::new(AllowList::default())),
cmd,
loader,
timeout,
}
}

pub fn allowlist(mut self, allowlist: TargetAllowList) -> Self {
self.allowlist = allowlist;
pub fn module_allowlist(mut self, module_allowlist: AllowList) -> Self {
self.module_allowlist = module_allowlist;
self
}

Expand Down Expand Up @@ -82,7 +80,7 @@ impl CoverageRecorder {
let child_pid = child_pid.clone();

timer::timed(self.timeout, move || {
let mut recorder = LinuxRecorder::new(&loader, self.allowlist, &self.cache);
let mut recorder = LinuxRecorder::new(&loader, self.module_allowlist, &self.cache);
let mut dbg = Debugger::new(&mut recorder);
let child = dbg.spawn(self.cmd)?;

Expand Down Expand Up @@ -128,7 +126,8 @@ impl CoverageRecorder {
let loader = self.loader.clone();

crate::timer::timed(self.timeout, move || {
let mut recorder = WindowsRecorder::new(&loader, self.allowlist, &self.cache);
let mut recorder =
WindowsRecorder::new(&loader, self.module_allowlist, self.cache.as_ref());
let (mut dbg, child) = Debugger::init(self.cmd, &mut recorder)?;
dbg.run(&mut recorder)?;

Expand Down
10 changes: 5 additions & 5 deletions src/agent/coverage/src/record/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use pete::Tracee;
pub mod debugger;
use debugger::{DebugEventHandler, DebuggerContext, ModuleImage};

use crate::allowlist::TargetAllowList;
use crate::allowlist::AllowList;
use crate::binary::{BinaryCoverage, DebugInfoCache};

pub struct LinuxRecorder<'cache, 'data> {
allowlist: TargetAllowList,
module_allowlist: AllowList,
cache: &'cache DebugInfoCache,
pub coverage: BinaryCoverage,
loader: &'data Loader,
Expand All @@ -28,14 +28,14 @@ pub struct LinuxRecorder<'cache, 'data> {
impl<'cache, 'data> LinuxRecorder<'cache, 'data> {
pub fn new(
loader: &'data Loader,
allowlist: TargetAllowList,
module_allowlist: AllowList,
cache: &'cache DebugInfoCache,
) -> Self {
let coverage = BinaryCoverage::default();
let modules = BTreeMap::new();

Self {
allowlist,
module_allowlist,
cache,
coverage,
loader,
Expand Down Expand Up @@ -80,7 +80,7 @@ impl<'cache, 'data> LinuxRecorder<'cache, 'data> {

let path = image.path();

if !self.allowlist.modules.is_allowed(path) {
if !self.module_allowlist.is_allowed(path) {
debug!("not inserting denylisted module: {path}");
return Ok(());
}
Expand Down
18 changes: 9 additions & 9 deletions src/agent/coverage/src/record/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use debuggable_module::windows::WindowsModule;
use debuggable_module::{Module, Offset};
use debugger::{BreakpointId, BreakpointType, DebugEventHandler, Debugger, ModuleLoadInfo};

use crate::allowlist::TargetAllowList;
use crate::binary::{BinaryCoverage, DebugInfoCache};
use crate::AllowList;

// For a new module image, we defer setting coverage breakpoints until exit from one of these
// functions (when present). This avoids breaking hotpatching routines in the ASan interceptor
Expand All @@ -26,7 +26,7 @@ const PROCESS_IMAGE_DEFERRAL_TRIGGER: &str = "__asan::AsanInitInternal(";
const LIBRARY_IMAGE_DEFERRAL_TRIGGER: &str = "DllMain(";

pub struct WindowsRecorder<'cache, 'data> {
allowlist: TargetAllowList,
module_allowlist: AllowList,
breakpoints: Breakpoints,
cache: &'cache DebugInfoCache,
deferred_breakpoints: BTreeMap<BreakpointId, (Breakpoint, DeferralState)>,
Expand All @@ -39,7 +39,7 @@ pub struct WindowsRecorder<'cache, 'data> {
impl<'cache, 'data> WindowsRecorder<'cache, 'data> {
pub fn new(
loader: &'data Loader,
allowlist: TargetAllowList,
module_allowlist: AllowList,
cache: &'cache DebugInfoCache,
) -> Self {
let breakpoints = Breakpoints::default();
Expand All @@ -49,7 +49,7 @@ impl<'cache, 'data> WindowsRecorder<'cache, 'data> {
let stop_error = None;

Self {
allowlist,
module_allowlist,
breakpoints,
cache,
deferred_breakpoints,
Expand All @@ -60,12 +60,12 @@ impl<'cache, 'data> WindowsRecorder<'cache, 'data> {
}
}

pub fn allowlist(&self) -> &TargetAllowList {
&self.allowlist
pub fn module_allowlist(&self) -> &AllowList {
&self.module_allowlist
}

pub fn allowlist_mut(&mut self) -> &mut TargetAllowList {
&mut self.allowlist
pub fn module_allowlist_mut(&mut self) -> &mut AllowList {
&mut self.module_allowlist
}

fn try_on_create_process(&mut self, dbg: &mut Debugger, module: &ModuleLoadInfo) -> Result<()> {
Expand Down Expand Up @@ -163,7 +163,7 @@ impl<'cache, 'data> WindowsRecorder<'cache, 'data> {
return Ok(());
}

if !self.allowlist.modules.is_allowed(&path) {
if !self.module_allowlist.is_allowed(&path) {
debug!("not inserting denylisted module: {path}");
return Ok(());
}
Expand Down
6 changes: 3 additions & 3 deletions src/agent/coverage/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ impl From<Line> for u32 {

pub fn binary_to_source_coverage(
binary: &BinaryCoverage,
allowlist: impl Into<Option<AllowList>>,
source_allowlist: impl Into<Option<AllowList>>,
) -> Result<SourceCoverage> {
use std::collections::btree_map::Entry;

use symbolic::debuginfo::Object;
use symbolic::symcache::{SymCache, SymCacheConverter};

let allowlist = allowlist.into().unwrap_or_default();
let source_allowlist = source_allowlist.into().unwrap_or_default();
let loader = Loader::new();

let mut source = SourceCoverage::default();
Expand Down Expand Up @@ -106,7 +106,7 @@ pub fn binary_to_source_coverage(

if let Some(file) = location.file() {
// Only include relevant inlinees.
if !allowlist.is_allowed(&file.full_path()) {
if !source_allowlist.is_allowed(&file.full_path()) {
continue;
}

Expand Down
Loading
Loading