Skip to content

Commit

Permalink
Feature/state machine selection (#786)
Browse files Browse the repository at this point in the history
* Create statemachine for llvm coverage instrumented tests
* Start rearchitecturing to allow for state machine selection at runtime
* Decoupling the execution and collecting coverage to make it more suitable for more test launch scenarios. The previous collecting of coverage from within a fork call limited how the API could be repurposed.
* Based on tracer engine either `execve` for ptrace or launch llvm instrumented binaries from execute
* Hook in CLI to TraceEngine as a hidden `--engine` argument
* Implement statemachine selection at runtime
*  Checks if ptrace is supported or if llvm coverage is selected and run the correct state machine. This does add a boxed trait but it's worth it
* Launch llvm test and get list of generated profraws. Currently, just prints out executable and the profraws it generated
  • Loading branch information
xd009642 authored Jul 8, 2021
1 parent 52317d9 commit 7d37144
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 109 deletions.
8 changes: 2 additions & 6 deletions src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,8 @@ fn clean_doctest_folder<P: AsRef<Path>>(doctest_dir: P) {
}

fn handle_llvm_flags(value: &mut String, config: &Config) {
if (config.engine == TraceEngine::Auto || config.engine == TraceEngine::Llvm)
&& supports_llvm_coverage()
{
if config.engine() == TraceEngine::Llvm {
value.push_str("-Z instrument-coverage ");
} else if config.engine == TraceEngine::Llvm {
error!("unable to utilise llvm coverage, due to compiler support. Falling back to Ptrace");
}
}

Expand Down Expand Up @@ -691,7 +687,7 @@ fn setup_environment(cmd: &mut Command, config: &Config) {
cmd.env(rustdoc, value);
}

fn supports_llvm_coverage() -> bool {
pub fn supports_llvm_coverage() -> bool {
if let Some(version) = CARGO_VERSION_INFO.as_ref() {
version.supports_llvm_cov()
} else {
Expand Down
37 changes: 28 additions & 9 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub use self::types::*;

use self::parse::*;
pub use self::types::*;
use crate::cargo::supports_llvm_coverage;
use cargo_metadata::{Metadata, MetadataCommand, Package};
use clap::{value_t, ArgMatches};
use coveralls_api::CiService;
Expand Down Expand Up @@ -166,7 +166,7 @@ pub struct Config {
/// Number of jobs used for building the tests
pub jobs: Option<usize>,
/// Engine to use to collect coverage
pub engine: TraceEngine,
engine: RefCell<TraceEngine>,
/// Specifying per-config rust flags
pub rustflags: Option<String>,
}
Expand Down Expand Up @@ -231,7 +231,7 @@ impl Default for Config {
avoid_cfg_tarpaulin: false,
jobs: None,
color: Color::Auto,
engine: TraceEngine::Ptrace,
engine: RefCell::new(TraceEngine::Ptrace),
rustflags: None,
}
}
Expand Down Expand Up @@ -263,11 +263,14 @@ impl<'a> From<&'a ArgMatches<'a>> for ConfigWrapper {
}
};

let engine = value_t!(args.value_of("engine"), TraceEngine).unwrap_or(TraceEngine::Ptrace);

let args_config = Config {
name: String::new(),
manifest: get_manifest(args),
config: None,
root: get_root(args),
engine: RefCell::new(engine),
command: value_t!(args.value_of("command"), Mode).unwrap_or(Mode::Test),
color: value_t!(args.value_of("color"), Color).unwrap_or(Color::Auto),
run_types: get_run_types(args),
Expand Down Expand Up @@ -318,7 +321,6 @@ impl<'a> From<&'a ArgMatches<'a>> for ConfigWrapper {
metadata: RefCell::new(None),
avoid_cfg_tarpaulin: args.is_present("avoid-cfg-tarpaulin"),
rustflags: get_rustflags(args),
engine: TraceEngine::Ptrace,
};
if args.is_present("ignore-config") {
Self(vec![args_config])
Expand All @@ -343,6 +345,22 @@ impl<'a> From<&'a ArgMatches<'a>> for ConfigWrapper {
}

impl Config {
/// This returns the engine selected for tarpaulin to run. This function will not return Auto
/// instead it will resolve to the best-fit `TraceEngine` for the given configuration
pub fn engine(&self) -> TraceEngine {
let engine = *self.engine.borrow();
match engine {
TraceEngine::Auto | TraceEngine::Llvm if supports_llvm_coverage() => TraceEngine::Llvm,
engine => {
if engine == TraceEngine::Llvm {
error!("unable to utilise llvm coverage, due to compiler support. Falling back to Ptrace");
self.engine.replace(TraceEngine::Ptrace);
}
TraceEngine::Ptrace
}
}
}

pub fn target_dir(&self) -> PathBuf {
if let Some(s) = &self.target_dir {
s.clone()
Expand Down Expand Up @@ -677,26 +695,27 @@ impl Config {
.any(|x| x.is_match(project.to_str().unwrap_or("")))
}

///
/// returns the relative path from the base_dir
/// uses root if set, else env::current_dir()
///
#[inline]
pub fn get_base_dir(&self) -> PathBuf {
if let Some(root) = &self.root {
if Path::new(root).is_absolute() {
PathBuf::from(root)
} else {
let base_dir = env::current_dir().unwrap();
base_dir.join(root).canonicalize().unwrap()
if let Ok(res) = base_dir.join(root).canonicalize() {
res
} else {
base_dir
}
}
} else {
env::current_dir().unwrap()
}
}

/// returns the relative path from the base_dir
///
#[inline]
pub fn strip_base_dir(&self, path: &Path) -> PathBuf {
path_relative_from(path, &self.get_base_dir()).unwrap_or_else(|| path.to_path_buf())
Expand Down
6 changes: 6 additions & 0 deletions src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum RunError {
Cargo(String),
/// Error trying to resolve package configuration in manifest
Packages(String),
/// Failure when attempting to launch test
TestLaunch(String),
/// Tests failed to compile
TestCompile(String),
/// Test failed during run
Expand All @@ -33,6 +35,8 @@ pub enum RunError {
Internal,
/// Tuple of actual coverage and threshold
BelowThreshold(f64, f64),
/// Error relating to tracing engine selected
Engine(String),
}

impl Display for RunError {
Expand All @@ -41,6 +45,7 @@ impl Display for RunError {
Self::Manifest(e) => write!(f, "Failed to parse Cargo.toml! Error: {}", e),
Self::Cargo(e) => write!(f, "Cargo failed to run! Error: {}", e),
Self::Packages(e) => write!(f, "Failed to resolve package in manifest! Error: {}", e),
Self::TestLaunch(e) => write!(f, "Failed to launch test: {}", e),
Self::TestCompile(e) => write!(f, "Failed to compile tests! Error: {}", e),
Self::TestRuntime(e) => write!(f, "Failed to run tests: {}", e),
Self::TestFailed => write!(f, "Test failed during run"),
Expand All @@ -62,6 +67,7 @@ impl Display for RunError {
Self::BelowThreshold(a, e) => {
write!(f, "Coverage is below the failure threshold {}% < {}%", a, e)
}
Self::Engine(s) => write!(f, "Engine error: {}", s),
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use cargo_tarpaulin::cargo::{rust_flags, rustdoc_flags};
use cargo_tarpaulin::config::{Color, Config, ConfigWrapper, Mode, OutputFile, RunType};
use cargo_tarpaulin::config::{
Color, Config, ConfigWrapper, Mode, OutputFile, RunType, TraceEngine,
};
use cargo_tarpaulin::{run, setup_logging};
use clap::{crate_version, App, Arg, ArgSettings, SubCommand};
use std::collections::HashMap;
Expand Down Expand Up @@ -103,6 +105,11 @@ fn main() -> Result<(), String> {
.possible_values(&OutputFile::variants())
.case_insensitive(true)
.multiple(true),
Arg::from_usage("--engine [ENGINE] 'Coverage tracing backend to use'")
.possible_values(&TraceEngine::variants())
.case_insensitive(true)
.multiple(false)
.hidden(true),
Arg::from_usage("--output-dir [PATH] 'Specify a custom directory to write report files'"),
Arg::from_usage("--run-types [TYPE]... 'Type of the coverage run'")
.possible_values(&RunType::variants())
Expand Down
45 changes: 23 additions & 22 deletions src/process_handling/linux.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
use crate::collect_coverage;
use crate::config::types::Mode;
use crate::errors::*;
use crate::event_log::*;
use crate::process_handling::execute_test;
use crate::ptrace_control::*;
use crate::source_analysis::LineAnalysis;
use crate::traces::*;
use crate::Config;
use crate::TestBinary;
use crate::TestHandle;
use nix::errno::Errno;
use nix::libc::{c_int, c_long};
use nix::sched::*;
use nix::unistd::*;
use nix::Error;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::path::PathBuf;
use std::path::Path;
use tracing::{info, warn};

#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
Expand All @@ -35,29 +31,19 @@ mod ffi {
/// Returns the coverage statistics for a test executable in the given workspace
pub fn get_test_coverage(
test: &TestBinary,
analysis: &HashMap<PathBuf, LineAnalysis>,
config: &Config,
ignored: bool,
logger: &Option<EventLog>,
) -> Result<Option<(TraceMap, i32)>, RunError> {
) -> Result<Option<TestHandle>, RunError> {
if !test.path().exists() {
warn!("Test at {} doesn't exist", test.path().display());
return Ok(None);
}
if let Err(e) = limit_affinity() {
warn!("Failed to set processor affinity {}", e);
}
if let Some(log) = logger.as_ref() {
log.push_binary(test.clone());
}
unsafe {
match fork() {
Ok(ForkResult::Parent { child }) => {
match collect_coverage(test.path(), child, analysis, config, logger) {
Ok(t) => Ok(Some(t)),
Err(e) => Err(RunError::TestCoverage(e.to_string())),
}
}
Ok(ForkResult::Parent { child }) => Ok(Some(TestHandle::Id(child))),
Ok(ForkResult::Child) => {
let bin_type = match config.command {
Mode::Test => "test",
Expand Down Expand Up @@ -104,14 +90,29 @@ pub fn limit_affinity() -> nix::Result<()> {
sched_setaffinity(this, &cpu_set)
}

pub fn execute(program: CString, argv: &[CString], envar: &[CString]) -> Result<(), RunError> {
pub fn execute(
test: &Path,
argv: &[String],
envar: &[(String, String)],
) -> Result<TestHandle, RunError> {
let program = CString::new(test.display().to_string()).unwrap_or_default();
disable_aslr().map_err(|e| RunError::TestRuntime(format!("ASLR disable failed: {}", e)))?;

request_trace().map_err(|e| RunError::Trace(e.to_string()))?;

let envar = envar
.iter()
.map(|(k, v)| CString::new(format!("{}={}", k, v).as_str()).unwrap_or_default())
.collect::<Vec<CString>>();

let argv = argv
.iter()
.map(|x| CString::new(x.as_str()).unwrap_or_default())
.collect::<Vec<CString>>();

let arg_ref = argv.iter().map(|x| x.as_ref()).collect::<Vec<&CStr>>();
let env_ref = envar.iter().map(|x| x.as_ref()).collect::<Vec<&CStr>>();
execve(&program, &arg_ref, &env_ref)
.map_err(|_| RunError::Internal)
.map(|_| ())
execve(&program, &arg_ref, &env_ref).map_err(|_| RunError::Internal)?;

unreachable!();
}
Loading

0 comments on commit 7d37144

Please sign in to comment.