From 4ff13f6081083c57cdbedd4abf4f40604d37730a Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 07:48:14 -0800 Subject: [PATCH 01/10] Add --colors --- src/list.rs | 4 +-- src/main.rs | 6 ++++- src/options.rs | 27 +++++++++++++++++-- ..._expected_mutants_for_own_source_tree.snap | 2 ++ src/visit.rs | 8 +++--- 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/list.rs b/src/list.rs index 1e8193fc..f5bb2034 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,4 +1,4 @@ -// Copyright 2023 Martin Pool +// Copyright 2023-2024 Martin Pool //! List mutants and files as text. @@ -49,7 +49,7 @@ pub(crate) fn list_mutants( writeln!( out, "{}", - mutant.name(options.show_line_col, options.colors) + mutant.name(options.show_line_col, options.colors_active()) )?; if options.emit_diffs { writeln!(out, "{}", mutant.diff())?; diff --git a/src/main.rs b/src/main.rs index 3b11bab5..a71b95bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,7 +54,7 @@ use crate::list::{list_files, list_mutants, FmtToIoWrite}; use crate::log_file::LogFile; use crate::manifest::fix_manifest; use crate::mutate::{Genre, Mutant}; -use crate::options::{Options, TestTool}; +use crate::options::{Colors, Options, TestTool}; use crate::outcome::{Phase, ScenarioOutcome}; use crate::scenario::Scenario; use crate::shard::Shard; @@ -120,6 +120,10 @@ struct Args { #[arg(long, help_heading = "Execution")] check: bool, + /// draw colors in output. + #[arg(long, value_enum, help_heading = "Output", default_value_t)] + colors: Colors, + /// show the mutation diffs. #[arg(long, help_heading = "Filters")] diff: bool, diff --git a/src/options.rs b/src/options.rs index b2058fca..1ff8287c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -92,7 +92,7 @@ pub struct Options { pub error_values: Vec, /// Show ANSI colors. - pub colors: bool, + pub colors: Colors, /// List mutants in json, etc. pub emit_json: bool, @@ -125,6 +125,19 @@ fn join_slices(a: &[String], b: &[String]) -> Vec { v } +/// Should ANSI colors be drawn? +/// +/// See also +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, Deserialize, ValueEnum)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum Colors { + #[default] + Auto, + Always, + Never, +} + impl Options { /// Build options by merging command-line args and config file. pub(crate) fn new(args: &Args, config: &Config) -> Result { @@ -146,7 +159,7 @@ impl Options { ), baseline: args.baseline, check_only: args.check, - colors: true, // TODO: An option for this and use CLICOLORS. + colors: args.colors, emit_json: args.json, emit_diffs: args.diff, error_values: join_slices(&args.error, &config.error_values), @@ -181,6 +194,16 @@ impl Options { }); Ok(options) } + + /// True if colors should be drawn. + pub fn colors_active(&self) -> bool { + // TODO: Also check (and memoize?) environment variables. + match self.colors { + Colors::Always => true, + Colors::Never => false, + Colors::Auto => true, // TODO: atty::is(atty::Stream::Stdout), + } + } } /// If the first slices is non-empty, return that, otherwise the second. diff --git a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap index d78eb9e5..809b7caa 100644 --- a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap +++ b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap @@ -207,6 +207,8 @@ src/options.rs: replace join_slices -> Vec with vec![String::new()] src/options.rs: replace join_slices -> Vec with vec!["xyzzy".into()] src/options.rs: replace + with - in join_slices src/options.rs: replace + with * in join_slices +src/options.rs: replace Options::colors_active -> bool with true +src/options.rs: replace Options::colors_active -> bool with false src/options.rs: replace or_slices -> &'c[T] with Vec::leak(Vec::new()) src/options.rs: replace or_slices -> &'c[T] with Vec::leak(vec![Default::default()]) src/options.rs: replace build_glob_set -> Result> with Ok(None) diff --git a/src/visit.rs b/src/visit.rs index 85f0ceaf..21f739f7 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2023 Martin Pool +// Copyright 2021-2024 Martin Pool //! Visit all the files in a source tree, and then the AST of each file, //! to discover mutation opportunities. @@ -590,9 +590,9 @@ mod test { fn expected_mutants_for_own_source_tree() { let config = Config::read_file(Path::new("./.cargo/mutants.toml")).expect("Read config"); let args = - Args::try_parse_from(["mutants", "--list", "--line-col=false"]).expect("Parse args"); - let mut options = Options::new(&args, &config).expect("Build options"); - options.colors = false; // TODO: Use a command-line arg. + Args::try_parse_from(["mutants", "--list", "--line-col=false", "--colors=never"]) + .expect("Parse args"); + let options = Options::new(&args, &config).expect("Build options"); let mut list_output = String::new(); let console = Console::new(); let workspace = Workspace::open( From 6aa3a9fafe060e24cc8ecd21568e8a49bb550ed2 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 07:55:48 -0800 Subject: [PATCH 02/10] Listen to CARGO_TERM_COLOR --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index a71b95bd..23d6bdce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,7 +121,13 @@ struct Args { check: bool, /// draw colors in output. - #[arg(long, value_enum, help_heading = "Output", default_value_t)] + #[arg( + long, + value_enum, + help_heading = "Output", + default_value_t, + env = "CARGO_TERM_COLOR" + )] colors: Colors, /// show the mutation diffs. From 6d4c40dd5816e4f0d67117c450a303a537f28c1a Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 08:03:38 -0800 Subject: [PATCH 03/10] --colors controls tracing colors --- src/console.rs | 5 +++-- src/list.rs | 2 +- src/main.rs | 2 +- src/options.rs | 25 +++++++++++++------------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/console.rs b/src/console.rs index ff09148d..e0ab836d 100644 --- a/src/console.rs +++ b/src/console.rs @@ -18,6 +18,7 @@ use tracing::Level; use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::prelude::*; +use crate::options::Colors; use crate::outcome::{LabOutcome, SummaryOutcome}; use crate::scenario::Scenario; use crate::tail_file::TailFile; @@ -241,7 +242,7 @@ impl Console { /// Configure tracing to send messages to the console and debug log. /// /// The debug log is opened later and provided by [Console::set_debug_log]. - pub fn setup_global_trace(&self, console_trace_level: Level) -> Result<()> { + pub fn setup_global_trace(&self, console_trace_level: Level, colors: Colors) -> Result<()> { // Show time relative to the start of the program. let uptime = tracing_subscriber::fmt::time::uptime(); let debug_log_layer = tracing_subscriber::fmt::layer() @@ -252,7 +253,7 @@ impl Console { .with_writer(self.make_debug_log_writer()); let level_filter = tracing_subscriber::filter::LevelFilter::from_level(console_trace_level); let console_layer = tracing_subscriber::fmt::layer() - .with_ansi(true) + .with_ansi(colors.active()) .with_writer(self.make_terminal_writer()) .with_target(false) .without_time() diff --git a/src/list.rs b/src/list.rs index f5bb2034..65386521 100644 --- a/src/list.rs +++ b/src/list.rs @@ -49,7 +49,7 @@ pub(crate) fn list_mutants( writeln!( out, "{}", - mutant.name(options.show_line_col, options.colors_active()) + mutant.name(options.show_line_col, options.colors.active()) )?; if options.emit_diffs { writeln!(out, "{}", mutant.diff())?; diff --git a/src/main.rs b/src/main.rs index 23d6bdce..43b7c8b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -337,7 +337,7 @@ fn main() -> Result<()> { } let console = Console::new(); - console.setup_global_trace(args.level)?; + console.setup_global_trace(args.level, args.colors)?; // We don't have Options yet. interrupt::install_handler(); let start_dir: &Utf8Path = if let Some(manifest_path) = &args.manifest_path { diff --git a/src/options.rs b/src/options.rs index 1ff8287c..230b190c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -126,8 +126,6 @@ fn join_slices(a: &[String], b: &[String]) -> Vec { } /// Should ANSI colors be drawn? -/// -/// See also #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Display, Deserialize, ValueEnum)] #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] @@ -138,6 +136,19 @@ pub enum Colors { Never, } +impl Colors { + /// True if colors should actually be drawn, resolving the `auto` behavior.` + pub fn active(&self) -> bool { + // TODO: Also check (and memoize?) environment variables, or maybe do that at a higher level + // when constructing Colors. + match self { + Colors::Always => true, + Colors::Never => false, + Colors::Auto => true, // TODO: Check and memoize atty, etc. + } + } +} + impl Options { /// Build options by merging command-line args and config file. pub(crate) fn new(args: &Args, config: &Config) -> Result { @@ -194,16 +205,6 @@ impl Options { }); Ok(options) } - - /// True if colors should be drawn. - pub fn colors_active(&self) -> bool { - // TODO: Also check (and memoize?) environment variables. - match self.colors { - Colors::Always => true, - Colors::Never => false, - Colors::Auto => true, // TODO: atty::is(atty::Stream::Stdout), - } - } } /// If the first slices is non-empty, return that, otherwise the second. From 05018fb5418fce5a63cf3d56dacd1f16bf4f31de Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 08:31:54 -0800 Subject: [PATCH 04/10] Hook --colors into --list and other styling --- src/console.rs | 12 ++++++- src/list.rs | 9 +++--- src/main.rs | 1 + src/options.rs | 32 ++++++++++++++----- ..._expected_mutants_for_own_source_tree.snap | 12 +++++-- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/console.rs b/src/console.rs index e0ab836d..580839d1 100644 --- a/src/console.rs +++ b/src/console.rs @@ -43,6 +43,13 @@ impl Console { } } + pub fn set_colors_enabled(&self, colors: Colors) { + if let Some(colors) = colors.active() { + ::console::set_colors_enabled(colors); + } + // Otherwise, let the console crate decide, based on isatty, etc. + } + pub fn walk_tree_start(&self) { self.view .update(|model| model.walk_tree = Some(WalkModel::default())); @@ -245,6 +252,9 @@ impl Console { pub fn setup_global_trace(&self, console_trace_level: Level, colors: Colors) -> Result<()> { // Show time relative to the start of the program. let uptime = tracing_subscriber::fmt::time::uptime(); + let stderr_colors = colors + .active() + .unwrap_or_else(::console::colors_enabled_stderr); let debug_log_layer = tracing_subscriber::fmt::layer() .with_ansi(false) .with_file(true) // source file name @@ -253,7 +263,7 @@ impl Console { .with_writer(self.make_debug_log_writer()); let level_filter = tracing_subscriber::filter::LevelFilter::from_level(console_trace_level); let console_layer = tracing_subscriber::fmt::layer() - .with_ansi(colors.active()) + .with_ansi(stderr_colors) .with_writer(self.make_terminal_writer()) .with_target(false) .without_time() diff --git a/src/list.rs b/src/list.rs index 65386521..a3045040 100644 --- a/src/list.rs +++ b/src/list.rs @@ -45,12 +45,11 @@ pub(crate) fn list_mutants( } out.write_str(&serde_json::to_string_pretty(&list)?)?; } else { + // TODO: Do we need to check this? Could the console library strip them if they're not + // supported? + let colors = options.colors.active_stdout(); for mutant in mutants { - writeln!( - out, - "{}", - mutant.name(options.show_line_col, options.colors.active()) - )?; + writeln!(out, "{}", mutant.name(options.show_line_col, colors))?; if options.emit_diffs { writeln!(out, "{}", mutant.diff())?; } diff --git a/src/main.rs b/src/main.rs index 43b7c8b1..ac6ee505 100644 --- a/src/main.rs +++ b/src/main.rs @@ -338,6 +338,7 @@ fn main() -> Result<()> { let console = Console::new(); console.setup_global_trace(args.level, args.colors)?; // We don't have Options yet. + console.set_colors_enabled(args.colors); interrupt::install_handler(); let start_dir: &Utf8Path = if let Some(manifest_path) = &args.manifest_path { diff --git a/src/options.rs b/src/options.rs index 230b190c..8c5bc845 100644 --- a/src/options.rs +++ b/src/options.rs @@ -137,16 +137,32 @@ pub enum Colors { } impl Colors { - /// True if colors should actually be drawn, resolving the `auto` behavior.` - pub fn active(&self) -> bool { - // TODO: Also check (and memoize?) environment variables, or maybe do that at a higher level - // when constructing Colors. - match self { - Colors::Always => true, - Colors::Never => false, - Colors::Auto => true, // TODO: Check and memoize atty, etc. + fn forced_value(&self) -> Option { + // From https://bixense.com/clicolors/ + if env::var("NO_COLOR").map_or(false, |x| x != "0") { + Some(false) + } else if env::var("CLICOLOR_FORCE").map_or(false, |x| x != "0") { + Some(true) + } else { + None + } + } + + pub fn active(&self) -> Option { + if let Some(active) = self.forced_value() { + Some(active) + } else { + match self { + Colors::Always => Some(true), + Colors::Never => Some(false), + Colors::Auto => None, // library should decide + } } } + + pub fn active_stdout(&self) -> bool { + self.active().unwrap_or_else(::console::colors_enabled) + } } impl Options { diff --git a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap index 809b7caa..e101c240 100644 --- a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap +++ b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap @@ -207,8 +207,16 @@ src/options.rs: replace join_slices -> Vec with vec![String::new()] src/options.rs: replace join_slices -> Vec with vec!["xyzzy".into()] src/options.rs: replace + with - in join_slices src/options.rs: replace + with * in join_slices -src/options.rs: replace Options::colors_active -> bool with true -src/options.rs: replace Options::colors_active -> bool with false +src/options.rs: replace Colors::forced_value -> Option with None +src/options.rs: replace Colors::forced_value -> Option with Some(true) +src/options.rs: replace Colors::forced_value -> Option with Some(false) +src/options.rs: replace != with == in Colors::forced_value +src/options.rs: replace != with == in Colors::forced_value +src/options.rs: replace Colors::active -> Option with None +src/options.rs: replace Colors::active -> Option with Some(true) +src/options.rs: replace Colors::active -> Option with Some(false) +src/options.rs: replace Colors::active_stdout -> bool with true +src/options.rs: replace Colors::active_stdout -> bool with false src/options.rs: replace or_slices -> &'c[T] with Vec::leak(Vec::new()) src/options.rs: replace or_slices -> &'c[T] with Vec::leak(vec![Default::default()]) src/options.rs: replace build_glob_set -> Result> with Ok(None) From dac4428b1783eb307864e082338db0953b0aaaca Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 08:37:28 -0800 Subject: [PATCH 05/10] Clear NOCOLOR etc when running cli tests --- tests/cli/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/cli/main.rs b/tests/cli/main.rs index a01ede50..66abd207 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -59,7 +59,12 @@ fn run() -> assert_cmd::Command { // as reasonably possible. env::vars() .map(|(k, _v)| k) - .filter(|k| k.starts_with("CARGO_MUTANTS_")) + .filter(|k| { + k.starts_with("CARGO_MUTANTS_") + || k == "CLICOLOR_FORCE" + || k == "NOCOLOR" + || k == "CARGO_TERM_COLOR" + }) .for_each(|k| { cmd.env_remove(k); }); From c53bfe08987faab57cbc1c55f4bf0f84bf57e683 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 08:45:42 -0800 Subject: [PATCH 06/10] Also force colors on console stderr --- src/console.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/console.rs b/src/console.rs index 580839d1..7cf3e2d2 100644 --- a/src/console.rs +++ b/src/console.rs @@ -44,8 +44,9 @@ impl Console { } pub fn set_colors_enabled(&self, colors: Colors) { - if let Some(colors) = colors.active() { + if let Some(colors) = colors.forced_value() { ::console::set_colors_enabled(colors); + ::console::set_colors_enabled_stderr(colors); } // Otherwise, let the console crate decide, based on isatty, etc. } @@ -253,7 +254,7 @@ impl Console { // Show time relative to the start of the program. let uptime = tracing_subscriber::fmt::time::uptime(); let stderr_colors = colors - .active() + .forced_value() .unwrap_or_else(::console::colors_enabled_stderr); let debug_log_layer = tracing_subscriber::fmt::layer() .with_ansi(false) From 0d6d23ce4e82cedc96febce423dd0f41b8732d9c Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Wed, 24 Jan 2024 08:46:46 -0800 Subject: [PATCH 07/10] Rename to Colors::forced_value --- src/options.rs | 18 ++++++++---------- ...__expected_mutants_for_own_source_tree.snap | 3 --- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/options.rs b/src/options.rs index 8c5bc845..d24a8939 100644 --- a/src/options.rs +++ b/src/options.rs @@ -137,20 +137,17 @@ pub enum Colors { } impl Colors { - fn forced_value(&self) -> Option { + /// If colors were forced on or off by the user through an option or + /// environment variable, return that value. + /// + /// Otherwise, return None, meaning we should decide based on the + /// detected terminal characteristics. + pub fn forced_value(&self) -> Option { // From https://bixense.com/clicolors/ if env::var("NO_COLOR").map_or(false, |x| x != "0") { Some(false) } else if env::var("CLICOLOR_FORCE").map_or(false, |x| x != "0") { Some(true) - } else { - None - } - } - - pub fn active(&self) -> Option { - if let Some(active) = self.forced_value() { - Some(active) } else { match self { Colors::Always => Some(true), @@ -161,7 +158,8 @@ impl Colors { } pub fn active_stdout(&self) -> bool { - self.active().unwrap_or_else(::console::colors_enabled) + self.forced_value() + .unwrap_or_else(::console::colors_enabled) } } diff --git a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap index e101c240..239c0353 100644 --- a/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap +++ b/src/snapshots/cargo_mutants__visit__test__expected_mutants_for_own_source_tree.snap @@ -212,9 +212,6 @@ src/options.rs: replace Colors::forced_value -> Option with Some(true) src/options.rs: replace Colors::forced_value -> Option with Some(false) src/options.rs: replace != with == in Colors::forced_value src/options.rs: replace != with == in Colors::forced_value -src/options.rs: replace Colors::active -> Option with None -src/options.rs: replace Colors::active -> Option with Some(true) -src/options.rs: replace Colors::active -> Option with Some(false) src/options.rs: replace Colors::active_stdout -> bool with true src/options.rs: replace Colors::active_stdout -> bool with false src/options.rs: replace or_slices -> &'c[T] with Vec::leak(Vec::new()) From 563bcd180fd3d3050ac06c53cd5f4214b18069dc Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Fri, 26 Jan 2024 07:28:24 -0800 Subject: [PATCH 08/10] Docs for CARGO_TERM_COLOR etc --- NEWS.md | 1 + book/src/SUMMARY.md | 1 + book/src/ci.md | 3 +++ book/src/controlling.md | 12 +----------- book/src/output.md | 23 +++++++++++++++++++++++ book/src/pr-diff.md | 3 +++ book/src/text-output.md | 1 - 7 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 book/src/output.md delete mode 100644 book/src/text-output.md diff --git a/NEWS.md b/NEWS.md index f3bf0cd3..c5f5e303 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # cargo-mutants changelog ## Unreleased + ## 24.1.2 - New: `--in-place` option tests mutations in the original source tree, without copying the tree. This is faster and uses less disk space, but it's incompatible with `--jobs`, and you must be careful not to edit or commit the source tree while tests are running. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e92b1fc6..0fa21aed 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,6 +12,7 @@ - [Filtering files](skip_files.md) - [Filtering functions and mutants](filter_mutants.md) - [Controlling cargo-mutants](controlling.md) + - [Display and output](output.md) - [Listing and previewing mutations](list.md) - [Workspaces and packages](workspaces.md) - [Passing options to Cargo](cargo-args.md) diff --git a/book/src/ci.md b/book/src/ci.md index 119847e0..bc085299 100644 --- a/book/src/ci.md +++ b/book/src/ci.md @@ -23,6 +23,9 @@ Here is an example of a GitHub Actions workflow that runs mutation tests and upl ```yml name: cargo-mutants +env: + CARGO_TERM_COLOR: always + on: push: branches: diff --git a/book/src/controlling.md b/book/src/controlling.md index fba9a824..b998d472 100644 --- a/book/src/controlling.md +++ b/book/src/controlling.md @@ -26,14 +26,4 @@ the source. `-d`, `--dir`: Test the Rust tree in the given directory, rather than the source tree enclosing the working directory where cargo-mutants is launched. -## Console output - -`-v`, `--caught`: Also print mutants that were caught by tests. - -`-V`, `--unviable`: Also print mutants that failed `cargo build`. - -`--no-times`: Don't print elapsed times. - -`-L`, `--level`, and `$CARGO_MUTANTS_TRACE_LEVEL`: set the verbosity of trace -output to stdout. The default is `info`, and it can be increased to `debug` or -`trace`. +`--manifest-path`: Also selects the tree to test, but takes a path to a Cargo.toml file rather than a directory. (This is less convenient but compatible with other Cargo commands.) diff --git a/book/src/output.md b/book/src/output.md new file mode 100644 index 00000000..b426a63c --- /dev/null +++ b/book/src/output.md @@ -0,0 +1,23 @@ +# Display and output + +cargo-mutants writes a list of missed or timed-out mutants to stderr, and optionally mutants that were caught (with `--caught`) or failed to build (with `--unviable`) to stdout. It writes error or debug messages to stderr. + +The following options control what is printed to stdout and stderr. + +`-v`, `--caught`: Also print mutants that were caught by tests. + +`-V`, `--unviable`: Also print mutants that failed `cargo build`. + +`--no-times`: Don't print elapsed times. (This is intended mostly to make the output more stable for testing.) + +## Colors + +`--colors=always|never|auto`: Control whether to use colors in output. The default is `auto`, which will write colors if the output is a terminal that supports colors. Color support is detected independently for stdout and stderr, so you should still see colors on stderr if stdout is redirected. + +The same values can be set with the `CARGO_TERM_COLOR` environment variable, which is respected by many Cargo commands. + +cargo-mutants also respects the [`NO_COLOR`](https://no-color.org/) and [`CLICOLOR_FORCE`](https://bixense.com/clicolors/) environment variables. If they are set to a value other than `0` then colors will be disabled or enabled regardless of any other settings. + +## Debug trace + +`-L`, `--level`, and `$CARGO_MUTANTS_TRACE_LEVEL`: set the verbosity of trace output to stderr. The default is `info`, and it can be increased to `debug` or `trace`. diff --git a/book/src/pr-diff.md b/book/src/pr-diff.md index 5414dbd7..099dca82 100644 --- a/book/src/pr-diff.md +++ b/book/src/pr-diff.md @@ -10,6 +10,9 @@ name: Tests permissions: contents: read +env: + CARGO_TERM_COLOR: always + on: push: branches: diff --git a/book/src/text-output.md b/book/src/text-output.md deleted file mode 100644 index 80c6cd21..00000000 --- a/book/src/text-output.md +++ /dev/null @@ -1 +0,0 @@ -# Text output From c40299e51f9187a651af0cae0ba4d85ac1573e66 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Fri, 26 Jan 2024 07:52:22 -0800 Subject: [PATCH 09/10] Tests for color options/vars --- tests/cli/colors.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++ tests/cli/main.rs | 1 + 2 files changed, 108 insertions(+) create mode 100644 tests/cli/colors.rs diff --git a/tests/cli/colors.rs b/tests/cli/colors.rs new file mode 100644 index 00000000..b653ac8c --- /dev/null +++ b/tests/cli/colors.rs @@ -0,0 +1,107 @@ +// Copyright 2024 Martin Pool + +//! Tests for color output. + +// Testing autodetection seems hard because we'd have to make a tty, so we'll rely on humans noticing +// for now. + +use predicates::prelude::*; + +use super::run; + +fn has_color_listing() -> impl Predicate { + predicates::str::contains("with \x1b[33m0\x1b[0m") +} + +fn has_ansi_escape() -> impl Predicate { + predicates::str::contains("\x1b[") +} + +fn has_color_debug() -> impl Predicate { + predicates::str::contains("\x1b[34mDEBUG\x1b[0m") +} + +/// The test fixtures force off colors, even if something else tries to turn it on. +#[test] +fn no_color_in_test_subprocesses_by_default() { + run() + .args(["mutants", "-d", "testdata/small_well_tested", "--list"]) + .assert() + .success() + .stdout(has_ansi_escape().not()) + .stderr(has_ansi_escape().not()); +} + +/// Colors can be turned on with `--color` and they show up in the listing and +/// in trace output. +#[test] +fn colors_always_shows_in_stdout_and_trace() { + run() + .args([ + "mutants", + "-d", + "testdata/small_well_tested", + "--list", + "--colors=always", + "-Ltrace", + ]) + .assert() + .success() + .stdout(has_color_listing()) + .stderr(has_color_debug()); +} + +#[test] +fn cargo_term_color_env_shows_colors() { + run() + .env("CARGO_TERM_COLOR", "always") + .args([ + "mutants", + "-d", + "testdata/small_well_tested", + "--list", + "-Ltrace", + ]) + .assert() + .success() + .stdout(has_color_listing()) + .stderr(has_color_debug()); +} + +#[test] +fn invalid_cargo_term_color_rejected_with_message() { + run() + .env("CARGO_TERM_COLOR", "invalid") + .args([ + "mutants", + "-d", + "testdata/small_well_tested", + "--list", + "-Ltrace", + ]) + .assert() + .stderr(predicate::str::contains( + // The message does not currently name the variable due to . + "invalid value 'invalid'", + )) + .code(1); +} + +/// Colors can be turned on with `CLICOLOR_FORCE`. +#[test] +fn clicolor_force_shows_in_stdout_and_trace() { + run() + .env("CLICOLOR_FORCE", "1") + .args([ + "mutants", + "-d", + "testdata/small_well_tested", + "--list", + "--colors=never", + "-Ltrace", + ]) + .assert() + .success() + .stdout(has_color_listing()) + .stderr(has_color_debug()); +} diff --git a/tests/cli/main.rs b/tests/cli/main.rs index 66abd207..df5cecb3 100644 --- a/tests/cli/main.rs +++ b/tests/cli/main.rs @@ -22,6 +22,7 @@ use subprocess::{Popen, PopenConfig, Redirection}; use tempfile::{tempdir, TempDir}; mod build_dir; +mod colors; mod config; mod error_value; mod in_diff; From 5b6b60dffee965abd1707e8a88fbf4e215e54106 Mon Sep 17 00:00:00 2001 From: Martin Pool Date: Fri, 26 Jan 2024 07:55:18 -0800 Subject: [PATCH 10/10] News about colors --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index c5f5e303..71c7d48b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ ## Unreleased +- New: Colored output can be enabled in CI or other noninteractive situations by passing `--colors=always`, or setting `CARGO_TERM_COLOR=always`, or `CLICOLOR_FORCE=1`. Colors can similarly be forced off with `--colors=never`, `CARGO_TERM_COLOR=never`, or `NO_COLOR=1`. + ## 24.1.2 - New: `--in-place` option tests mutations in the original source tree, without copying the tree. This is faster and uses less disk space, but it's incompatible with `--jobs`, and you must be careful not to edit or commit the source tree while tests are running.