From 7d37144d09c09297550aaaad84ab399c2db7dbd0 Mon Sep 17 00:00:00 2001 From: xd009642 Date: Thu, 8 Jul 2021 09:18:51 +0100 Subject: [PATCH] Feature/state machine selection (#786) * 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 --- src/cargo.rs | 8 +- src/config/mod.rs | 37 ++++-- src/errors/mod.rs | 6 + src/main.rs | 9 +- src/process_handling/linux.rs | 45 +++---- src/process_handling/mod.rs | 198 +++++++++++++++++++++++-------- src/statemachine/instrumented.rs | 104 ++++++++++++++++ src/statemachine/linux.rs | 11 +- src/statemachine/mod.rs | 78 +++++++++--- 9 files changed, 387 insertions(+), 109 deletions(-) create mode 100644 src/statemachine/instrumented.rs diff --git a/src/cargo.rs b/src/cargo.rs index 0dac75819c..6b760d3dd4 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -549,12 +549,8 @@ fn clean_doctest_folder>(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"); } } @@ -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 { diff --git a/src/config/mod.rs b/src/config/mod.rs index feede3745a..2ab46c2316 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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; @@ -166,7 +166,7 @@ pub struct Config { /// Number of jobs used for building the tests pub jobs: Option, /// Engine to use to collect coverage - pub engine: TraceEngine, + engine: RefCell, /// Specifying per-config rust flags pub rustflags: Option, } @@ -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, } } @@ -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), @@ -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]) @@ -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() @@ -677,10 +695,8 @@ 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 { @@ -688,7 +704,11 @@ impl Config { 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() @@ -696,7 +716,6 @@ impl Config { } /// 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()) diff --git a/src/errors/mod.rs b/src/errors/mod.rs index a08fb6e5c5..72ccd3a1d4 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -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 @@ -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 { @@ -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"), @@ -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), } } } diff --git a/src/main.rs b/src/main.rs index ef9cfbdfd1..c49a0f0585 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; @@ -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()) diff --git a/src/process_handling/linux.rs b/src/process_handling/linux.rs index 2263fe055b..ab092710dc 100644 --- a/src/process_handling/linux.rs +++ b/src/process_handling/linux.rs @@ -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"))] @@ -35,11 +31,9 @@ mod ffi { /// Returns the coverage statistics for a test executable in the given workspace pub fn get_test_coverage( test: &TestBinary, - analysis: &HashMap, config: &Config, ignored: bool, - logger: &Option, -) -> Result, RunError> { +) -> Result, RunError> { if !test.path().exists() { warn!("Test at {} doesn't exist", test.path().display()); return Ok(None); @@ -47,17 +41,9 @@ pub fn get_test_coverage( 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", @@ -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 { + 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::>(); + + let argv = argv + .iter() + .map(|x| CString::new(x.as_str()).unwrap_or_default()) + .collect::>(); + let arg_ref = argv.iter().map(|x| x.as_ref()).collect::>(); let env_ref = envar.iter().map(|x| x.as_ref()).collect::>(); - execve(&program, &arg_ref, &env_ref) - .map_err(|_| RunError::Internal) - .map(|_| ()) + execve(&program, &arg_ref, &env_ref).map_err(|_| RunError::Internal)?; + + unreachable!(); } diff --git a/src/process_handling/mod.rs b/src/process_handling/mod.rs index 10311d7ff7..3567c56ee9 100644 --- a/src/process_handling/mod.rs +++ b/src/process_handling/mod.rs @@ -5,9 +5,118 @@ use crate::traces::*; use crate::{Config, EventLog, LineAnalysis, RunError, TestBinary, TraceEngine}; use std::collections::HashMap; use std::env; -use std::ffi::CString; +use std::ffi::OsStr; +use std::fmt; +use std::fs; use std::path::{Path, PathBuf}; -use tracing::{info, trace_span}; +use std::process::{Child, Command}; +use tracing::{error, info, trace_span}; + +/// Handle to a test currently either PID or a `std::process::Child` +pub enum TestHandle { + Id(ProcessHandle), + Process(RunningProcessHandle), +} + +pub struct RunningProcessHandle { + /// Used to map coverage counters to line numbers + pub(crate) path: PathBuf, + /// Get the exit status to work out if tests have passed + pub(crate) child: Child, + /// maintain a list of existing profraws in the project root to avoid picking up old results + pub(crate) existing_profraws: Vec, +} + +impl RunningProcessHandle { + pub fn new(path: PathBuf, cmd: &mut Command, config: &Config) -> Result { + let child = cmd.spawn()?; + let existing_profraws = fs::read_dir(config.root())? + .into_iter() + .filter_map(|x| x.ok()) + .filter(|x| x.path().is_file() && x.path().extension() == Some(OsStr::new("profraw"))) + .map(|x| x.path()) + .collect(); + + Ok(Self { + path, + child, + existing_profraws, + }) + } +} + +impl fmt::Display for TestHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TestHandle::Id(id) => write!(f, "{}", id), + TestHandle::Process(c) => write!(f, "{}", c.child.id()), + } + } +} + +impl From for TestHandle { + fn from(handle: ProcessHandle) -> Self { + Self::Id(handle) + } +} + +impl From for TestHandle { + fn from(handle: RunningProcessHandle) -> Self { + Self::Process(handle) + } +} + +pub fn get_test_coverage( + test: &TestBinary, + analysis: &HashMap, + config: &Config, + ignored: bool, + logger: &Option, +) -> Result, RunError> { + let handle = launch_test(test, config, ignored, logger)?; + if let Some(handle) = handle { + match collect_coverage(test.path(), handle, analysis, config, logger) { + Ok(t) => Ok(Some(t)), + Err(e) => Err(RunError::TestCoverage(e.to_string())), + } + } else { + Ok(None) + } +} + +fn launch_test( + test: &TestBinary, + config: &Config, + ignored: bool, + logger: &Option, +) -> Result, RunError> { + if let Some(log) = logger.as_ref() { + log.push_binary(test.clone()); + } + match config.engine() { + TraceEngine::Ptrace => { + cfg_if::cfg_if! { + if #[cfg(target_os="linux")] { + linux::get_test_coverage(test, config, ignored) + } else { + error!("Ptrace is not supported on this platform"); + Err(RunError::TestCoverage("Unsupported OS".to_string())) + } + } + } + TraceEngine::Llvm => { + let res = execute_test(test, ignored, config)?; + Ok(Some(res)) + } + e => { + error!( + "Tarpaulin cannot execute tests with {:?} on this platform", + e + ); + Err(RunError::TestCoverage("Unsupported OS".to_string())) + } + } +} cfg_if::cfg_if! { if #[cfg(target_os= "linux")] { @@ -20,30 +129,13 @@ cfg_if::cfg_if! { pub type ProcessHandle = nix::unistd::Pid; } else { pub type ProcessHandle = u64; - - /// Returns the coverage statistics for a test executable in the given workspace - pub fn get_test_coverage( - test: &TestBinary, - analysis: &HashMap, - config: &Config, - ignored: bool, - logger: &Option, - ) -> Result, RunError> { - tracing::error!("Tarpaulin does not support executing tests on this platform"); - Err(RunError::TestCoverage("Unsupported OS".to_string())) - } - - pub fn execute(program: CString, argv: &[CString], envar: &[CString]) -> Result<(), RunError> { - tracing::error!("Tarpaulin does not support executing tests on this platform"); - Err(RunError::TestCoverage("Unsupported OS".to_string())) - } } } /// Collects the coverage data from the launched test pub(crate) fn collect_coverage( test_path: &Path, - test: ProcessHandle, + test: TestHandle, analysis: &HashMap, config: &Config, logger: &Option, @@ -69,56 +161,62 @@ pub(crate) fn collect_coverage( } /// Launches the test executable -fn execute_test(test: &TestBinary, ignored: bool, config: &Config) -> Result<(), RunError> { - let exec_path = CString::new(test.path().to_str().unwrap()).unwrap(); +fn execute_test(test: &TestBinary, ignored: bool, config: &Config) -> Result { info!("running {}", test.path().display()); let _ = match test.manifest_dir() { Some(md) => env::set_current_dir(&md), None => env::set_current_dir(&config.root()), }; - let mut envars: Vec = Vec::new(); + let mut envars: Vec<(String, String)> = Vec::new(); for (key, value) in env::vars() { - let mut temp = String::new(); - temp.push_str(key.as_str()); - temp.push('='); - temp.push_str(value.as_str()); - envars.push(CString::new(temp).unwrap()); - } - let mut argv = if ignored { - vec![exec_path.clone(), CString::new("--ignored").unwrap()] - } else { - vec![exec_path.clone()] - }; - if config.verbose { - envars.push(CString::new("RUST_BACKTRACE=1").unwrap()); + envars.push((key.to_string(), value.to_string())); + } + let mut argv = vec![]; + if ignored { + argv.push("--ignored".to_string()); } - for s in &config.varargs { - argv.push(CString::new(s.as_bytes()).unwrap_or_default()); + if config.verbose { + envars.push(("RUST_BACKTRACE".to_string(), "1".to_string())); } + argv.extend_from_slice(&config.varargs); if config.color != Color::Auto { - argv.push(CString::new("--color").unwrap_or_default()); - argv.push(CString::new(config.color.to_string().to_ascii_lowercase()).unwrap_or_default()); + argv.push("--color".to_string()); + argv.push(config.color.to_string().to_ascii_lowercase()); } if let Some(s) = test.pkg_name() { - envars.push(CString::new(format!("CARGO_PKG_NAME={}", s)).unwrap_or_default()); + envars.push(("CARGO_PKG_NAME".to_string(), s.to_string())); } if let Some(s) = test.pkg_version() { - envars.push(CString::new(format!("CARGO_PKG_VERSION={}", s)).unwrap_or_default()); + envars.push(("CARGO_PKG_VERSION".to_string(), s.to_string())); } if let Some(s) = test.pkg_authors() { - envars.push(CString::new(format!("CARGO_PKG_AUTHORS={}", s.join(":"))).unwrap_or_default()); + envars.push(("CARGO_PKG_AUTHORS".to_string(), s.join(":"))); } if let Some(s) = test.manifest_dir() { - envars - .push(CString::new(format!("CARGO_MANIFEST_DIR={}", s.display())).unwrap_or_default()); - } - if config.engine == TraceEngine::Llvm || config.engine == TraceEngine::Auto { - // Used for llvm coverage to avoid report naming clashes - envars.push(CString::new("LLVM_PROFILE_FILE=default_%p.profraw").unwrap_or_default()); + envars.push(("CARGO_MANIFEST_DIR".to_string(), s.display().to_string())); } + match config.engine() { + TraceEngine::Llvm => { + // Used for llvm coverage to avoid report naming clashes TODO could have clashes + // between runs + envars.push(( + "LLVM_PROFILE_FILE".to_string(), + "default_%p.profraw".to_string(), + )); + let mut child = Command::new(test.path()); + child.envs(envars).args(&argv); - execute(exec_path, &argv, envars.as_slice()) + let hnd = RunningProcessHandle::new(test.path().to_path_buf(), &mut child, config)?; + Ok(hnd.into()) + } + #[cfg(target_os = "linux")] + TraceEngine::Ptrace => execute(test.path(), &argv, envars.as_slice()), + e => Err(RunError::Engine(format!( + "invalid execution engine {:?}", + e + ))), + } } diff --git a/src/statemachine/instrumented.rs b/src/statemachine/instrumented.rs new file mode 100644 index 0000000000..8db7949517 --- /dev/null +++ b/src/statemachine/instrumented.rs @@ -0,0 +1,104 @@ +use crate::config::Config; +use crate::errors::RunError; +use crate::process_handling::RunningProcessHandle; +use crate::source_analysis::LineAnalysis; +use crate::statemachine::*; +use crate::TestHandle; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs; +use std::path::PathBuf; +use tracing::info; + +pub fn create_state_machine<'a>( + test: impl Into, + traces: &'a mut TraceMap, + analysis: &'a HashMap, + config: &'a Config, + event_log: &'a Option, +) -> (TestState, LlvmInstrumentedData<'a>) { + let handle = test.into(); + if let TestHandle::Process(process) = handle { + let llvm = LlvmInstrumentedData { + process: Some(process), + event_log, + config, + traces, + analysis, + }; + (TestState::start_state(), llvm) + } else { + error!("The llvm cov statemachine requires a process::Child"); + let invalid = LlvmInstrumentedData { + process: None, + config, + event_log, + traces, + analysis, + }; + (TestState::End(1), invalid) + } +} + +/// Handle to the process for an instrumented binary. This will simply +pub struct LlvmInstrumentedData<'a> { + /// Parent pid of the test + process: Option, + /// Program config + config: &'a Config, + /// Optional event log to update as the test progresses + event_log: &'a Option, + /// Instrumentation points in code with associated coverage data + traces: &'a mut TraceMap, + /// Source analysis, needed in case we need to follow any executables + analysis: &'a HashMap, +} + +impl<'a> StateData for LlvmInstrumentedData<'a> { + fn start(&mut self) -> Result, RunError> { + // Nothing needs to be done at startup as this runs like a normal process + Ok(Some(TestState::wait_state())) + } + + fn init(&mut self) -> Result { + // Nothing needs to be done at init as this runs like a normal process + unreachable!(); + } + + fn wait(&mut self) -> Result, RunError> { + if let Some(parent) = self.process.as_mut() { + match parent.child.wait() { + Ok(exit) => { + let profraws = fs::read_dir(self.config.root())? + .into_iter() + .filter_map(|x| x.ok()) + .filter(|x| { + x.path().is_file() + && x.path().extension() == Some(OsStr::new("profraw")) + && !parent.existing_profraws.contains(&x.path()) + }) + .map(|x| x.path()) + .collect::>(); + + info!( + "For binary: {}", + self.config.strip_base_dir(&parent.path).display() + ); + for prof in &profraws { + info!("Generated: {}", self.config.strip_base_dir(&prof).display()); + } + self.process = None; + let code = exit.code().unwrap_or(1); + Ok(Some(TestState::End(code))) + } + Err(e) => Err(e.into()), + } + } else { + Err(RunError::TestCoverage("Test was not launched".to_string())) + } + } + + fn stop(&mut self) -> Result { + unreachable!(); + } +} diff --git a/src/statemachine/linux.rs b/src/statemachine/linux.rs index 4958375e67..e7b704801c 100644 --- a/src/statemachine/linux.rs +++ b/src/statemachine/linux.rs @@ -6,6 +6,7 @@ use crate::generate_tracemap; use crate::ptrace_control::*; use crate::source_analysis::LineAnalysis; use crate::statemachine::*; +use crate::TestHandle; use nix::errno::Errno; use nix::sys::signal::Signal; use nix::sys::wait::*; @@ -56,14 +57,20 @@ pub struct TracedProcess { } pub fn create_state_machine<'a>( - test: Pid, + test: impl Into, traces: &'a mut TraceMap, source_analysis: &'a HashMap, config: &'a Config, event_log: &'a Option, ) -> (TestState, LinuxData<'a>) { let mut data = LinuxData::new(traces, source_analysis, config, event_log); - data.parent = test; + let handle = test.into(); + match handle { + TestHandle::Id(test) => { + data.parent = test; + } + _ => unreachable!("Test handle must be a PID for ptrace engine"), + } (TestState::start_state(), data) } diff --git a/src/statemachine/mod.rs b/src/statemachine/mod.rs index 97e1e1f6db..53291a6d89 100644 --- a/src/statemachine/mod.rs +++ b/src/statemachine/mod.rs @@ -1,32 +1,54 @@ -use crate::config::Config; +use crate::config::{Config, TraceEngine}; use crate::errors::RunError; use crate::event_log::*; use crate::traces::*; +use crate::LineAnalysis; +use crate::TestHandle; +use std::collections::HashMap; +use std::path::PathBuf; use std::time::Instant; use tracing::error; -#[cfg(target_os = "linux")] -pub mod linux; - +pub mod instrumented; cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { - pub use linux::*; - } else { - use std::collections::HashMap; - use std::path::PathBuf; - use crate::LineAnalysis; - - pub fn create_state_machine<'a>( - test: crate::ProcessHandle, - traces: &'a mut TraceMap, - source_analysis: &'a HashMap, - config: &'a Config, - event_log: &'a Option, - ) -> (TestState, ()) { + pub mod linux; + pub use linux::ProcessInfo; + } +} +pub fn create_state_machine<'a>( + test: impl Into, + traces: &'a mut TraceMap, + source_analysis: &'a HashMap, + config: &'a Config, + event_log: &'a Option, +) -> (TestState, Box) { + match config.engine() { + TraceEngine::Ptrace => { + cfg_if::cfg_if! { + if #[cfg(target_os="linux")] { + let (state, machine) = linux::create_state_machine(test, traces, source_analysis, config, event_log); + (state, Box::new(machine)) + } else { + error!("The ptrace backend is not supported on this system"); + (TestState::End(1), Box::new(())) + } + } + } + TraceEngine::Llvm => { + let (state, machine) = instrumented::create_state_machine( + test, + traces, + source_analysis, + config, + event_log, + ); + (state, Box::new(machine)) + } + _ => { error!("Tarpaulin is not currently supported on this system"); - (TestState::End(1), ()) + (TestState::End(1), Box::new(())) } - } } @@ -112,6 +134,24 @@ impl StateData for () { } } +impl<'a> StateData for Box { + fn start(&mut self) -> Result, RunError> { + self.as_mut().start() + } + + fn init(&mut self) -> Result { + self.as_mut().init() + } + + fn wait(&mut self) -> Result, RunError> { + self.as_mut().wait() + } + + fn stop(&mut self) -> Result { + self.as_mut().stop() + } +} + impl TestState { /// Convenience function used to check if the test has finished or errored pub fn is_finished(self) -> bool {