Repository: sourcefrog/cargo-mutants (rust)

Issue:
Show number of mutants processed
So I've been running mutants on my code base all day, and while showing only the failing mutants and current one does keep the output nice and terse it does mean I have no idea how many more are left to run before it's finished. Something like `N/total` on the in-progress line would helpful for gauging if it's worth waiting for it to finish or whether I should consider killing it and adding some ignores in the codebase :sweat_smile: 

Pull request description:
Show [i/n] indicator in progress bar
Fixes #21 

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9452f2c..bba29ce 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -94,7 +94,7 @@ dependencies = [
 
 [[package]]
 name = "cargo-mutants"
-version = "0.2.1"
+version = "0.2.2-pre"
 dependencies = [
  "anyhow",
  "argh",
diff --git a/Cargo.toml b/Cargo.toml
index 3bf4746..bf84e6f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "cargo-mutants"
-version = "0.2.1"
+version = "0.2.2-pre"
 edition = "2018"
 authors = ["Martin Pool"]
 license = "MIT"
diff --git a/NEWS.md b/NEWS.md
index 0e6f6c8..47a4be4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,9 @@
 # cargo-mutants changelog
 
+## unreleased
+
+- In the progress bar, show which mutant is being tested out of how many total.
+
 ## 0.2.1
 
 Released 2022-02-10
diff --git a/src/console.rs b/src/console.rs
index a78400e..7137b36 100644
--- a/src/console.rs
+++ b/src/console.rs
@@ -27,9 +27,20 @@ impl Console {
         }
     }
 
-    /// Create an Activity for a new mutation.
-    pub fn start_mutation(&self, mutation: &Mutation) -> Activity {
-        self.start_activity(&style_mutation(mutation))
+    pub fn start_scenario(&self, scenario: &Scenario) -> Activity {
+        match scenario {
+            Scenario::SourceTree => self.start_activity("source tree"),
+            Scenario::Baseline => self.start_activity("unmutated baseline"),
+            Scenario::Mutant {
+                mutation,
+                i_mutation,
+                n_mutations,
+            } => {
+                let mut activity = self.start_activity(&style_mutation(mutation));
+                activity.overall_progress = Some((i_mutation + 1, *n_mutations));
+                activity
+            }
+        }
     }
 
     /// Start a general-purpose activity.
@@ -46,6 +57,7 @@ impl Console {
             progress_bar,
             start_time: Instant::now(),
             console: self,
+            overall_progress: None,
         }
     }
 
@@ -60,12 +72,18 @@ pub struct Activity<'c> {
     progress_bar: ProgressBar,
     task: String,
     console: &'c Console,
+    /// Optionally, progress counter through the overall lab. Shown in the progress bar
+    /// but not on permanent output.
+    overall_progress: Option<(usize, usize)>,
 }
 
 impl<'c> Activity<'c> {
     pub fn set_phase(&mut self, phase: &'static str) {
+        let overall_text = self
+            .overall_progress
+            .map_or(String::new(), |(a, b)| format!("[{}/{}] ", a, b));
         self.progress_bar
-            .set_message(format!("{} ({})", self.task, phase));
+            .set_message(format!("{}{} ({})", overall_text, self.task, phase));
     }
 
     /// Mark this activity as interrupted.
@@ -170,7 +188,7 @@ pub fn style_outcome(outcome: &Outcome) -> StyledObject<&'static str> {
             Failure => style("FAILED").red().bold(),
             Timeout => style("TIMEOUT").red().bold(),
         },
-        Mutant(_mutant) => match (outcome.last_phase(), outcome.last_phase_result()) {
+        Mutant { .. } => match (outcome.last_phase(), outcome.last_phase_result()) {
             (Phase::Test, Failure) => style("caught").green(),
             (Phase::Test, Success) => style("NOT CAUGHT").red().bold(),
             (Phase::Build, Success) => style("build ok").green(),
diff --git a/src/lab.rs b/src/lab.rs
index 0832938..04c5011 100644
--- a/src/lab.rs
+++ b/src/lab.rs
@@ -30,7 +30,13 @@ pub enum Scenario {
     /// Build in a copy of the source tree but with no mutations applied.
     Baseline,
     /// Build with a mutant applied.
-    Mutant(Mutation),
+    Mutant {
+        mutation: Mutation,
+        /// Index of the mutation being applied, to calculate progress.
+        i_mutation: usize,
+        /// Total number of mutations.
+        n_mutations: usize,
+    },
 }
 
 impl fmt::Display for Scenario {
@@ -38,14 +44,14 @@ impl fmt::Display for Scenario {
         match self {
             Scenario::SourceTree => f.write_str("source tree"),
             Scenario::Baseline => f.write_str("baseline"),
-            Scenario::Mutant(mutant) => mutant.fmt(f),
+            Scenario::Mutant { mutation, .. } => mutation.fmt(f),
         }
     }
 }
 
 impl Scenario {
     pub fn is_mutant(&self) -> bool {
-        matches!(self, Scenario::Mutant(_))
+        matches!(self, Scenario::Mutant { .. })
     }
 }
 
@@ -113,9 +119,14 @@ pub fn test_unmutated_then_all_mutants(
         }
     );
 
-    for mutation in mutations {
+    let n_mutations = mutations.len();
+    for (i_mutation, mutation) in mutations.into_iter().enumerate() {
         lab_outcome.add(&test_mutation(
-            &mutation,
+            &Scenario::Mutant {
+                mutation,
+                i_mutation,
+                n_mutations,
+            },
             build_dir.path(),
             &output_dir,
             &options,
@@ -142,25 +153,19 @@ fn run_cargo_phases(
     build_dir: &Path,
     output_dir: &OutputDir,
     options: &Options,
-    scenario: Scenario,
+    scenario: &Scenario,
     phases: &[Phase],
     console: &Console,
 ) -> Result<Outcome> {
     let scenario_name = scenario.to_string();
     let mut log_file = output_dir.create_log(&scenario_name)?;
     log_file.message(&scenario_name);
-    if let Scenario::Mutant(mutant) = &scenario {
-        log_file.message(&mutant.diff());
+    if let Scenario::Mutant { mutation, .. } = scenario {
+        log_file.message(&mutation.diff());
     }
-    let mut activity = match &scenario {
-        Scenario::SourceTree => {
-            console.start_activity(&format!("{} source tree", phases.last().unwrap()))
-        }
-        Scenario::Baseline => console.start_activity("unmutated baseline"),
-        Scenario::Mutant(mutant) => console.start_mutation(mutant),
-    };
+    let mut activity = console.start_scenario(scenario);
 
-    let mut outcome = Outcome::new(&log_file, scenario);
+    let mut outcome = Outcome::new(&log_file, scenario.clone());
     for &phase in phases {
         let phase_start = Instant::now();
         activity.set_phase(phase.name());
@@ -245,7 +250,7 @@ fn check_and_build_source_tree(
         source_tree.root(),
         output_dir,
         options,
-        Scenario::SourceTree,
+        &Scenario::SourceTree,
         phases,
         console,
     )
@@ -265,7 +270,7 @@ fn test_baseline(
         build_dir,
         output_dir,
         options,
-        Scenario::Baseline,
+        &Scenario::Baseline,
         Phase::ALL,
         console,
     )
@@ -273,20 +278,24 @@ fn test_baseline(
 
 /// Test with one mutation applied.
 fn test_mutation(
-    mutation: &Mutation,
+    scenario: &Scenario,
     build_dir: &Path,
     output_dir: &OutputDir,
     options: &Options,
     console: &Console,
 ) -> Result<Outcome> {
-    mutation.with_mutation_applied(build_dir, || {
-        run_cargo_phases(
-            build_dir,
-            output_dir,
-            options,
-            Scenario::Mutant(mutation.clone()),
-            Phase::ALL,
-            console,
-        )
-    })
+    if let Scenario::Mutant { mutation, .. } = scenario {
+        mutation.with_mutation_applied(build_dir, || {
+            run_cargo_phases(
+                build_dir,
+                output_dir,
+                options,
+                scenario,
+                Phase::ALL,
+                console,
+            )
+        })
+    } else {
+        unreachable!()
+    }
 }


Test patch:
diff --git a/tests/cli.rs b/tests/cli.rs
index 2f90c9f..ed44892 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -301,7 +301,7 @@ fn factorial_mutants_with_all_logs() {
         .assert()
         .code(2)
         .stderr("")
-        .stdout(is_match(r"build source tree \.\.\. ok in \d+\.\d\d\ds").unwrap())
+        .stdout(is_match(r"source tree \.\.\. ok in \d+\.\d\d\ds").unwrap())
         .stdout(is_match(
 r"copy source and build products to scratch directory \.\.\. \d+ MB in \d+\.\d\d\ds"
         ).unwrap())
@@ -435,7 +435,7 @@ fn source_tree_build_fails() {
         .env_remove("RUST_BACKTRACE")
         .assert()
         .failure() // TODO: This should be a distinct error code
-        .stdout(is_match(r"build source tree \.\.\. FAILED in \d+\.\d{3}s").unwrap())
+        .stdout(is_match(r"source tree \.\.\. FAILED in \d+\.\d{3}s").unwrap())
         .stdout(contains(r"This isn't Rust").name("The problem source line"))
         .stdout(contains("*** source tree"))
         .stdout(contains("check --tests")) // Caught at the check phase
diff --git a/tests/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.snap b/tests/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.snap
index c065b95..d73a86c 100644
--- a/tests/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.snap
+++ b/tests/snapshots/cli__check_succeeds_in_tree_that_builds_but_fails_tests.snap
@@ -1,10 +1,10 @@
 ---
 source: tests/cli.rs
-assertion_line: 332
+assertion_line: 349
 expression: stdout
 
 ---
-check source tree ... ok
+source tree ... ok
 copy source and build products to scratch directory ... done
 unmutated baseline ... ok
 found 1 mutation to test
diff --git a/tests/snapshots/cli__check_tree_with_mutants_skip.snap b/tests/snapshots/cli__check_tree_with_mutants_skip.snap
index 7b7346e..fa434d2 100644
--- a/tests/snapshots/cli__check_tree_with_mutants_skip.snap
+++ b/tests/snapshots/cli__check_tree_with_mutants_skip.snap
@@ -1,10 +1,10 @@
 ---
 source: tests/cli.rs
-assertion_line: 349
+assertion_line: 366
 expression: stdout
 
 ---
-check source tree ... ok
+source tree ... ok
 copy source and build products to scratch directory ... done
 unmutated baseline ... ok
 found 1 mutation to test
diff --git a/tests/snapshots/cli__uncaught_mutant_in_factorial.snap b/tests/snapshots/cli__uncaught_mutant_in_factorial.snap
index 6dc2f52..26e6549 100644
--- a/tests/snapshots/cli__uncaught_mutant_in_factorial.snap
+++ b/tests/snapshots/cli__uncaught_mutant_in_factorial.snap
@@ -4,7 +4,7 @@ assertion_line: 266
 expression: redact_timestamps_sizes(stdout)
 
 ---
-build source tree ... ok in x.xxxs
+source tree ... ok in x.xxxs
 copy source and build products to scratch directory ... xxx MB in x.xxxs
 unmutated baseline ... ok in x.xxxs
 auto-set test timeout to x.xxxs
diff --git a/tests/snapshots/cli__well_tested_tree_check_only.snap b/tests/snapshots/cli__well_tested_tree_check_only.snap
index 8e696c7..f07c835 100644
--- a/tests/snapshots/cli__well_tested_tree_check_only.snap
+++ b/tests/snapshots/cli__well_tested_tree_check_only.snap
@@ -1,10 +1,10 @@
 ---
 source: tests/cli.rs
-assertion_line: 207
+assertion_line: 238
 expression: stdout
 
 ---
-check source tree ... ok
+source tree ... ok
 copy source and build products to scratch directory ... done
 unmutated baseline ... ok
 found 13 mutations to test
diff --git a/tests/snapshots/cli__well_tested_tree_finds_no_problems.snap b/tests/snapshots/cli__well_tested_tree_finds_no_problems.snap
index 8f13c1c..babcd32 100644
--- a/tests/snapshots/cli__well_tested_tree_finds_no_problems.snap
+++ b/tests/snapshots/cli__well_tested_tree_finds_no_problems.snap
@@ -1,10 +1,10 @@
 ---
 source: tests/cli.rs
-assertion_line: 188
+assertion_line: 219
 expression: stdout
 
 ---
-build source tree ... ok
+source tree ... ok
 copy source and build products to scratch directory ... done
 unmutated baseline ... ok
 found 13 mutations to test
diff --git a/tests/snapshots/cli__well_tested_tree_quiet.snap b/tests/snapshots/cli__well_tested_tree_quiet.snap
index 0341030..6c27df4 100644
--- a/tests/snapshots/cli__well_tested_tree_quiet.snap
+++ b/tests/snapshots/cli__well_tested_tree_quiet.snap
@@ -4,7 +4,7 @@ assertion_line: 198
 expression: stdout
 
 ---
-build source tree ... ok
+source tree ... ok
 copy source and build products to scratch directory ... done
 unmutated baseline ... ok
 found 13 mutations to test