diff --git a/Cargo.lock b/Cargo.lock index 3540c89..dd8dfb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ansiterm" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab587f5395da16dd2e6939adf53dede583221b320cadfb94e02b5b7b9bf24cc" +dependencies = [ + "winapi", +] + [[package]] name = "anstream" version = "0.6.5" @@ -61,7 +70,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -71,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -119,6 +128,7 @@ dependencies = [ "path-absolutize", "path-slash", "pretty_env_logger", + "prodash", "rustc-demangle", "serde", "serde_json", @@ -236,6 +246,41 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crosstermion" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "400415b86f4dc01b9e4e129e822dad900e546287319da7ab229654978d3e07e1" +dependencies = [ + "ansiterm", + "crossterm", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -256,7 +301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -307,7 +352,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -363,7 +408,7 @@ checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -442,6 +487,16 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -454,6 +509,18 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -487,6 +554,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + [[package]] name = "path-absolutize" version = "3.1.1" @@ -542,6 +632,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prodash" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +dependencies = [ + "crosstermion", + "humantime", + "is-terminal", + "parking_lot", + "unicode-width", +] + [[package]] name = "quote" version = "1.0.35" @@ -605,7 +708,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -614,6 +717,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.195" @@ -645,6 +754,42 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + [[package]] name = "strsim" version = "0.10.0" @@ -672,7 +817,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -740,6 +885,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "url" version = "2.5.0" @@ -763,6 +914,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -857,6 +1014,15 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ff372e1..20ba59b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,15 @@ path-absolutize = "3.0.14" path-slash = "0.2.1" pretty_env_logger = "0.5.0" proc-macro2 = "1.0.47" +prodash = { version = "28.0.0", default-features = false, features = [ + "render-line", + "render-line-crossterm", + "render-line-autoconfigure", + "progress-tree", +] } quote = "1.0.21" rustc-demangle = "0.1.21" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tempfile = "3.0" thiserror = "1.0" - - diff --git a/cargo-difftests/Cargo.toml b/cargo-difftests/Cargo.toml index 8567f21..8670977 100644 --- a/cargo-difftests/Cargo.toml +++ b/cargo-difftests/Cargo.toml @@ -24,6 +24,7 @@ log.workspace = true path-absolutize.workspace = true path-slash.workspace = true pretty_env_logger.workspace = true +prodash.workspace = true rustc-demangle.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/cargo-difftests/src/bin/cargo-difftests-default-rerunner.rs b/cargo-difftests/src/bin/cargo-difftests-default-rerunner.rs index d999d7e..2961c47 100644 --- a/cargo-difftests/src/bin/cargo-difftests-default-rerunner.rs +++ b/cargo-difftests/src/bin/cargo-difftests-default-rerunner.rs @@ -1,6 +1,11 @@ #![feature(exit_status_error)] -use cargo_difftests::{cargo_difftests_test_rerunner, test_rerunner_core::TestRerunnerInvocation}; +use std::io::Read; + +use cargo_difftests::{ + cargo_difftests_test_rerunner, + test_rerunner_core::{TestRerunnerInvocation, TestRunnerInvocationTestCounts}, +}; #[derive(serde::Serialize, serde::Deserialize)] struct TestRerunnerDefaultExtra { @@ -16,10 +21,24 @@ enum Error { Json(#[from] serde_json::Error), #[error("exit status error: {0}")] ExitStatusError(#[from] std::process::ExitStatusError), + #[error("difftests error: {0}")] + DifftestsError(#[from] cargo_difftests::DifftestsError), +} + +struct FailGuard<'invocation>(TestRunnerInvocationTestCounts<'invocation>); + +impl<'invocation> Drop for FailGuard<'invocation> { + fn drop(&mut self) { + self.0.fail_if_running().unwrap(); + } } fn rerunner(invocation: TestRerunnerInvocation) -> Result<(), Error> { - let profile = std::env::var("CARGO_DIFFTESTS_PROFILE").unwrap_or_else(|_| "difftests".to_owned()); + let mut counts = FailGuard(invocation.test_counts()); + counts.0.initialize_test_counts(invocation.tests().len())?; + + let profile = + std::env::var("CARGO_DIFFTESTS_PROFILE").unwrap_or_else(|_| "difftests".to_owned()); let extra_cargo_args = std::env::var("CARGO_DIFFTESTS_EXTRA_CARGO_ARGS"); let extra_cargo_args = extra_cargo_args @@ -28,14 +47,46 @@ fn rerunner(invocation: TestRerunnerInvocation) -> Result<(), Error> { .unwrap_or_default(); for test in invocation.tests() { let e = test.parse_extra::()?; - std::process::Command::new("cargo") - .args(&["test", "-p", &e.pkg_name, &e.test_name, "--profile", &profile]) + let mut child = std::process::Command::new("cargo") + .args(&[ + "test", + "-p", + &e.pkg_name, + &e.test_name, + "--profile", + &profile, + ]) .args(&extra_cargo_args) .args(&["--", "--exact"]) - .status()? - .exit_ok()?; + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn()?; + + let mut child_stdout = child.stdout.take().unwrap(); + let mut child_stderr = child.stderr.take().unwrap(); + + let r = child.wait()?; + + if r.success() { + counts.0.inc()?; + } else { + counts.0.fail_if_running()?; + + let mut stdout = String::new(); + let mut stderr = String::new(); + + child_stdout.read_to_string(&mut stdout)?; + child_stderr.read_to_string(&mut stderr)?; + + print!("{stdout}"); + eprintln!("{stderr}"); + + std::process::exit(1); + } } + counts.0.test_count_done()?; + Ok(()) } diff --git a/cargo-difftests/src/main.rs b/cargo-difftests/src/main.rs index 0b47e95..5608c3f 100644 --- a/cargo-difftests/src/main.rs +++ b/cargo-difftests/src/main.rs @@ -19,8 +19,9 @@ use core::fmt; use std::fmt::{Display, Formatter}; use std::fs; -use std::io::Write; +use std::io::{BufRead, Write}; use std::path::{Path, PathBuf}; +use std::sync::Arc; use anyhow::{bail, Context}; use cargo_difftests::analysis::{ @@ -29,16 +30,22 @@ use cargo_difftests::analysis::{ use cargo_difftests::difftest::{Difftest, DiscoverIndexPathResolver, ExportProfdataConfig}; use cargo_difftests::group_difftest::GroupDifftestGroup; use cargo_difftests::index_data::{IndexDataCompilerConfig, IndexSize, TestIndex}; +use cargo_difftests::test_rerunner_core::State as TestRunnerState; use cargo_difftests::{ AnalysisVerdict, AnalyzeAllSingleTestGroup, IndexCompareDifferences, TouchSameFilesDifference, }; use clap::{Args, Parser, ValueEnum}; -use log::warn; +use log::{info, warn}; +use prodash::render::line; +use prodash::tree::Root as Tree; +use prodash::unit; #[derive(Args, Debug)] pub struct ExportProfdataCommand { #[clap(flatten)] export_profdata_config_flags: ExportProfdataConfigFlags, + #[clap(flatten)] + ignore_registry_files: IgnoreRegistryFilesFlag, } #[derive(ValueEnum, Debug, Copy, Clone)] @@ -60,8 +67,6 @@ impl Display for FlattenFilesTarget { #[derive(Args, Debug, Copy, Clone)] pub struct CompileTestIndexFlags { - #[clap(flatten)] - ignore_cargo_registry: IgnoreRegistryFilesFlag, /// Whether to flatten all files to a directory. #[clap(long)] flatten_files_to: Option, @@ -73,7 +78,7 @@ pub struct CompileTestIndexFlags { #[clap( long = "no-remove-bin-path", default_value_t = true, - action(clap::ArgAction::SetFalse) + action = clap::ArgAction::SetFalse, )] remove_bin_path: bool, /// Whether to generate a full index, or a tiny index. @@ -96,7 +101,7 @@ pub struct CompileTestIndexFlags { #[clap( long = "no-path-slash-replace", default_value_t = true, - action(clap::ArgAction::SetFalse) + action = clap::ArgAction::SetFalse, )] path_slash_replace: bool, } @@ -104,9 +109,6 @@ pub struct CompileTestIndexFlags { impl Default for CompileTestIndexFlags { fn default() -> Self { Self { - ignore_cargo_registry: IgnoreRegistryFilesFlag { - ignore_registry_files: true, - }, flatten_files_to: Some(FlattenFilesTarget::RepoRoot), remove_bin_path: true, full_index: false, @@ -206,6 +208,8 @@ pub enum LowLevelCommand { export_profdata_config_flags: ExportProfdataConfigFlags, #[clap(flatten)] compile_test_index_flags: CompileTestIndexFlags, + #[clap(flatten)] + ignore_registry_files: IgnoreRegistryFilesFlag, }, /// Runs the analysis for a single test index. RunAnalysisWithTestIndex { @@ -431,23 +435,21 @@ pub struct IgnoreRegistryFilesFlag { #[clap( long = "no-ignore-registry-files", default_value_t = true, - action(clap::ArgAction::SetFalse) + action = clap::ArgAction::SetFalse, )] ignore_registry_files: bool, } #[derive(Args, Debug, Clone)] pub struct ExportProfdataConfigFlags { - #[clap(flatten)] - ignore_registry_files: IgnoreRegistryFilesFlag, #[clap(flatten)] other_binaries: OtherBinaries, } impl ExportProfdataConfigFlags { - fn config(&self) -> ExportProfdataConfig { + fn config(&self, ignore_registry_files: IgnoreRegistryFilesFlag) -> ExportProfdataConfig { ExportProfdataConfig { - ignore_registry_files: self.ignore_registry_files.ignore_registry_files, + ignore_registry_files: ignore_registry_files.ignore_registry_files, other_binaries: self.other_binaries.other_binaries.clone(), } } @@ -496,7 +498,11 @@ pub struct AnalyzeAllActionArgs { } impl AnalyzeAllActionArgs { - fn perform_for(&self, results: &[AnalyzeAllSingleTestGroup]) -> CargoDifftestsResult { + fn perform_for( + &self, + ctxt: &CargoDifftestsContext, + results: &[AnalyzeAllSingleTestGroup], + ) -> CargoDifftestsResult { match self.action { AnalyzeAllActionKind::Print => { let out_json = serde_json::to_string(&results)?; @@ -514,13 +520,16 @@ impl AnalyzeAllActionArgs { cargo_difftests::test_rerunner_core::TestRerunnerInvocation::create_invocation_from( results .iter() - .filter(|r| r.verdict == AnalysisVerdict::Dirty) + .filter(|r| r.verdict == AnalysisVerdict::Dirty), )?; if invocation.is_empty() { return Ok(()); } + let mut pb = ctxt.shell.tree.add_child("Rerunning dirty tests"); + pb.init(Some(1), Some(unit::label("test sets"))); + let invocation_str = serde_json::to_string(&invocation)?; let mut invocation_file = tempfile::NamedTempFile::new()?; @@ -528,14 +537,79 @@ impl AnalyzeAllActionArgs { invocation_file.flush()?; let mut cmd = std::process::Command::new(&self.runner.runner); - cmd.arg(invocation_file.path()).env( - cargo_difftests::test_rerunner_core::CARGO_DIFFTESTS_VER_NAME, - env!("CARGO_PKG_VERSION"), - ); + cmd.arg(invocation_file.path()) + .env( + cargo_difftests::test_rerunner_core::CARGO_DIFFTESTS_VER_NAME, + env!("CARGO_PKG_VERSION"), + ) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + let mut child = cmd.spawn()?; + + let mut stdout_child = child.stdout.take().unwrap(); + let mut stderr_child = child.stderr.take().unwrap(); + + let tests = pb.add_child("Tests"); + let handle = std::thread::spawn(move || { + let mut tests = tests; + let mut tests_initialized = false; + for line in std::io::BufReader::new(&mut stdout_child).lines() { + let line = line?; + if line.starts_with("cargo-difftests-test-counts::") { + let l = line.trim_start_matches("cargo-difftests-test-counts::"); + let counts: TestRunnerState = serde_json::from_str(l)?; + match counts { + TestRunnerState::None => {} + TestRunnerState::Running { + current_test_count, + total_test_count, + } => { + if !tests_initialized { + tests.init( + Some(total_test_count), + Some(unit::label("tests")), + ); + tests_initialized = true; + } + + tests.set(current_test_count); + } + TestRunnerState::Done => { + tests.done("Tests are done"); + } + TestRunnerState::Error => { + tests.fail("Tests failed"); + } + } + } else { + info!("rerun stdout: {line}"); + } + } + + Ok::<_, anyhow::Error>(()) + }); - let status = cmd.status()?; + let status = child.wait()?; - status.exit_ok()?; + handle.join().unwrap()?; + + for line in std::io::BufReader::new(&mut stderr_child).lines() { + let line = line?; + info!("rerun stderr: {line}"); + } + + pb.inc(); + + match status.exit_ok() { + Ok(()) => { + pb.done("Rerun successful"); + } + Err(e) => { + pb.fail("Rerun failed"); + bail!(e); + } + } } } Ok(()) @@ -586,6 +660,9 @@ pub enum App { /// `if-available`. #[clap(long, default_value = "target/tmp/cargo-difftests")] root: Option, + + #[clap(flatten)] + ignore_registry_files: IgnoreRegistryFilesFlag, }, /// Treats all the difftests found in the given directory as a single /// group, and analyzes them together. @@ -613,6 +690,9 @@ pub enum App { /// `if-available`. #[clap(long, default_value = "target/tmp/cargo-difftests")] root: Option, + + #[clap(flatten)] + ignore_registry_files: IgnoreRegistryFilesFlag, }, /// Analyze all the difftests in a given directory. /// @@ -625,6 +705,8 @@ pub enum App { /// the paths passed to `cargo_difftests_testclient::init`. #[clap(long, default_value = "target/tmp/cargo-difftests")] dir: PathBuf, + #[clap(flatten)] + ignore_registry_files: IgnoreRegistryFilesFlag, /// Whether to force the generation of intermediary files. /// /// Without this flag, if the intermediary files are already present, @@ -712,6 +794,7 @@ fn discover_difftests( } fn run_discover_difftests( + ctxt: &CargoDifftestsContext, dir: PathBuf, index_root: Option, ignore_incompatible: bool, @@ -740,7 +823,10 @@ fn run_export_profdata(dir: PathBuf, cmd: ExportProfdataCommand) -> CargoDifftes bail!("difftest directory does not have a .profdata file"); } - let coverage = discovered.export_profdata(cmd.export_profdata_config_flags.config())?; + let coverage = discovered.export_profdata( + cmd.export_profdata_config_flags + .config(cmd.ignore_registry_files), + )?; let s = serde_json::to_string(&coverage)?; @@ -805,6 +891,7 @@ fn run_analysis_with_test_index( fn compile_test_index_config( compile_test_index_flags: CompileTestIndexFlags, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let flatten_root = match compile_test_index_flags.flatten_files_to { Some(FlattenFilesTarget::RepoRoot) => { @@ -838,11 +925,7 @@ fn compile_test_index_config( p }), accept_file: Box::new(move |path| { - if compile_test_index_flags - .ignore_cargo_registry - .ignore_registry_files - && file_is_from_cargo_registry(path) - { + if ignore_registry_files.ignore_registry_files && file_is_from_cargo_registry(path) { return false; } @@ -864,14 +947,17 @@ fn run_compile_test_index( output: PathBuf, export_profdata_config_flags: ExportProfdataConfigFlags, compile_test_index_flags: CompileTestIndexFlags, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let discovered = Difftest::discover_from(dir, None)?; assert!(discovered.has_profdata()); - let config = compile_test_index_config(compile_test_index_flags)?; + let config = compile_test_index_config(compile_test_index_flags, ignore_registry_files)?; - let result = - discovered.compile_test_index_data(export_profdata_config_flags.config(), config)?; + let result = discovered.compile_test_index_data( + export_profdata_config_flags.config(ignore_registry_files), + config, + )?; result.write_to_file(&output)?; @@ -893,7 +979,7 @@ fn run_indexes_touch_same_files_report( Ok(()) } -fn run_low_level_cmd(cmd: LowLevelCommand) -> CargoDifftestsResult { +fn run_low_level_cmd(ctxt: &CargoDifftestsContext, cmd: LowLevelCommand) -> CargoDifftestsResult { match cmd { LowLevelCommand::MergeProfdata { dir, force } => { run_merge_profdata(dir.dir, force)?; @@ -912,12 +998,14 @@ fn run_low_level_cmd(cmd: LowLevelCommand) -> CargoDifftestsResult { output, export_profdata_config_flags, compile_test_index_flags, + ignore_registry_files, } => { run_compile_test_index( dir.dir, output, export_profdata_config_flags, compile_test_index_flags, + ignore_registry_files, )?; } LowLevelCommand::RunAnalysisWithTestIndex { @@ -946,12 +1034,13 @@ fn analyze_single_test( export_profdata_config_flags: ExportProfdataConfigFlags, analysis_index: &AnalysisIndex, resolver: Option<&DiscoverIndexPathResolver>, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let mut analysis_cx = match analysis_index.index_strategy { AnalysisIndexStrategy::Never => { difftest.merge_profraw_files_into_profdata(force)?; - difftest.start_analysis(export_profdata_config_flags.config())? + difftest.start_analysis(export_profdata_config_flags.config(ignore_registry_files))? } AnalysisIndexStrategy::Always => { 'l: { @@ -962,11 +1051,15 @@ fn analyze_single_test( difftest.merge_profraw_files_into_profdata(force)?; - let config = - compile_test_index_config(analysis_index.compile_test_index_flags.clone())?; + let config = compile_test_index_config( + analysis_index.compile_test_index_flags.clone(), + ignore_registry_files, + )?; - let test_index_data = difftest - .compile_test_index_data(export_profdata_config_flags.config(), config)?; + let test_index_data = difftest.compile_test_index_data( + export_profdata_config_flags.config(ignore_registry_files), + config, + )?; if let Some(p) = resolver.and_then(|r| r.resolve(difftest.dir())) { let parent = p.parent().unwrap(); @@ -988,11 +1081,15 @@ fn analyze_single_test( difftest.merge_profraw_files_into_profdata(force)?; - let config = - compile_test_index_config(analysis_index.compile_test_index_flags.clone())?; + let config = compile_test_index_config( + analysis_index.compile_test_index_flags.clone(), + ignore_registry_files, + )?; - let test_index_data = difftest - .compile_test_index_data(export_profdata_config_flags.config(), config)?; + let test_index_data = difftest.compile_test_index_data( + export_profdata_config_flags.config(ignore_registry_files), + config, + )?; if let Some(p) = resolver.and_then(|r| r.resolve(difftest.dir())) { let parent = p.parent().unwrap(); @@ -1016,7 +1113,8 @@ fn analyze_single_test( difftest.merge_profraw_files_into_profdata(force)?; - difftest.start_analysis(export_profdata_config_flags.config())? + difftest + .start_analysis(export_profdata_config_flags.config(ignore_registry_files))? } } }; @@ -1032,12 +1130,14 @@ fn analyze_single_test( } fn analyze_single_group( + ctxt: &CargoDifftestsContext, group: &mut GroupDifftestGroup, force: bool, algo: DirtyAlgorithm, commit: Option, analysis_index: &AnalysisIndex, resolver: Option<&DiscoverIndexPathResolver>, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let mut analysis_cx = match analysis_index.index_strategy { AnalysisIndexStrategy::Never => { @@ -1054,8 +1154,10 @@ fn analyze_single_group( group.merge_profraws(force)?; - let config = - compile_test_index_config(analysis_index.compile_test_index_flags.clone())?; + let config = compile_test_index_config( + analysis_index.compile_test_index_flags.clone(), + ignore_registry_files, + )?; let test_index_data = group.compile_test_index_data(config)?; @@ -1079,8 +1181,10 @@ fn analyze_single_group( group.merge_profraws(force)?; - let config = - compile_test_index_config(analysis_index.compile_test_index_flags.clone())?; + let config = compile_test_index_config( + analysis_index.compile_test_index_flags.clone(), + ignore_registry_files, + )?; let test_index_data = group.compile_test_index_data(config)?; @@ -1122,6 +1226,7 @@ fn analyze_single_group( } fn run_analyze( + ctxt: &CargoDifftestsContext, dir: PathBuf, force: bool, algo: DirtyAlgorithm, @@ -1129,6 +1234,7 @@ fn run_analyze( export_profdata_config_flags: ExportProfdataConfigFlags, root: Option, analysis_index: AnalysisIndex, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let resolver = analysis_index.index_resolver(root)?; @@ -1142,6 +1248,7 @@ fn run_analyze( export_profdata_config_flags, &analysis_index, resolver.as_ref(), + ignore_registry_files, )?; display_analysis_result(r); @@ -1150,6 +1257,7 @@ fn run_analyze( } pub fn run_analyze_all( + ctxt: &CargoDifftestsContext, dir: PathBuf, force: bool, algo: DirtyAlgorithm, @@ -1158,6 +1266,7 @@ pub fn run_analyze_all( analysis_index: AnalysisIndex, ignore_incompatible: bool, action_args: AnalyzeAllActionArgs, + ignore_registry_files: IgnoreRegistryFilesFlag, ) -> CargoDifftestsResult { let resolver = analysis_index.index_resolver(Some(dir.clone()))?; let discovered = @@ -1165,7 +1274,10 @@ pub fn run_analyze_all( let mut results = vec![]; - for mut difftest in discovered { + let mut pb = ctxt.shell.tree.add_child("Analyzing tests"); + pb.init(Some(discovered.len()), Some(unit::label("difftests"))); + + for mut difftest in discovered.into_iter() { let r = analyze_single_test( &mut difftest, force, @@ -1174,6 +1286,7 @@ pub fn run_analyze_all( export_profdata_config_flags.clone(), &analysis_index, resolver.as_ref(), + ignore_registry_files, )?; let result = AnalyzeAllSingleTestGroup { @@ -1184,9 +1297,13 @@ pub fn run_analyze_all( }; results.push(result); + + pb.inc(); } - action_args.perform_for(&results)?; + pb.done("done"); + + action_args.perform_for(ctxt, &results)?; Ok(()) } @@ -1211,6 +1328,7 @@ fn discover_indexes_to_vec( } pub fn run_analyze_all_from_index( + ctxt: &CargoDifftestsContext, index_root: PathBuf, algo: DirtyAlgorithm, commit: Option, @@ -1246,13 +1364,38 @@ pub fn run_analyze_all_from_index( results.push(result); } - action_args.perform_for(&results)?; + action_args.perform_for(ctxt, &results)?; Ok(()) } +struct Shell { + tree: Arc, +} + +pub struct CargoDifftestsContext { + shell: Shell, +} + fn main_impl() -> CargoDifftestsResult { - pretty_env_logger::init_custom_env("CARGO_DIFFTESTS_LOG"); + pretty_env_logger::formatted_builder() + .parse_env("CARGO_DIFFTESTS_LOG") + .init(); + + let ctxt = CargoDifftestsContext { + shell: Shell { tree: Tree::new() }, + }; + + let handle = line::render( + std::io::stderr(), + Arc::downgrade(&ctxt.shell.tree), + line::Options { + frames_per_second: 20.0, + ..Default::default() + } + .auto_configure(line::StreamKind::Stderr), + ); + let CargoApp::Difftests { app } = CargoApp::parse(); match app { @@ -1261,7 +1404,7 @@ fn main_impl() -> CargoDifftestsResult { index_root, ignore_incompatible, } => { - run_discover_difftests(dir, index_root, ignore_incompatible)?; + run_discover_difftests(&ctxt, dir, index_root, ignore_incompatible)?; } App::Analyze { dir, @@ -1270,8 +1413,10 @@ fn main_impl() -> CargoDifftestsResult { algo: AlgoArgs { algo, commit }, export_profdata_config_flags, analysis_index, + ignore_registry_files, } => { run_analyze( + &ctxt, dir.dir, force, algo, @@ -1279,6 +1424,7 @@ fn main_impl() -> CargoDifftestsResult { export_profdata_config_flags, root, analysis_index, + ignore_registry_files, )?; } App::AnalyzeGroup { @@ -1288,6 +1434,7 @@ fn main_impl() -> CargoDifftestsResult { other_binaries, analysis_index, root, + ignore_registry_files, } => { let resolver = analysis_index.index_resolver(root)?; let mut group = cargo_difftests::group_difftest::index_group( @@ -1297,12 +1444,14 @@ fn main_impl() -> CargoDifftestsResult { )?; let r = analyze_single_group( + &ctxt, &mut group, force, algo.algo, algo.commit, &analysis_index, resolver.as_ref(), + ignore_registry_files, )?; display_analysis_result(r); @@ -1311,12 +1460,14 @@ fn main_impl() -> CargoDifftestsResult { dir, force, algo: AlgoArgs { algo, commit }, + ignore_registry_files, export_profdata_config_flags, analysis_index, ignore_incompatible, action_args, } => { run_analyze_all( + &ctxt, dir, force, algo, @@ -1325,6 +1476,7 @@ fn main_impl() -> CargoDifftestsResult { analysis_index, ignore_incompatible, action_args, + ignore_registry_files, )?; } App::AnalyzeAllFromIndex { @@ -1332,13 +1484,15 @@ fn main_impl() -> CargoDifftestsResult { algo: AlgoArgs { algo, commit }, action_args, } => { - run_analyze_all_from_index(index_root, algo, commit, action_args)?; + run_analyze_all_from_index(&ctxt, index_root, algo, commit, action_args)?; } App::LowLevel { cmd } => { - run_low_level_cmd(cmd)?; + run_low_level_cmd(&ctxt, cmd)?; } } + handle.shutdown_and_wait(); + Ok(()) } diff --git a/cargo-difftests/src/test_rerunner_core.rs b/cargo-difftests/src/test_rerunner_core.rs index c0f4407..715b24a 100644 --- a/cargo-difftests/src/test_rerunner_core.rs +++ b/cargo-difftests/src/test_rerunner_core.rs @@ -1,7 +1,106 @@ +use core::panic; +use std::marker::PhantomData; + use cargo_difftests_core::{CoreGroupDesc, CoreTestDesc}; use crate::{AnalyzeAllSingleTestGroup, DifftestsResult}; +#[derive(serde::Serialize, serde::Deserialize)] +pub enum State { + None, + Running { + current_test_count: usize, + total_test_count: usize, + }, + Done, + Error, +} + +pub struct TestRunnerInvocationTestCounts<'invocation> { + state: State, + _pd: PhantomData<&'invocation ()>, +} + +impl<'invocation> Drop for TestRunnerInvocationTestCounts<'invocation> { + fn drop(&mut self) { + self.test_count_done().unwrap(); + } +} + +impl<'invocation> TestRunnerInvocationTestCounts<'invocation> { + pub fn initialize_test_counts(&mut self, total_tests_to_run: usize) -> DifftestsResult<()> { + match self.state { + State::None => { + self.state = State::Running { + current_test_count: 0, + total_test_count: total_tests_to_run, + }; + + self.write_test_counts()?; + + Ok(()) + } + _ => panic!("test counts already initialized"), + } + } + + pub fn inc(&mut self) -> DifftestsResult<()> { + match &mut self.state { + State::None => { + panic!("test counts not initialized"); + } + State::Running { + current_test_count, + total_test_count, + } => { + *current_test_count += 1; + assert!(*current_test_count <= *total_test_count); + self.write_test_counts()?; + } + State::Done | State::Error => { + panic!("test counts already done"); + } + } + + self.write_test_counts()?; + + Ok(()) + } + + pub fn test_count_done(&mut self) -> DifftestsResult { + match self.state { + State::Done => {} + State::Running { .. } => { + self.state = State::Done; + self.write_test_counts()?; + } + _ => panic!("test counts not initialized"), + } + + Ok(()) + } + + pub fn fail_if_running(&mut self) -> DifftestsResult { + match self.state { + State::Running { .. } => { + self.state = State::Error; + self.write_test_counts()?; + } + _ => {} + } + + Ok(()) + } + + fn write_test_counts(&self) -> DifftestsResult { + println!( + "cargo-difftests-test-counts::{}", + serde_json::to_string(&self.state)? + ); + Ok(()) + } +} + #[derive(serde::Serialize, serde::Deserialize)] pub struct TestRerunnerInvocation { tests: Vec, @@ -23,7 +122,9 @@ impl TestRerunnerInvocation { } else { // Most likely came from an index. assert_eq!(g.test_desc.len(), 1); - let [test] = g.test_desc.as_slice() else { unreachable!() }; + let [test] = g.test_desc.as_slice() else { + unreachable!() + }; tests.push(test.clone()); } } @@ -42,11 +143,18 @@ impl TestRerunnerInvocation { pub fn groups(&self) -> &[CoreGroupDesc] { &self.groups } + + pub fn test_counts(&self) -> TestRunnerInvocationTestCounts { + TestRunnerInvocationTestCounts { + state: State::None, + _pd: PhantomData, + } + } } pub const CARGO_DIFFTESTS_VER_NAME: &str = "CARGO_DIFFTESTS_VER"; -pub fn read_invocation_from_command_line() -> std::io::Result { +pub fn read_invocation_from_command_line() -> DifftestsResult { let v = std::env::var(CARGO_DIFFTESTS_VER_NAME).map_err(|e| { std::io::Error::new( std::io::ErrorKind::InvalidInput, @@ -62,7 +170,8 @@ pub fn read_invocation_from_command_line() -> std::io::Result std::io::Result { - fn main() -> std::io::Result { + fn main() -> $crate::DifftestsResult { let invocation = $crate::test_rerunner_core::read_invocation_from_command_line()?; let result = $impl_fn(invocation); diff --git a/sample/cargo-difftests-sample-project/tests/tests.rs b/sample/cargo-difftests-sample-project/tests/tests.rs index 2fe6a83..ec2facc 100644 --- a/sample/cargo-difftests-sample-project/tests/tests.rs +++ b/sample/cargo-difftests-sample-project/tests/tests.rs @@ -87,9 +87,13 @@ pub fn setup_difftests_group(group_name: &'static str) -> DifftestsEnv { } } +#[cfg_attr(cargo_difftests, inline(never))] +fn guard() {} + #[test] fn test_add() { let _env = setup_difftests("test_add"); + let _g = guard(); std::thread::sleep(std::time::Duration::from_millis(400)); assert_eq!(add(1, 2), 3); } @@ -97,13 +101,15 @@ fn test_add() { #[test] fn test_sub() { let _env = setup_difftests("test_sub"); + let _g = guard(); std::thread::sleep(std::time::Duration::from_millis(100)); - assert_eq!(sub(3, 2), 1); + assert_eq!(sub(3, 2), 2); } #[test] fn test_mul() { let _env = setup_difftests_group("advanced"); + let _g = guard(); std::thread::sleep(std::time::Duration::from_millis(1000)); assert_eq!(mul(2, 3), 6); } @@ -111,6 +117,7 @@ fn test_mul() { #[test] fn test_div() { let _env = setup_difftests_group("advanced"); + let _g = guard(); std::thread::sleep(std::time::Duration::from_millis(1000)); assert_eq!(div(6, 3), Some(2)); } @@ -118,6 +125,7 @@ fn test_div() { #[test] fn test_div_2() { let _env = setup_difftests_group("advanced"); + let _g = guard(); std::thread::sleep(std::time::Duration::from_millis(1000)); assert_eq!(div(6, 0), None); }