use crate::utils::get_test_path; use cargo_tarpaulin::event_log::EventLog; use cargo_tarpaulin::path_utils::*; use cargo_tarpaulin::traces::TraceMap; use cargo_tarpaulin::{ args::TarpaulinCli, config::{Config, ConfigWrapper, Mode, OutputFile, RunType, TraceEngine}, }; use cargo_tarpaulin::{launch_tarpaulin, run}; use clap::Parser; #[cfg(windows)] use regex::Regex; use rusty_fork::rusty_fork_test; use std::collections::HashSet; #[cfg(windows)] use std::io; use std::path::Path; #[cfg(windows)] use std::path::PathBuf; use std::time::Duration; use std::{env, fs}; use test_log::test; #[cfg(nightly)] mod doc_coverage; mod failure_thresholds; mod failures; mod line_coverage; mod test_types; mod utils; mod workspaces; pub fn check_percentage_with_cli_args( minimum_coverage: f64, has_lines: bool, args: &[String], ) -> TraceMap { let restore_dir = env::current_dir().unwrap(); let args = TarpaulinCli::parse_from(args); let mut configs = ConfigWrapper::from(args.config).0; let mut res = TraceMap::new(); for config in &mut configs { config.set_clean(false); let (t, _) = launch_tarpaulin(config, &None).unwrap(); res.merge(&t); } res.dedup(); env::set_current_dir(restore_dir).unwrap(); if has_lines { assert!( res.coverage_percentage() >= minimum_coverage, "Assertion failed {} >= {}", res.coverage_percentage(), minimum_coverage ); assert!(res.total_coverable() > 0); } res } pub fn run_config(project_name: &str, mut config: Config) { config.test_timeout = Duration::from_secs(60); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path(project_name); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); env::set_current_dir(config.root()).unwrap(); config.set_clean(false); run(&[config]).unwrap(); env::set_current_dir(restore_dir).unwrap(); } pub fn check_percentage_with_config( project_name: &str, minimum_coverage: f64, has_lines: bool, mut config: Config, ) -> TraceMap { config.test_timeout = Duration::from_secs(60); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path(project_name); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); config.set_clean(false); // Note to contributors. If an integration test fails, uncomment this to be able to see the // tarpaulin logs //cargo_tarpaulin::setup_logging(true, true, false); let event_log = if config.dump_traces { let mut paths = HashSet::new(); paths.insert(config.manifest()); Some(EventLog::new(paths, &config)) } else { None }; let (res, ret) = launch_tarpaulin(&config, &event_log).unwrap(); assert_eq!(ret, 0); env::set_current_dir(restore_dir).unwrap(); if has_lines { assert!(res.total_coverable() > 0); assert!( res.coverage_percentage() >= minimum_coverage, "Assertion failed {} >= {}", res.coverage_percentage(), minimum_coverage ); } else { assert_eq!(res.total_coverable(), 0); } res } pub fn check_percentage(project_name: &str, minimum_coverage: f64, has_lines: bool) -> TraceMap { let mut config = Config::default(); config.set_include_tests(true); config.set_clean(false); check_percentage_with_config(project_name, minimum_coverage, has_lines, config) } rusty_fork_test! { #[test] fn incorrect_manifest_path() { let mut config = Config::default(); let mut invalid = config.manifest(); invalid.push("__invalid_dir__"); config.set_manifest(invalid); config.set_clean(false); let launch = launch_tarpaulin(&config, &None); assert!(launch.is_err()); } #[test] fn proc_macro_link() { let mut config = Config::default(); config.test_timeout = Duration::from_secs(60); config.set_clean(false); let test_dir = get_test_path("proc_macro"); config.set_manifest(test_dir.join("Cargo.toml")); assert!(launch_tarpaulin(&config, &None).is_ok()); } #[test] fn array_coverage() { check_percentage("arrays", 1.0f64, true); } #[test] fn dependency_build_script() { // From issue #1297 check_percentage("metatensor", 1.0f64, true); } #[test] fn lets_coverage() { check_percentage("lets", 1.0f64, true); } #[test] #[cfg_attr(not(target_os="linux"), ignore)] // TODO So there are linker issues I can't adequately diagnose myself in windows #[cfg(not(tarpaulin))] fn picking_up_shared_objects() { // Need a project which downloads a shared object to target folder and uses build script to set // the linker path. check_percentage("torch_test", 1.0f64, true); } // Just for linux if we have ptrace as default #[test] fn llvm_sanity_test() { let mut config = Config::default(); config.set_engine(TraceEngine::Llvm); config.follow_exec = true; config.set_include_tests(true); config.set_clean(false); check_percentage_with_config("structs", 1.0f64, true, config.clone()); check_percentage_with_config("ifelse", 1.0f64, true, config.clone()); check_percentage_with_config("returns", 1.0f64, true, config.clone()); check_percentage_with_config("follow_exe", 1.0f64, true, config); } #[cfg_attr(not(ptrace_supported), test)] #[should_panic] fn ptrace_not_unsupported_system() { let config = Config::default(); config.set_engine(TraceEngine::Ptrace); run_config("simple_project", config); } #[test] fn struct_expr_coverage() { check_percentage("structs", 1.0f64, true); } #[test] fn ifelse_expr_coverage() { check_percentage("ifelse", 1.0f64, true); } #[test] fn returns_expr_coverage() { check_percentage("returns", 1.0f64, true); } #[test] fn loops_expr_coverage() { check_percentage("loops", 1.0f64, true); } #[test] fn loops_assigns_coverage() { check_percentage("assigns", 1.0f64, true); } #[test] fn paths_coverage() { check_percentage("paths", 1.0f64, true); } #[test] fn futures_coverage() { check_percentage("futures", 1.0f64, true); } #[test] fn breaks_expr_coverage() { check_percentage("breaks", 0.95f64, true); } #[test] fn continues_expr_coverage() { check_percentage("continues", 1.0f64, true); } #[test] fn boxes_coverage() { check_percentage("boxes", 1.0f64, true); } #[test] #[ignore] fn method_calls_expr_coverage() { check_percentage("method_calls", 1.0f64, true); } #[test] #[cfg(not(windows))] // TODO fix fn config_file_coverage() { let test_dir = get_test_path("configs"); let mut args = vec![ "tarpaulin".to_string(), "--root".to_string(), test_dir.display().to_string(), ]; check_percentage_with_cli_args(1.0f64, true, &args); args.push("--ignore-config".to_string()); check_percentage_with_cli_args(0.0f64, true, &args); } #[test] fn issue_966_follow_exec() { let test_dir = get_test_path("follow_exec_issue966"); let args = vec![ "tarpaulin".to_string(), "--root".to_string(), test_dir.display().to_string(), "--post-test-delay".to_string(), 10.to_string(), ]; check_percentage_with_cli_args(1.0f64, true, &args); } #[test] fn rustflags_config_coverage() { let test_dir = get_test_path("multiple_rustflags"); let mut args = vec![ "tarpaulin".to_string(), "--root".to_string(), test_dir.display().to_string(), ]; check_percentage_with_cli_args(1.0f64, true, &args); args.push("--ignore-config".to_string()); check_percentage_with_cli_args(0.0f64, false, &args); } #[test] fn match_expr_coverage() { check_percentage("matches", 1.0f64, true); } #[test] #[ignore] fn benchmark_coverage() { let test = "benchmark_coverage"; check_percentage(test, 0.0f64, true); let mut config = Config::default(); config.set_clean(false); config.run_types = vec![RunType::Benchmarks]; check_percentage_with_config(test, 1.0f64, true, config); } #[test] fn cargo_run_coverage() { let mut config = Config::default(); config.command = Mode::Build; config.set_clean(false); check_percentage_with_config("run_coverage", 1.0f64, true, config); } #[test] #[cfg(not(windows))] // TODO fix fn examples_coverage() { let test = "example_test"; check_percentage(test, 0.0f64, true); let mut config = Config::default(); config.run_types = vec![RunType::Examples]; config.set_clean(false); config.set_include_tests(true); check_percentage_with_config(test, 1.0f64, true, config.clone()); config.run_types.clear(); config.example_names.insert("say_hello".to_string()); check_percentage_with_config(test, 1.0f64, true, config); } #[test] fn access_env_var() { // This test is mainly to check that expected environment variables are present // using `CARGO_BIN_EXE_` to test let test = "env_var"; check_percentage(test, 1.0f64, true); } #[test] fn tarpaulin_attrs() { check_percentage("tarpaulin_attrs", 0.0f64, true); } #[test] #[cfg(nightly)] fn tarpaulin_tool_attr() { check_percentage("tool_attr", 0.0f64, false); } #[test] #[cfg(nightly)] fn filter_with_inner_attributes() { check_percentage("filter_inner_modules", 0.0f64, false); } #[test] fn cargo_home_filtering() { let new_home = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/HttptestAndReqwest/new_home"); let previous = env::var("CARGO_HOME"); let mut config = Config::default(); config.test_timeout = Duration::from_secs(60); config.set_clean(false); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path("HttptestAndReqwest"); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); env::set_var("CARGO_HOME", new_home.display().to_string()); let run = launch_tarpaulin(&config, &None); let _ = fs::remove_dir_all(&new_home); match previous { Ok(s) => env::set_var("CARGO_HOME", s), Err(_) => { env::remove_var("CARGO_HOME"); } } let (res, _) = run.unwrap(); env::set_current_dir(restore_dir).unwrap(); assert_eq!(res.iter().count(), 1); } #[test] fn rustflags_handling() { env::remove_var("RUSTFLAGS"); check_percentage("rustflags", 1.0f64, true); env::set_var("RUSTFLAGS", "--cfg=foo"); let mut config = Config::default(); config.set_clean(false); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path("rustflags"); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); let res = launch_tarpaulin(&config, &None); env::set_current_dir(&restore_dir).unwrap(); env::remove_var("RUSTFLAGS"); assert!(res.is_err() || res.unwrap().1 != 0); let (_, ret) = launch_tarpaulin(&config, &None).unwrap(); env::set_current_dir(&restore_dir).unwrap(); assert_eq!(ret, 0); } #[test] fn follow_exes_down() { let mut config = Config::default(); config.follow_exec = true; config.set_clean(false); check_percentage_with_config("follow_exe", 1.0f64, true, config); } #[test] fn handle_module_level_exclude_attrs() { check_percentage("crate_level_ignores", 1.0f64, true); } #[test] #[cfg(unix)] fn handle_forks() { let mut config = Config::default(); config.set_clean(false); config.set_include_tests(true); config.post_test_delay = Some(Duration::from_secs(10)); // Some false negatives on more recent compilers so lets just aim for above a reasonable threshold and 0 return code check_percentage_with_config("fork-test", 0.78f64, true, config); } #[test] fn no_test_args() { let test_dir = get_test_path("no_test_args"); let args = vec![ "tarpaulin".to_string(), "--root".to_string(), test_dir.display().to_string(), "--implicit-test-threads".to_string(), "--include-tests".to_string(), ]; check_percentage_with_cli_args(1.0, true, &args); } #[test] fn dot_rs_in_dir_name() { // issue #857 let mut config = Config::default(); config.set_clean(false); config.set_include_tests(true); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path("not_a_file.rs"); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); let (res, _ret) = launch_tarpaulin(&config, &None).unwrap(); env::set_current_dir(&restore_dir).unwrap(); assert_eq!(res.files().len(), 1); for dir in get_source_walker(&config) { assert!(dir.path().is_file()); } } #[test] #[cfg(unix)] #[cfg(not(tarpaulin))] fn kill_used_in_test() { let mut config = Config::default(); if config.engine() == TraceEngine::Llvm { println!("Tests using signals are not supported"); return; } config.follow_exec = true; config.set_clean(false); config.set_include_tests(true); // Currently 2 false negatives, but if it was only covering the integration test max coverage // is 75% so this is high enough to prove it works check_percentage_with_config("kill_proc", 0.9f64, true, config); } #[test] fn doc_test_bootstrap() { let mut config = Config::default(); config.verbose = true; config.set_clean(false); config.test_timeout = Duration::from_secs(60); let test_dir = get_test_path("doc_coverage"); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); config.run_types = vec![RunType::Doctests]; env::set_var("RUSTC_BOOTSTRAP", "1"); let (_res, ret) = launch_tarpaulin(&config, &None).unwrap(); assert_eq!(ret, 0); } #[test] #[cfg(windows)] fn sanitised_paths() { let test_dir = get_test_path("assigns"); let report_dir = test_dir.join("reports"); let mut config = Config::default(); config.set_engine(TraceEngine::Llvm); config.set_include_tests(true); config.set_clean(false); config.generate.push(OutputFile::Lcov); config.generate.push(OutputFile::Html); config.generate.push(OutputFile::Xml); config.generate.push(OutputFile::Json); let _ = fs::remove_dir_all(&report_dir); let _ = fs::create_dir(&report_dir); config.output_directory = Some(report_dir.clone()); config.test_timeout = Duration::from_secs(60); let restore_dir = env::current_dir().unwrap(); let test_dir = get_test_path("assigns"); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); env::set_current_dir(format!(r#"\\?\{}"#, config.root().display())).unwrap(); config.set_manifest(PathBuf::from(format!(r#"\\?\{}"#, config.manifest().display()))); config.set_clean(false); println!("{:#?}", config); run(&[config]).unwrap(); env::set_current_dir(restore_dir).unwrap(); println!("Look at reports"); let mut count = 0; let bad_path_regex = Regex::new(r#"\\\\\?\\\w:\\"#).unwrap(); for entry in fs::read_dir(&report_dir).unwrap() { let entry = entry.unwrap().path(); if !entry.is_dir() { count += 1; println!("Checking: {}", entry.display()); let f = fs::File::open(entry).unwrap(); if let Ok(s) = io::read_to_string(f) { assert!(!s.is_empty()); assert!(bad_path_regex.find(&s).is_none()); } else { println!("Not unicode"); } } } assert_eq!(count, 4); } #[test] fn output_dir_workspace() { let test_dir = get_test_path("workspace"); let report_dir = test_dir.join("reports"); let mut config = Config::default(); config.set_engine(TraceEngine::Llvm); config.set_include_tests(true); config.set_clean(false); config.dump_traces = true; config.generate.push(OutputFile::Lcov); config.generate.push(OutputFile::Html); config.generate.push(OutputFile::Xml); config.generate.push(OutputFile::Json); let _ = fs::remove_dir_all(&report_dir); let _ = fs::create_dir(&report_dir); config.output_directory = Some(report_dir.clone()); config.test_timeout = Duration::from_secs(60); run_config("workspace", config); let mut output = HashSet::new(); for entry in fs::read_dir(&report_dir).unwrap() { let entry = entry.unwrap().path(); if !entry.is_dir() { let file_name = entry.file_name().unwrap().to_string_lossy().to_string(); output.insert(file_name); } } assert!(output.remove("cobertura.xml")); assert!(output.remove("lcov.info")); assert!(output.remove("tarpaulin-report.html")); assert!(output.remove("tarpaulin-report.json")); assert_eq!(output.len(), 1); for event_log in &output { let events = report_dir.join(event_log); let log = fs::read(events).unwrap(); // We can deserialize event log so it must be good serde_json::from_slice::(log.as_slice()).unwrap(); } } #[test] fn stripped_crate() { let mut config = Config::default(); config.verbose = true; config.set_clean(false); config.test_timeout = Duration::from_secs(60); check_percentage_with_config("stripped", 0.9, true, config); } #[test] fn workspace_no_fail_fast() { let mut config = Config::default(); config.set_clean(false); config.set_include_tests(true); config.no_fail_fast = true; let test_dir = get_test_path("workspace_with_fail_tests"); env::set_current_dir(&test_dir).unwrap(); let mut manifest = test_dir; manifest.push("Cargo.toml"); config.set_manifest(manifest); config.packages = vec!["bar".to_string(), "foo".to_string()]; let result = launch_tarpaulin(&config, &None); let (result, ret) = result.expect("Test failed"); let files = result.files(); assert!(files.iter().any(|f| f.ends_with("foo/src/lib.rs"))); assert!(ret != 0); } #[test] fn warning_flags_in_config() { check_percentage("config_warnings", 1.0f64, true); } #[test] fn workspace_default_members() { let mut config = Config::default(); config.set_clean(false); config.set_include_tests(true); let only_default = check_percentage_with_config("default_members", 1.0f64, true, config.clone()); let files = only_default.files(); assert_eq!(files.len(), 1); assert!(files[0].ends_with(Path::new("workspace_1/src/lib.rs"))); config.all= true; let all = check_percentage_with_config("default_members", 1.0f64, true, config); let files = all.files(); assert_eq!(files.len(), 2); // We use a BTreeMap so they'll be alphabetically ordered assert!(files[0].ends_with(Path::new("workspace_1/src/lib.rs"))); assert!(files[1].ends_with(Path::new("workspace_2/src/lib.rs"))); } }