From 4c56227bee9b3e918f6feaebc4769dbfc3dfe9b2 Mon Sep 17 00:00:00 2001 From: danggg Date: Sun, 23 Nov 2025 15:46:28 +0700 Subject: [PATCH 1/2] [nextest-runner] add verbose flag to display test command lines --- nextest-runner/src/list/test_list.rs | 30 ++++++++++++++++++++ nextest-runner/src/reporter/displayer/imp.rs | 15 ++++++++++ nextest-runner/src/reporter/events.rs | 3 ++ nextest-runner/src/reporter/imp.rs | 1 + nextest-runner/src/runner/dispatcher.rs | 2 ++ nextest-runner/src/runner/executor.rs | 14 +++++++++ nextest-runner/src/runner/internal_events.rs | 1 + 7 files changed, 66 insertions(+) diff --git a/nextest-runner/src/list/test_list.rs b/nextest-runner/src/list/test_list.rs index f250b08a896..195b4b01ffa 100644 --- a/nextest-runner/src/list/test_list.rs +++ b/nextest-runner/src/list/test_list.rs @@ -1182,6 +1182,36 @@ impl<'a> TestInstance<'a> { interceptor, ) } + + /// Returns the command line string for running this test. + pub(crate) fn command_line( + &self, + ctx: &TestExecuteContext<'_>, + test_list: &TestList<'_>, + wrapper_script: Option<&'a WrapperScriptConfig>, + extra_args: &[String], + ) -> String { + let platform_runner = ctx + .target_runner + .for_build_platform(self.suite_info.build_platform); + + let mut cli = TestCommandCli::default(); + cli.apply_wrappers( + wrapper_script, + platform_runner, + test_list.workspace_root(), + &test_list.rust_build_meta().target_directory, + ); + cli.push(self.suite_info.binary_path.as_str()); + + cli.extend(["--exact", self.name, "--nocapture"]); + if self.test_info.ignored { + cli.push("--ignored"); + } + cli.extend(extra_args.iter().map(String::as_str)); + + shell_words::join(cli.to_owned_cli()) + } } #[derive(Clone, Debug, Default)] diff --git a/nextest-runner/src/reporter/displayer/imp.rs b/nextest-runner/src/reporter/displayer/imp.rs index fc84ed316e1..a2519ccb4c5 100644 --- a/nextest-runner/src/reporter/displayer/imp.rs +++ b/nextest-runner/src/reporter/displayer/imp.rs @@ -54,6 +54,7 @@ pub(crate) struct DisplayReporterBuilder { pub(crate) failure_output: Option, pub(crate) should_colorize: bool, pub(crate) no_capture: bool, + pub(crate) verbose: bool, pub(crate) show_progress: ShowProgress, pub(crate) no_output_indent: bool, pub(crate) max_progress_running: MaxProgressRunning, @@ -141,6 +142,7 @@ impl DisplayReporterBuilder { final_status_level: self.status_levels.final_status_level, }, no_capture: self.no_capture, + verbose: self.verbose, no_output_indent: self.no_output_indent, counter_width, styles, @@ -377,6 +379,7 @@ struct DisplayReporterImpl<'a> { default_filter: CompiledDefaultFilter, status_levels: StatusLevels, no_capture: bool, + verbose: bool, no_output_indent: bool, // None if no counter is displayed. If a counter is displayed, this is the // width of the total number of tests to run. @@ -601,6 +604,7 @@ impl<'a> DisplayReporterImpl<'a> { stress_index, test_instance, current_stats, + command_line, .. } => { // In no-capture mode, print out a test start event. @@ -623,6 +627,15 @@ impl<'a> DisplayReporterImpl<'a> { ), )?; } + + if self.verbose { + writeln!( + writer, + "{:>12} {}", + "COMMAND".style(self.styles.count), + command_line, + )?; + } } TestEventKind::TestSlow { stress_index, @@ -2390,10 +2403,12 @@ mod tests { failure_output: Some(TestOutputDisplay::Immediate), should_colorize: false, no_capture: true, + verbose: false, show_progress: ShowProgress::Counter, no_output_indent: false, max_progress_running: MaxProgressRunning::default(), }; + let output = ReporterStderr::Buffer(out); let reporter = builder.build(&configs, output); f(reporter); diff --git a/nextest-runner/src/reporter/events.rs b/nextest-runner/src/reporter/events.rs index 50e2b285b4c..1d176e8221d 100644 --- a/nextest-runner/src/reporter/events.rs +++ b/nextest-runner/src/reporter/events.rs @@ -168,6 +168,9 @@ pub enum TestEventKind<'a> { /// The number of tests currently running, including this one. running: usize, + + /// The command line that will be used to run this test. + command_line: String, }, /// A test was slower than a configured soft timeout. diff --git a/nextest-runner/src/reporter/imp.rs b/nextest-runner/src/reporter/imp.rs index 8bf7e6cafbf..67c1c3a9790 100644 --- a/nextest-runner/src/reporter/imp.rs +++ b/nextest-runner/src/reporter/imp.rs @@ -148,6 +148,7 @@ impl ReporterBuilder { failure_output: self.failure_output, should_colorize: self.should_colorize, no_capture: self.no_capture, + verbose: self.verbose, show_progress: self.show_progress, no_output_indent: self.no_output_indent, max_progress_running: self.max_progress_running, diff --git a/nextest-runner/src/runner/dispatcher.rs b/nextest-runner/src/runner/dispatcher.rs index e6fd0b86ea9..d7827027a1f 100644 --- a/nextest-runner/src/runner/dispatcher.rs +++ b/nextest-runner/src/runner/dispatcher.rs @@ -656,6 +656,7 @@ where InternalEvent::Executor(ExecutorEvent::Started { stress_index, test_instance, + command_line, req_rx_tx, }) => { if self.run_stats.cancel_reason.is_some() { @@ -678,6 +679,7 @@ where test_instance, current_stats: self.run_stats, running: self.running_tests.len(), + command_line, }) } InternalEvent::Executor(ExecutorEvent::Slow { diff --git a/nextest-runner/src/runner/executor.rs b/nextest-runner/src/runner/executor.rs index 66e3bcf6aab..95021f686e3 100644 --- a/nextest-runner/src/runner/executor.rs +++ b/nextest-runner/src/runner/executor.rs @@ -210,11 +210,25 @@ impl<'a> ExecutorContext<'a> { let (req_rx_tx, req_rx_rx) = oneshot::channel(); + // Compute the command line for this test. + let ctx = TestExecuteContext { + profile_name: self.profile.name(), + double_spawn: &self.double_spawn, + target_runner: &self.target_runner, + }; + let command_line = test.instance.command_line( + &ctx, + self.test_list, + settings.run_wrapper(), + settings.run_extra_args(), + ); + // Wait for the Started event to be processed by the // execution future. _ = resp_tx.send(ExecutorEvent::Started { stress_index, test_instance: test.instance, + command_line, req_rx_tx, }); let mut req_rx = match req_rx_rx.await { diff --git a/nextest-runner/src/runner/internal_events.rs b/nextest-runner/src/runner/internal_events.rs index 18c18739894..73d04eb3dc7 100644 --- a/nextest-runner/src/runner/internal_events.rs +++ b/nextest-runner/src/runner/internal_events.rs @@ -69,6 +69,7 @@ pub(super) enum ExecutorEvent<'a> { Started { stress_index: Option, test_instance: TestInstance<'a>, + command_line: String, // The channel over which to return the unit request. // // The callback context is solely responsible for coordinating the From dc0683da725d8b6c7a60ed67866b43e84d5a0ec8 Mon Sep 17 00:00:00 2001 From: danggg Date: Sat, 20 Dec 2025 10:03:43 +0700 Subject: [PATCH 2/2] refactor command display and deduplicate execution logic --- nextest-runner/src/list/test_list.rs | 44 ++++++++------------ nextest-runner/src/reporter/displayer/imp.rs | 2 +- nextest-runner/src/reporter/events.rs | 2 +- nextest-runner/src/runner/executor.rs | 21 +++++----- nextest-runner/src/runner/internal_events.rs | 2 +- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/nextest-runner/src/list/test_list.rs b/nextest-runner/src/list/test_list.rs index 195b4b01ffa..31678a8becb 100644 --- a/nextest-runner/src/list/test_list.rs +++ b/nextest-runner/src/list/test_list.rs @@ -1133,32 +1133,14 @@ impl<'a> TestInstance<'a> { /// Creates the command for this test instance. pub(crate) fn make_command( &self, - ctx: &TestExecuteContext<'_>, + ctx: &'a TestExecuteContext<'a>, test_list: &TestList<'_>, wrapper_script: Option<&'a WrapperScriptConfig>, - extra_args: &[String], + extra_args: &'a [String], interceptor: &Interceptor, ) -> TestCommand { // TODO: non-rust tests - - let platform_runner = ctx - .target_runner - .for_build_platform(self.suite_info.build_platform); - - let mut cli = TestCommandCli::default(); - cli.apply_wrappers( - wrapper_script, - platform_runner, - test_list.workspace_root(), - &test_list.rust_build_meta().target_directory, - ); - cli.push(self.suite_info.binary_path.as_str()); - - cli.extend(["--exact", self.name, "--nocapture"]); - if self.test_info.ignored { - cli.push("--ignored"); - } - cli.extend(extra_args.iter().map(String::as_str)); + let cli = self.compute_cli(ctx, test_list, wrapper_script, extra_args); let lctx = LocalExecuteContext { phase: TestCommandPhase::Run, @@ -1183,14 +1165,24 @@ impl<'a> TestInstance<'a> { ) } - /// Returns the command line string for running this test. pub(crate) fn command_line( &self, - ctx: &TestExecuteContext<'_>, + ctx: &'a TestExecuteContext<'a>, test_list: &TestList<'_>, wrapper_script: Option<&'a WrapperScriptConfig>, - extra_args: &[String], - ) -> String { + extra_args: &'a [String], + ) -> Vec { + self.compute_cli(ctx, test_list, wrapper_script, extra_args) + .to_owned_cli() + } + + fn compute_cli( + &self, + ctx: &'a TestExecuteContext<'a>, + test_list: &TestList<'_>, + wrapper_script: Option<&'a WrapperScriptConfig>, + extra_args: &'a [String], + ) -> TestCommandCli<'a> { let platform_runner = ctx .target_runner .for_build_platform(self.suite_info.build_platform); @@ -1210,7 +1202,7 @@ impl<'a> TestInstance<'a> { } cli.extend(extra_args.iter().map(String::as_str)); - shell_words::join(cli.to_owned_cli()) + cli } } diff --git a/nextest-runner/src/reporter/displayer/imp.rs b/nextest-runner/src/reporter/displayer/imp.rs index a2519ccb4c5..826182dd68c 100644 --- a/nextest-runner/src/reporter/displayer/imp.rs +++ b/nextest-runner/src/reporter/displayer/imp.rs @@ -633,7 +633,7 @@ impl<'a> DisplayReporterImpl<'a> { writer, "{:>12} {}", "COMMAND".style(self.styles.count), - command_line, + shell_words::join(command_line), )?; } } diff --git a/nextest-runner/src/reporter/events.rs b/nextest-runner/src/reporter/events.rs index 1d176e8221d..d1dcabed4a7 100644 --- a/nextest-runner/src/reporter/events.rs +++ b/nextest-runner/src/reporter/events.rs @@ -170,7 +170,7 @@ pub enum TestEventKind<'a> { running: usize, /// The command line that will be used to run this test. - command_line: String, + command_line: Vec, }, /// A test was slower than a configured soft timeout. diff --git a/nextest-runner/src/runner/executor.rs b/nextest-runner/src/runner/executor.rs index 95021f686e3..48bb2de8c73 100644 --- a/nextest-runner/src/runner/executor.rs +++ b/nextest-runner/src/runner/executor.rs @@ -94,6 +94,14 @@ impl<'a> ExecutorContext<'a> { } } + fn test_execute_context(&self) -> TestExecuteContext<'_> { + TestExecuteContext { + profile_name: self.profile.name(), + double_spawn: &self.double_spawn, + target_runner: &self.target_runner, + } + } + /// Run scripts, returning data about each successfully executed script. pub(super) async fn run_setup_scripts( &self, @@ -210,12 +218,7 @@ impl<'a> ExecutorContext<'a> { let (req_rx_tx, req_rx_rx) = oneshot::channel(); - // Compute the command line for this test. - let ctx = TestExecuteContext { - profile_name: self.profile.name(), - double_spawn: &self.double_spawn, - target_runner: &self.target_runner, - }; + let ctx = self.test_execute_context(); let command_line = test.instance.command_line( &ctx, self.test_list, @@ -678,11 +681,7 @@ impl<'a> ExecutorContext<'a> { resp_tx: &UnboundedSender>, req_rx: &mut UnboundedReceiver>, ) -> Result, ChildStartError> { - let ctx = TestExecuteContext { - profile_name: self.profile.name(), - double_spawn: &self.double_spawn, - target_runner: &self.target_runner, - }; + let ctx = self.test_execute_context(); let mut cmd = test.test_instance.make_command( &ctx, self.test_list, diff --git a/nextest-runner/src/runner/internal_events.rs b/nextest-runner/src/runner/internal_events.rs index 73d04eb3dc7..3d2984e64f7 100644 --- a/nextest-runner/src/runner/internal_events.rs +++ b/nextest-runner/src/runner/internal_events.rs @@ -69,7 +69,7 @@ pub(super) enum ExecutorEvent<'a> { Started { stress_index: Option, test_instance: TestInstance<'a>, - command_line: String, + command_line: Vec, // The channel over which to return the unit request. // // The callback context is solely responsible for coordinating the