Skip to main content

nextest_runner/runner/
imp.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use super::{DispatcherContext, ExecutorContext, RunnerTaskState, StressLoopAction};
5use crate::{
6    config::{
7        core::EvaluatableProfile,
8        elements::{FlakyResult, MaxFail, RetryPolicy, TestGroup, TestThreads},
9        scripts::SetupScriptExecuteData,
10    },
11    double_spawn::DoubleSpawnInfo,
12    errors::{
13        ConfigureHandleInheritanceError, DebuggerCommandParseError, StressCountParseError,
14        TestRunnerBuildError, TestRunnerExecuteErrors, TracerCommandParseError,
15    },
16    input::{InputHandler, InputHandlerKind, InputHandlerStatus},
17    list::{OwnedTestInstanceId, TestInstanceWithSettings, TestList},
18    reporter::events::{ReporterEvent, RunOutcome, StressIndex},
19    runner::ExecutorEvent,
20    signal::{SignalHandler, SignalHandlerKind},
21    target_runner::TargetRunner,
22    test_output::CaptureStrategy,
23};
24use async_scoped::TokioScope;
25use chrono::{DateTime, Local};
26use future_queue::{FutureQueueContext, StreamExt};
27use futures::{future::Fuse, prelude::*};
28use nextest_metadata::FilterMatch;
29use quick_junit::ReportUuid;
30use semver::Version;
31use std::{
32    collections::BTreeSet, convert::Infallible, fmt, num::NonZero, pin::Pin, str::FromStr,
33    sync::Arc, time::Duration,
34};
35use tokio::{
36    runtime::Runtime,
37    sync::{mpsc::unbounded_channel, oneshot},
38    task::JoinError,
39};
40use tracing::{debug, warn};
41
42/// A parsed debugger command.
43#[derive(Clone, Debug)]
44pub struct DebuggerCommand {
45    program: String,
46    args: Vec<String>,
47}
48
49impl DebuggerCommand {
50    /// Gets the program.
51    pub fn program(&self) -> &str {
52        // The from_str constructor ensures that there is at least one part.
53        &self.program
54    }
55
56    /// Gets the arguments.
57    pub fn args(&self) -> &[String] {
58        &self.args
59    }
60}
61
62impl FromStr for DebuggerCommand {
63    type Err = DebuggerCommandParseError;
64
65    fn from_str(command: &str) -> Result<Self, Self::Err> {
66        let mut parts =
67            shell_words::split(command).map_err(DebuggerCommandParseError::ShellWordsParse)?;
68        if parts.is_empty() {
69            return Err(DebuggerCommandParseError::EmptyCommand);
70        }
71        let program = parts.remove(0);
72        Ok(Self {
73            program,
74            args: parts,
75        })
76    }
77}
78
79/// A parsed tracer command.
80#[derive(Clone, Debug)]
81pub struct TracerCommand {
82    program: String,
83    args: Vec<String>,
84}
85
86impl TracerCommand {
87    /// Gets the program.
88    pub fn program(&self) -> &str {
89        &self.program
90    }
91
92    /// Gets the arguments.
93    pub fn args(&self) -> &[String] {
94        &self.args
95    }
96}
97
98impl FromStr for TracerCommand {
99    type Err = TracerCommandParseError;
100
101    fn from_str(command: &str) -> Result<Self, Self::Err> {
102        let mut parts =
103            shell_words::split(command).map_err(TracerCommandParseError::ShellWordsParse)?;
104        if parts.is_empty() {
105            return Err(TracerCommandParseError::EmptyCommand);
106        }
107        let program = parts.remove(0);
108        Ok(Self {
109            program,
110            args: parts,
111        })
112    }
113}
114
115/// An interceptor wraps test execution with a debugger or tracer.
116#[derive(Clone, Debug, Default)]
117pub enum Interceptor {
118    /// No interceptor - standard test execution.
119    #[default]
120    None,
121
122    /// Run the test under a debugger.
123    Debugger(DebuggerCommand),
124
125    /// Run the test under a syscall tracer.
126    Tracer(TracerCommand),
127}
128
129impl Interceptor {
130    /// Returns true if timeouts should be disabled.
131    ///
132    /// Both debuggers and tracers disable timeouts.
133    pub fn should_disable_timeouts(&self) -> bool {
134        match self {
135            Interceptor::None => false,
136            Interceptor::Debugger(_) | Interceptor::Tracer(_) => true,
137        }
138    }
139
140    /// Returns true if stdin should be passed through to child test processes.
141    ///
142    /// Only debuggers need stdin passthrough for interactive debugging.
143    pub fn should_passthrough_stdin(&self) -> bool {
144        match self {
145            Interceptor::None | Interceptor::Tracer(_) => false,
146            Interceptor::Debugger(_) => true,
147        }
148    }
149
150    /// Returns true if a process group should be created for the child.
151    ///
152    /// Debuggers need terminal control, so no process group is created. Tracers
153    /// work fine with process groups.
154    pub fn should_create_process_group(&self) -> bool {
155        match self {
156            Interceptor::None | Interceptor::Tracer(_) => true,
157            Interceptor::Debugger(_) => false,
158        }
159    }
160
161    /// Returns true if leak detection should be skipped.
162    ///
163    /// Both debuggers and tracers skip leak detection to avoid interference.
164    pub fn should_skip_leak_detection(&self) -> bool {
165        match self {
166            Interceptor::None => false,
167            Interceptor::Debugger(_) | Interceptor::Tracer(_) => true,
168        }
169    }
170
171    /// Returns true if the test command should be displayed.
172    ///
173    /// Used to determine if we should print the wrapper command for debugging.
174    pub fn should_show_wrapper_command(&self) -> bool {
175        match self {
176            Interceptor::None => false,
177            Interceptor::Debugger(_) | Interceptor::Tracer(_) => true,
178        }
179    }
180
181    /// Returns true if, on receiving SIGTSTP, we should send SIGTSTP to the
182    /// child.
183    ///
184    /// Debugger mode has special signal handling where we don't send SIGTSTP to
185    /// the child (it receives it directly from the terminal, since no process
186    /// group is created). Tracers use standard signal handling.
187    pub fn should_send_sigtstp(&self) -> bool {
188        match self {
189            Interceptor::None | Interceptor::Tracer(_) => true,
190            Interceptor::Debugger(_) => false,
191        }
192    }
193}
194
195/// Version-related environment variables set for tests and setup scripts.
196///
197/// These expose the current nextest version and any version constraints from
198/// the repository's configuration.
199#[derive(Clone, Debug)]
200pub struct VersionEnvVars {
201    /// The current nextest version.
202    pub current_version: Version,
203
204    /// The required nextest version from configuration, if any.
205    pub required_version: Option<Version>,
206
207    /// The recommended nextest version from configuration, if any.
208    pub recommended_version: Option<Version>,
209}
210
211impl VersionEnvVars {
212    /// Applies the version environment variables to a command.
213    pub(crate) fn apply_env(&self, cmd: &mut std::process::Command) {
214        cmd.env("NEXTEST_VERSION", self.current_version.to_string());
215        cmd.env(
216            "NEXTEST_REQUIRED_VERSION",
217            match &self.required_version {
218                Some(v) => v.to_string(),
219                None => "none".to_owned(),
220            },
221        );
222        cmd.env(
223            "NEXTEST_RECOMMENDED_VERSION",
224            match &self.recommended_version {
225                Some(v) => v.to_string(),
226                None => "none".to_owned(),
227            },
228        );
229    }
230}
231
232/// A child process identifier: either a single process or a process group.
233#[derive(Copy, Clone, Debug)]
234pub(super) enum ChildPid {
235    /// A single process ID.
236    Process(#[cfg_attr(not(unix), expect(unused))] u32),
237
238    /// A process group ID.
239    #[cfg(unix)]
240    ProcessGroup(u32),
241}
242
243impl ChildPid {
244    /// Returns the PID value to use with `libc::kill`.
245    ///
246    /// - `Process(pid)` returns `pid as i32` (positive, kills single process).
247    /// - `ProcessGroup(pid)` returns `-(pid as i32)` (negative, kills process group).
248    ///
249    /// On Windows, always returns `pid as i32`.
250    #[cfg(unix)]
251    pub(super) fn for_kill(self) -> i32 {
252        match self {
253            ChildPid::Process(pid) => pid as i32,
254            ChildPid::ProcessGroup(pid) => -(pid as i32),
255        }
256    }
257}
258
259/// Test runner options.
260#[derive(Debug, Default)]
261pub struct TestRunnerBuilder {
262    capture_strategy: CaptureStrategy,
263    retries: Option<RetryPolicy>,
264    flaky_result: Option<FlakyResult>,
265    max_fail: Option<MaxFail>,
266    test_threads: Option<TestThreads>,
267    stress_condition: Option<StressCondition>,
268    interceptor: Interceptor,
269    expected_outstanding: Option<BTreeSet<OwnedTestInstanceId>>,
270}
271
272impl TestRunnerBuilder {
273    /// Sets the capture strategy for the test runner
274    ///
275    /// * [`CaptureStrategy::Split`]
276    ///   * pro: output from `stdout` and `stderr` can be identified and easily split
277    ///   * con: ordering between the streams cannot be guaranteed
278    /// * [`CaptureStrategy::Combined`]
279    ///   * pro: output is guaranteed to be ordered as it would in a terminal emulator
280    ///   * con: distinction between `stdout` and `stderr` is lost
281    /// * [`CaptureStrategy::None`] -
282    ///   * In this mode, tests will always be run serially: `test_threads` will always be 1.
283    pub fn set_capture_strategy(&mut self, strategy: CaptureStrategy) -> &mut Self {
284        self.capture_strategy = strategy;
285        self
286    }
287
288    /// Sets the number of retries for this test runner.
289    pub fn set_retries(&mut self, retries: RetryPolicy) -> &mut Self {
290        self.retries = Some(retries);
291        self
292    }
293
294    /// Sets the flaky result behavior for this test runner.
295    pub fn set_flaky_result(&mut self, flaky_result: FlakyResult) -> &mut Self {
296        self.flaky_result = Some(flaky_result);
297        self
298    }
299
300    /// Sets the max-fail value for this test runner.
301    pub fn set_max_fail(&mut self, max_fail: MaxFail) -> &mut Self {
302        self.max_fail = Some(max_fail);
303        self
304    }
305
306    /// Sets the number of tests to run simultaneously.
307    pub fn set_test_threads(&mut self, test_threads: TestThreads) -> &mut Self {
308        self.test_threads = Some(test_threads);
309        self
310    }
311
312    /// Sets the stress testing condition.
313    pub fn set_stress_condition(&mut self, stress_condition: StressCondition) -> &mut Self {
314        self.stress_condition = Some(stress_condition);
315        self
316    }
317
318    /// Sets the interceptor (debugger or tracer) to use for running tests.
319    pub fn set_interceptor(&mut self, interceptor: Interceptor) -> &mut Self {
320        self.interceptor = interceptor;
321        self
322    }
323
324    /// Sets the expected outstanding tests for rerun tracking.
325    ///
326    /// When set, the dispatcher will track which tests were seen during the run
327    /// and emit a `TestsNotSeen` as part of the `RunFinished` if some expected
328    /// tests were not seen.
329    pub fn set_expected_outstanding(
330        &mut self,
331        expected: BTreeSet<OwnedTestInstanceId>,
332    ) -> &mut Self {
333        self.expected_outstanding = Some(expected);
334        self
335    }
336
337    /// Creates a new test runner.
338    ///
339    /// `run_id` identifies this invocation. The caller should generate it via
340    /// [`force_or_new_run_id`](crate::helpers::force_or_new_run_id) and pass it
341    /// in.
342    #[expect(clippy::too_many_arguments)]
343    pub fn build<'a>(
344        self,
345        run_id: ReportUuid,
346        version_env_vars: VersionEnvVars,
347        test_list: &'a TestList,
348        profile: &'a EvaluatableProfile<'a>,
349        cli_args: Vec<String>,
350        signal_handler: SignalHandlerKind,
351        input_handler: InputHandlerKind,
352        double_spawn: DoubleSpawnInfo,
353        target_runner: TargetRunner,
354    ) -> Result<TestRunner<'a>, TestRunnerBuildError> {
355        let test_threads = match self.capture_strategy {
356            CaptureStrategy::None => 1,
357            CaptureStrategy::Combined | CaptureStrategy::Split => self
358                .test_threads
359                .unwrap_or_else(|| profile.test_threads())
360                .compute(),
361        };
362        let max_fail = self.max_fail.unwrap_or_else(|| profile.max_fail());
363
364        let runtime = tokio::runtime::Builder::new_multi_thread()
365            .enable_all()
366            .thread_name("nextest-runner-worker")
367            .build()
368            .map_err(TestRunnerBuildError::TokioRuntimeCreate)?;
369        let _guard = runtime.enter();
370
371        // signal_handler.build() must be called from within the guard.
372        let signal_handler = signal_handler.build()?;
373
374        let input_handler = input_handler.build();
375
376        Ok(TestRunner {
377            inner: TestRunnerInner {
378                run_id,
379                started_at: Local::now(),
380                profile,
381                test_list,
382                test_threads,
383                double_spawn,
384                target_runner,
385                capture_strategy: self.capture_strategy,
386                force_retries: self.retries,
387                force_flaky_result: self.flaky_result,
388                cli_args,
389                max_fail,
390                stress_condition: self.stress_condition,
391                interceptor: self.interceptor,
392                expected_outstanding: self.expected_outstanding,
393                version_env_vars,
394                runtime,
395            },
396            signal_handler,
397            input_handler,
398        })
399    }
400}
401
402/// Stress testing condition.
403#[derive(Clone, Debug)]
404pub enum StressCondition {
405    /// Run each test `count` times.
406    Count(StressCount),
407
408    /// Run until this duration has elapsed.
409    Duration(Duration),
410}
411
412/// A count for stress testing.
413#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
414#[serde(tag = "kind", rename_all = "kebab-case")]
415#[cfg_attr(test, derive(test_strategy::Arbitrary))]
416pub enum StressCount {
417    /// Run each test `count` times.
418    Count {
419        /// The number of times to run each test.
420        count: NonZero<u32>,
421    },
422
423    /// Run indefinitely.
424    Infinite,
425}
426
427impl FromStr for StressCount {
428    type Err = StressCountParseError;
429
430    fn from_str(s: &str) -> Result<Self, Self::Err> {
431        if s == "infinite" {
432            Ok(StressCount::Infinite)
433        } else {
434            match s.parse() {
435                Ok(count) => Ok(StressCount::Count { count }),
436                Err(_) => Err(StressCountParseError::new(s)),
437            }
438        }
439    }
440}
441
442/// Context for running tests.
443///
444/// Created using [`TestRunnerBuilder::build`].
445#[derive(Debug)]
446pub struct TestRunner<'a> {
447    inner: TestRunnerInner<'a>,
448    signal_handler: SignalHandler,
449    input_handler: InputHandler,
450}
451
452impl<'a> TestRunner<'a> {
453    /// Returns the unique ID for this test run.
454    pub fn run_id(&self) -> ReportUuid {
455        self.inner.run_id
456    }
457
458    /// Returns the timestamp when this test run was started.
459    pub fn started_at(&self) -> DateTime<Local> {
460        self.inner.started_at
461    }
462
463    /// Returns the status of the input handler.
464    pub fn input_handler_status(&self) -> InputHandlerStatus {
465        self.input_handler.status()
466    }
467
468    /// Executes the listed tests, each one in its own process.
469    ///
470    /// The callback is called with the results of each test.
471    ///
472    /// Returns the final result of the run. For stress runs, this accounts for
473    /// every sub-run, not just the last one.
474    ///
475    /// Returns an error if any of the tasks panicked.
476    pub fn execute<F>(
477        self,
478        mut callback: F,
479    ) -> Result<RunOutcome, TestRunnerExecuteErrors<Infallible>>
480    where
481        F: FnMut(ReporterEvent<'a>) + Send,
482    {
483        self.try_execute::<Infallible, _>(|event| {
484            callback(event);
485            Ok(())
486        })
487    }
488
489    /// Executes the listed tests, each one in its own process.
490    ///
491    /// Accepts a callback that is called with the results of each test. If the callback returns an
492    /// error, the test run terminates and the callback is no longer called.
493    ///
494    /// Returns the final result of the run. For stress runs, this accounts for
495    /// every sub-run, not just the last one.
496    ///
497    /// Returns an error if any of the tasks panicked.
498    pub fn try_execute<E, F>(
499        mut self,
500        mut callback: F,
501    ) -> Result<RunOutcome, TestRunnerExecuteErrors<E>>
502    where
503        F: FnMut(ReporterEvent<'a>) -> Result<(), E> + Send,
504        E: fmt::Debug + Send,
505    {
506        let (report_cancel_tx, report_cancel_rx) = oneshot::channel();
507
508        // If report_cancel_tx is None, at least one error has occurred and the
509        // runner has been instructed to shut down. first_error is also set to
510        // Some in that case.
511        let mut report_cancel_tx = Some(report_cancel_tx);
512        let mut first_error = None;
513
514        let res = self.inner.execute(
515            &mut self.signal_handler,
516            &mut self.input_handler,
517            report_cancel_rx,
518            |event| {
519                match callback(event) {
520                    Ok(()) => {}
521                    Err(error) => {
522                        // If the callback fails, we need to let the runner know to start shutting
523                        // down. But we keep reporting results in case the callback starts working
524                        // again.
525                        if let Some(report_cancel_tx) = report_cancel_tx.take() {
526                            let _ = report_cancel_tx.send(());
527                            first_error = Some(error);
528                        }
529                    }
530                }
531            },
532        );
533
534        // On Windows, the stdout and stderr futures might spawn processes that keep the runner
535        // stuck indefinitely if it's dropped the normal way. Shut it down aggressively, being OK
536        // with leaked resources.
537        self.inner.runtime.shutdown_background();
538
539        match (res, first_error) {
540            (Ok(outcome), None) => Ok(outcome),
541            (Ok(_), Some(report_error)) => Err(TestRunnerExecuteErrors {
542                report_error: Some(report_error),
543                join_errors: Vec::new(),
544            }),
545            (Err(join_errors), report_error) => Err(TestRunnerExecuteErrors {
546                report_error,
547                join_errors,
548            }),
549        }
550    }
551}
552
553#[derive(Debug)]
554struct TestRunnerInner<'a> {
555    run_id: ReportUuid,
556    started_at: DateTime<Local>,
557    profile: &'a EvaluatableProfile<'a>,
558    test_list: &'a TestList<'a>,
559    test_threads: usize,
560    double_spawn: DoubleSpawnInfo,
561    target_runner: TargetRunner,
562    capture_strategy: CaptureStrategy,
563    force_retries: Option<RetryPolicy>,
564    force_flaky_result: Option<FlakyResult>,
565    cli_args: Vec<String>,
566    max_fail: MaxFail,
567    stress_condition: Option<StressCondition>,
568    interceptor: Interceptor,
569    expected_outstanding: Option<BTreeSet<OwnedTestInstanceId>>,
570    version_env_vars: VersionEnvVars,
571    runtime: Runtime,
572}
573
574impl<'a> TestRunnerInner<'a> {
575    fn execute<F>(
576        &self,
577        signal_handler: &mut SignalHandler,
578        input_handler: &mut InputHandler,
579        report_cancel_rx: oneshot::Receiver<()>,
580        callback: F,
581    ) -> Result<RunOutcome, Vec<JoinError>>
582    where
583        F: FnMut(ReporterEvent<'a>) + Send,
584    {
585        // TODO: add support for other test-running approaches, measure performance.
586
587        // Disable the global timeout when an interceptor is active.
588        let global_timeout = if self.interceptor.should_disable_timeouts() {
589            crate::time::far_future_duration()
590        } else {
591            self.profile.global_timeout(self.test_list.mode()).period
592        };
593
594        let mut dispatcher_cx = DispatcherContext::new(
595            callback,
596            self.run_id,
597            self.profile.name(),
598            self.cli_args.clone(),
599            self.test_list.run_count(),
600            self.max_fail,
601            global_timeout,
602            self.stress_condition.clone(),
603            self.expected_outstanding.clone(),
604        );
605
606        let executor_cx = ExecutorContext::new(
607            self.run_id,
608            self.profile,
609            self.test_list,
610            self.test_threads,
611            self.double_spawn.clone(),
612            self.target_runner.clone(),
613            self.capture_strategy,
614            self.force_retries,
615            self.force_flaky_result,
616            self.interceptor.clone(),
617            self.version_env_vars.clone(),
618        );
619
620        // Send the initial event.
621        dispatcher_cx.run_started(self.test_list, self.test_threads);
622
623        let _guard = self.runtime.enter();
624
625        let mut report_cancel_rx = std::pin::pin!(report_cancel_rx.fuse());
626
627        if self.stress_condition.is_some() {
628            let mut progress = dispatcher_cx
629                .stress_progress()
630                .expect("stress_condition is Some => stress progress is Some");
631            loop {
632                dispatcher_cx.stress_sub_run_started(progress);
633
634                self.do_run(
635                    dispatcher_cx.stress_index(),
636                    &mut dispatcher_cx,
637                    &executor_cx,
638                    signal_handler,
639                    input_handler,
640                    report_cancel_rx.as_mut(),
641                )?;
642
643                // The action is decided after each sub-run because we want to
644                // make sure at least one sub-run occurs (i.e., something like
645                // do-while, not just a plain while).
646                match dispatcher_cx.stress_sub_run_finished() {
647                    StressLoopAction::Continue { next_progress } => progress = next_progress,
648                    StressLoopAction::Stop => break,
649                }
650            }
651        } else {
652            self.do_run(
653                None,
654                &mut dispatcher_cx,
655                &executor_cx,
656                signal_handler,
657                input_handler,
658                report_cancel_rx,
659            )?;
660        }
661
662        Ok(dispatcher_cx.run_finished())
663    }
664
665    fn do_run<F>(
666        &self,
667        stress_index: Option<StressIndex>,
668        dispatcher_cx: &mut DispatcherContext<'a, F>,
669        executor_cx: &ExecutorContext<'a>,
670        signal_handler: &mut SignalHandler,
671        input_handler: &mut InputHandler,
672        report_cancel_rx: Pin<&mut Fuse<oneshot::Receiver<()>>>,
673    ) -> Result<(), Vec<JoinError>>
674    where
675        F: FnMut(ReporterEvent<'a>) + Send,
676    {
677        let ((), results) = TokioScope::scope_and_block(move |scope| {
678            let (resp_tx, resp_rx) = unbounded_channel::<ExecutorEvent<'a>>();
679
680            // Run the dispatcher to completion in a task.
681            let dispatcher_fut =
682                dispatcher_cx.run(resp_rx, signal_handler, input_handler, report_cancel_rx);
683            scope.spawn_cancellable(dispatcher_fut, || RunnerTaskState::Cancelled);
684
685            let (script_tx, mut script_rx) = unbounded_channel::<SetupScriptExecuteData<'a>>();
686            let script_resp_tx = resp_tx.clone();
687            let run_scripts_fut = async move {
688                // Since script tasks are run serially, we just reuse the one
689                // script task.
690                let script_data = executor_cx
691                    .run_setup_scripts(stress_index, script_resp_tx)
692                    .await;
693                if script_tx.send(script_data).is_err() {
694                    // The dispatcher has shut down, so we should too.
695                    debug!("script_tx.send failed, shutting down");
696                }
697                RunnerTaskState::finished_no_children()
698            };
699            scope.spawn_cancellable(run_scripts_fut, || RunnerTaskState::Cancelled);
700
701            let Some(script_data) = script_rx.blocking_recv() else {
702                // Most likely the harness is shutting down, so we should too.
703                debug!("no script data received, shutting down");
704                return;
705            };
706
707            // groups is going to be passed to future_queue_grouped.
708            let groups = self
709                .profile
710                .test_group_config()
711                .iter()
712                .map(|(group_name, config)| (group_name, config.max_threads.compute()));
713
714            let setup_script_data = Arc::new(script_data);
715
716            let filter_resp_tx = resp_tx.clone();
717
718            let tests = self.test_list.to_priority_queue(self.profile);
719            let run_tests_fut = futures::stream::iter(tests)
720                .filter_map(move |test| {
721                    // Filter tests before assigning a FutureQueueContext to
722                    // them.
723                    //
724                    // Note that this function is called lazily due to the
725                    // `future_queue_grouped` below. This means that skip
726                    // notifications will go out as tests are iterated over, not
727                    // all at once.
728                    let filter_resp_tx = filter_resp_tx.clone();
729                    async move {
730                        if let FilterMatch::Mismatch { reason } =
731                            test.instance.test_info.filter_match
732                        {
733                            // Failure to send means the receiver was dropped.
734                            let _ = filter_resp_tx.send(ExecutorEvent::Skipped {
735                                stress_index,
736                                test_instance: test.instance,
737                                reason,
738                                junit_report_skipped: test.settings.junit_report_skipped(),
739                            });
740                            return None;
741                        }
742                        Some(test)
743                    }
744                })
745                .map(move |test: TestInstanceWithSettings<'a>| {
746                    let threads_required =
747                        test.settings.threads_required().compute(self.test_threads);
748                    let test_group = match test.settings.test_group() {
749                        TestGroup::Global => None,
750                        TestGroup::Custom(name) => Some(name.clone()),
751                    };
752                    let resp_tx = resp_tx.clone();
753                    let setup_script_data = setup_script_data.clone();
754
755                    let test_instance = test.instance;
756
757                    let f = move |cx: FutureQueueContext| {
758                        debug!("running test instance: {}; cx: {cx:?}", test_instance.id());
759                        // Use a separate Tokio task for each test. For repos
760                        // with lots of small tests, this has been observed to
761                        // be much faster than using a single task for all tests
762                        // (what we used to do). It also provides some degree of
763                        // per-test isolation.
764                        async move {
765                            // SAFETY: Within an outer scope_and_block (which we
766                            // have here), scope_and_collect is safe as long as
767                            // the returned future isn't forgotten. We're not
768                            // forgetting it below -- we're running it to
769                            // completion immediately.
770                            //
771                            // But recursive scoped calls really feel like
772                            // pushing against the limits of async-scoped. For
773                            // example, there's no way built into async-scoped
774                            // to propagate a cancellation signal from the outer
775                            // scope to the inner scope. (But there could be,
776                            // right? That seems solvable via channels. And we
777                            // could likely do our own channels here.)
778                            let ((), mut ret) = unsafe {
779                                TokioScope::scope_and_collect(move |scope| {
780                                    scope.spawn(executor_cx.run_test_instance(
781                                        stress_index,
782                                        test,
783                                        cx,
784                                        resp_tx.clone(),
785                                        setup_script_data,
786                                    ))
787                                })
788                            }
789                            .await;
790
791                            // If no future was started, that's really strange.
792                            // Worth at least logging.
793                            let Some(result) = ret.pop() else {
794                                warn!(
795                                    "no task was started for test instance: {}",
796                                    test_instance.id()
797                                );
798                                return None;
799                            };
800                            result.err()
801                        }
802                    };
803
804                    (threads_required, test_group, f)
805                })
806                // future_queue_grouped means tests are spawned in the order
807                // defined, but returned in any order.
808                .future_queue_grouped(self.test_threads, groups)
809                // Drop the None values.
810                .filter_map(std::future::ready)
811                .collect::<Vec<_>>()
812                // Interestingly, using a more idiomatic `async move {
813                // run_tests_fut.await ... }` block causes Rust 1.83 to complain
814                // about a weird lifetime mismatch. FutureExt::map as used below
815                // does not.
816                .map(|child_join_errors| RunnerTaskState::Finished { child_join_errors });
817
818            scope.spawn_cancellable(run_tests_fut, || RunnerTaskState::Cancelled);
819        });
820
821        // Were there any join errors in tasks?
822        //
823        // If one of the tasks panics, we likely end up stuck because the
824        // dispatcher, which is spawned in the same async-scoped block, doesn't
825        // get relayed the panic immediately. That should probably be fixed at
826        // some point.
827        let mut cancelled_count = 0;
828        let join_errors = results
829            .into_iter()
830            .flat_map(|r| {
831                match r {
832                    Ok(RunnerTaskState::Finished { child_join_errors }) => child_join_errors,
833                    // Largely ignore cancelled tasks since it most likely means
834                    // shutdown -- we don't cancel tasks manually.
835                    Ok(RunnerTaskState::Cancelled) => {
836                        cancelled_count += 1;
837                        Vec::new()
838                    }
839                    Err(join_error) => vec![join_error],
840                }
841            })
842            .collect::<Vec<_>>();
843
844        if cancelled_count > 0 {
845            debug!(
846                "{} tasks were cancelled -- this \
847                 generally should only happen due to panics",
848                cancelled_count
849            );
850        }
851        if !join_errors.is_empty() {
852            return Err(join_errors);
853        }
854
855        Ok(())
856    }
857}
858
859/// Configures stdout, stdin and stderr inheritance by test processes on Windows.
860///
861/// With Rust on Windows, these handles can be held open by tests (and therefore by grandchild processes)
862/// even if we run the tests with `Stdio::inherit`. This can cause problems with leaky tests.
863///
864/// This changes global state on the Win32 side, so the application must manage mutual exclusion
865/// around it. Call this right before [`TestRunner::try_execute`].
866///
867/// This is a no-op on non-Windows platforms.
868///
869/// See [this issue on the Rust repository](https://github.com/rust-lang/rust/issues/54760) for more
870/// discussion.
871pub fn configure_handle_inheritance(
872    no_capture: bool,
873) -> Result<(), ConfigureHandleInheritanceError> {
874    super::os::configure_handle_inheritance_impl(no_capture)
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880    use crate::{config::core::NextestConfig, platform::BuildPlatforms};
881
882    #[test]
883    fn no_capture_settings() {
884        // Ensure that output settings are ignored with no-capture.
885        let mut builder = TestRunnerBuilder::default();
886        builder
887            .set_capture_strategy(CaptureStrategy::None)
888            .set_test_threads(TestThreads::Count(20));
889        let test_list = TestList::empty();
890        let config = NextestConfig::default_config("/fake/dir");
891        let profile = config.profile(NextestConfig::DEFAULT_PROFILE).unwrap();
892        let build_platforms = BuildPlatforms::new_with_no_target().unwrap();
893        let signal_handler = SignalHandlerKind::Noop;
894        let input_handler = InputHandlerKind::Noop;
895        let profile = profile.apply_build_platforms(&build_platforms);
896        let version_env_vars = VersionEnvVars {
897            current_version: Version::new(0, 0, 0),
898            required_version: None,
899            recommended_version: None,
900        };
901        let runner = builder
902            .build(
903                crate::helpers::force_or_new_run_id(),
904                version_env_vars,
905                &test_list,
906                &profile,
907                vec![],
908                signal_handler,
909                input_handler,
910                DoubleSpawnInfo::disabled(),
911                TargetRunner::empty(),
912            )
913            .unwrap();
914        assert_eq!(runner.inner.capture_strategy, CaptureStrategy::None);
915        assert_eq!(runner.inner.test_threads, 1, "tests run serially");
916    }
917
918    #[test]
919    fn test_debugger_command_parsing() {
920        // Valid commands
921        let cmd = DebuggerCommand::from_str("gdb --args").unwrap();
922        assert_eq!(cmd.program(), "gdb");
923        assert_eq!(cmd.args(), &["--args"]);
924
925        let cmd = DebuggerCommand::from_str("rust-gdb -ex run --args").unwrap();
926        assert_eq!(cmd.program(), "rust-gdb");
927        assert_eq!(cmd.args(), &["-ex", "run", "--args"]);
928
929        // With quotes
930        let cmd = DebuggerCommand::from_str(r#"gdb -ex "set print pretty on" --args"#).unwrap();
931        assert_eq!(cmd.program(), "gdb");
932        assert_eq!(cmd.args(), &["-ex", "set print pretty on", "--args"]);
933
934        // Empty command
935        let err = DebuggerCommand::from_str("").unwrap_err();
936        assert!(matches!(err, DebuggerCommandParseError::EmptyCommand));
937
938        // Whitespace only
939        let err = DebuggerCommand::from_str("   ").unwrap_err();
940        assert!(matches!(err, DebuggerCommandParseError::EmptyCommand));
941    }
942}