use crate::cargo::TestBinary;
use crate::config::*;
use crate::errors::*;
use crate::event_log::*;
use crate::path_utils::*;
use crate::process_handling::*;
use crate::report::report_coverage;
use crate::source_analysis::{LineAnalysis, SourceAnalysis};
use crate::test_loader::*;
use crate::traces::*;
use std::ffi::OsString;
use std::fs::{create_dir_all, remove_dir_all};
use tracing::{debug, error, info, warn};
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
pub mod args;
pub mod cargo;
pub mod config;
pub mod errors;
pub mod event_log;
pub mod path_utils;
mod process_handling;
pub mod report;
pub mod source_analysis;
pub mod statemachine;
pub mod test_loader;
pub mod traces;
const RUST_LOG_ENV: &str = "RUST_LOG";
#[cfg(not(tarpaulin_include))]
pub fn setup_logging(color: Color, debug: bool, verbose: bool) {
let base_exceptions = |env: EnvFilter| {
if debug {
env.add_directive("cargo_tarpaulin=trace".parse().unwrap())
.add_directive("llvm_profparser=trace".parse().unwrap())
} else if verbose {
env.add_directive("cargo_tarpaulin=debug".parse().unwrap())
.add_directive("llvm_profparser=warn".parse().unwrap())
} else {
env.add_directive("cargo_tarpaulin=info".parse().unwrap())
.add_directive("llvm_profparser=error".parse().unwrap())
}
.add_directive(LevelFilter::INFO.into())
};
let filter = match std::env::var_os(RUST_LOG_ENV).map(OsString::into_string) {
Some(Ok(env)) => {
let mut filter = base_exceptions(EnvFilter::new(""));
for s in env.split(',') {
match s.parse() {
Ok(d) => filter = filter.add_directive(d),
Err(err) => println!("WARN ignoring log directive: `{s}`: {err}"),
};
}
filter
}
_ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)),
};
let with_ansi = color != Color::Never;
let res = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::ERROR)
.with_env_filter(filter)
.with_ansi(with_ansi)
.try_init();
if let Err(e) = res {
eprintln!("Logging may be misconfigured: {e}");
}
debug!("set up logging");
}
pub fn trace(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
let logger = create_logger(configs);
let mut tracemap = TraceMap::new();
let mut ret = 0;
let mut fail_fast_ret = 0;
let mut tarpaulin_result = Ok(());
let mut bad_threshold = Ok(());
for config in configs.iter() {
if config.name == "report" {
continue;
}
if let Some(log) = logger.as_ref() {
let name = config_name(config);
log.push_config(name);
}
create_target_dir(config);
match launch_tarpaulin(config, &logger) {
Ok((t, r)) => {
if config.no_fail_fast {
fail_fast_ret |= r;
} else {
ret |= r;
}
if configs.len() > 1 {
bad_threshold = check_fail_threshold(&t, config);
}
tracemap.merge(&t);
}
Err(e) => {
error!("{e}");
tarpaulin_result = tarpaulin_result.and(Err(e));
}
}
}
tracemap.dedup();
if let Err(bad_limit) = bad_threshold {
let _ = report_coverage(&configs[0], &tracemap);
Err(bad_limit)
} else if ret == 0 {
tarpaulin_result.map(|_| (tracemap, fail_fast_ret))
} else {
Err(RunError::TestFailed)
}
}
fn create_logger(configs: &[Config]) -> Option<EventLog> {
if configs.iter().any(|c| c.dump_traces) {
let config = if let Some(c) = configs.iter().find(|c| c.output_directory.is_some()) {
c
} else {
&configs[0]
};
Some(EventLog::new(
configs.iter().map(|x| x.root()).collect(),
config,
))
} else {
None
}
}
fn create_target_dir(config: &Config) {
let path = config.target_dir();
if !path.exists() {
if let Err(e) = create_dir_all(&path) {
warn!("Failed to create target-dir {}", e);
}
}
}
fn config_name(config: &Config) -> String {
if config.name.is_empty() {
"<anonymous>".to_string()
} else {
config.name.clone()
}
}
fn check_fail_threshold(traces: &TraceMap, config: &Config) -> Result<(), RunError> {
let percent = traces.coverage_percentage() * 100.0;
match config.fail_under.as_ref() {
Some(limit) if percent < *limit => {
let error = RunError::BelowThreshold(percent, *limit);
error!("{}", error);
Err(error)
}
_ => Ok(()),
}
}
pub fn run(configs: &[Config]) -> Result<(), RunError> {
if configs.iter().any(|x| x.engine() == TraceEngine::Llvm) {
let profraw_dir = configs[0].profraw_dir();
let _ = remove_dir_all(&profraw_dir);
if let Err(e) = create_dir_all(&profraw_dir) {
warn!(
"Unable to create profraw directory in tarpaulin's target folder: {}",
e
);
}
}
let (tracemap, ret) = collect_tracemap(configs)?;
report_tracemap(configs, tracemap)?;
if ret != 0 {
Err(RunError::TestFailed)
} else {
Ok(())
}
}
fn collect_tracemap(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
let (mut tracemap, ret) = trace(configs)?;
if !configs.is_empty() {
for dir in get_source_walker(&configs[0]) {
tracemap.add_file(dir.path());
}
}
Ok((tracemap, ret))
}
pub fn report_tracemap(configs: &[Config], tracemap: TraceMap) -> Result<(), RunError> {
let mut reported = false;
for c in configs.iter() {
if c.no_run || c.name != "report" {
continue;
}
report_coverage_with_check(c, &tracemap)?;
reported = true;
}
if !reported && !configs.is_empty() && !configs[0].no_run {
report_coverage_with_check(&configs[0], &tracemap)?;
}
Ok(())
}
fn report_coverage_with_check(c: &Config, tracemap: &TraceMap) -> Result<(), RunError> {
report_coverage(c, tracemap)?;
check_fail_threshold(tracemap, c)
}
pub fn launch_tarpaulin(
config: &Config,
logger: &Option<EventLog>,
) -> Result<(TraceMap, i32), RunError> {
if !config.name.is_empty() {
info!("Running config {}", config.name);
}
info!("Running Tarpaulin");
let mut result = TraceMap::new();
let mut return_code = 0i32;
info!("Building project");
let executables = cargo::get_tests(config)?;
if !config.no_run {
let project_analysis = SourceAnalysis::get_analysis(config);
result.set_functions(project_analysis.create_function_map());
let project_analysis = project_analysis.lines;
let mut other_bins = config.objects().to_vec();
other_bins.extend(executables.binaries.iter().cloned());
for exe in &executables.test_binaries {
if exe.should_panic() {
info!("Running a test executable that is expected to panic");
}
let coverage =
get_test_coverage(exe, &other_bins, &project_analysis, config, false, logger);
let coverage = match coverage {
Ok(coverage) => coverage,
Err(run_error) => {
if config.no_fail_fast {
info!("No failing fast!");
return_code = 101;
None
} else {
return Err(run_error);
}
}
};
if let Some(res) = coverage {
result.merge(&res.0);
return_code |= if exe.should_panic() {
(res.1 == 0).into()
} else {
res.1
};
}
if config.run_ignored {
let coverage =
get_test_coverage(exe, &other_bins, &project_analysis, config, true, logger);
let coverage = match coverage {
Ok(coverage) => coverage,
Err(run_error) => {
if config.no_fail_fast {
return_code = 101;
None
} else {
return Err(run_error);
}
}
};
if let Some(res) = coverage {
result.merge(&res.0);
return_code |= res.1;
}
}
if config.fail_immediately && return_code != 0 {
return Err(RunError::TestFailed);
}
}
result.dedup();
}
Ok((result, return_code))
}