nextest_runner/reporter/displayer/
imp.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Prints out and aggregates test execution statuses.
5//!
6//! The main structure in this module is [`TestReporter`].
7
8use super::{
9    ChildOutputSpec, FinalStatusLevel, OutputStoreFinal, StatusLevel, StatusLevels,
10    UnitOutputReporter,
11    config::{DisplayConfig, ProgressDisplay},
12    formatters::{
13        DisplayBracketedDuration, DisplayDurationBy, DisplaySlowDuration, DisplayUnitKind,
14        write_final_warnings, write_skip_counts,
15    },
16    progress::{
17        MaxProgressRunning, ProgressBarState, progress_bar_msg, progress_str, write_summary_str,
18    },
19    unit_output::{OutputDisplayOverrides, TestOutputDisplay},
20};
21use crate::{
22    config::{
23        elements::{LeakTimeoutResult, SlowTimeoutResult},
24        overrides::CompiledDefaultFilter,
25        scripts::ScriptId,
26    },
27    errors::WriteEventError,
28    helpers::{
29        DisplayCounterIndex, DisplayScriptInstance, DisplayTestInstance, ThemeCharacters, plural,
30        usize_decimal_char_width,
31    },
32    indenter::indented,
33    list::TestInstanceId,
34    output_spec::{LiveSpec, RecordingSpec},
35    record::{LoadOutput, OutputEventKind, ReplayHeader, ShortestRunIdPrefix},
36    reporter::{
37        displayer::{
38            formatters::DisplayHhMmSs,
39            progress::{ShowTerminalProgress, TerminalProgress},
40        },
41        events::*,
42        helpers::Styles,
43        imp::ReporterOutput,
44    },
45    run_mode::NextestRunMode,
46    runner::StressCount,
47    write_str::WriteStr,
48};
49use debug_ignore::DebugIgnore;
50use nextest_metadata::MismatchReason;
51use owo_colors::OwoColorize;
52use std::{
53    borrow::Cow,
54    cmp::{Ordering, Reverse},
55    io::{self, BufWriter, IsTerminal, Write},
56    time::Duration,
57};
58
59/// The kind of displayer being used.
60#[derive(Copy, Clone, Debug, Eq, PartialEq)]
61pub(crate) enum DisplayerKind {
62    /// The displayer is showing output from a live test run.
63    Live,
64
65    /// The displayer is showing output from a replay of a recorded run.
66    ///
67    /// In replay mode, if output was not captured during the original run, a
68    /// helpful message is displayed to indicate this.
69    Replay,
70}
71
72pub(crate) struct DisplayReporterBuilder {
73    pub(crate) mode: NextestRunMode,
74    pub(crate) default_filter: CompiledDefaultFilter,
75    pub(crate) display_config: DisplayConfig,
76    pub(crate) run_count: usize,
77    pub(crate) success_output: Option<TestOutputDisplay>,
78    pub(crate) failure_output: Option<TestOutputDisplay>,
79    pub(crate) should_colorize: bool,
80    pub(crate) verbose: bool,
81    pub(crate) no_output_indent: bool,
82    pub(crate) max_progress_running: MaxProgressRunning,
83    pub(crate) show_term_progress: ShowTerminalProgress,
84    pub(crate) displayer_kind: DisplayerKind,
85}
86
87impl DisplayReporterBuilder {
88    pub(crate) fn build<'a>(self, output: ReporterOutput<'a>) -> DisplayReporter<'a> {
89        let mut styles: Box<Styles> = Box::default();
90        if self.should_colorize {
91            styles.colorize();
92        }
93
94        let mut theme_characters = ThemeCharacters::default();
95        match &output {
96            ReporterOutput::Terminal => {
97                if supports_unicode::on(supports_unicode::Stream::Stderr) {
98                    theme_characters.use_unicode();
99                }
100            }
101            ReporterOutput::Writer { use_unicode, .. } => {
102                if *use_unicode {
103                    theme_characters.use_unicode();
104                }
105            }
106        }
107
108        let is_terminal = matches!(&output, ReporterOutput::Terminal) && io::stderr().is_terminal();
109        let is_ci = is_ci::uncached();
110
111        let resolved = self.display_config.resolve(is_terminal, is_ci);
112
113        let output = match output {
114            ReporterOutput::Terminal => {
115                let progress_bar = if resolved.progress_display == ProgressDisplay::Bar {
116                    Some(ProgressBarState::new(
117                        self.mode,
118                        self.run_count,
119                        theme_characters.progress_chars(),
120                        self.max_progress_running,
121                    ))
122                } else {
123                    None
124                };
125                let term_progress = TerminalProgress::new(self.show_term_progress);
126                ReporterOutputImpl::Terminal {
127                    progress_bar: progress_bar.map(Box::new),
128                    term_progress,
129                }
130            }
131            ReporterOutput::Writer { writer, .. } => ReporterOutputImpl::Writer(writer),
132        };
133
134        let no_capture = self.display_config.no_capture;
135
136        // success_output is meaningless if the runner isn't capturing any
137        // output. However, failure output is still meaningful for exec fail
138        // events.
139        let overrides = OutputDisplayOverrides {
140            force_success_output: match no_capture {
141                true => Some(TestOutputDisplay::Never),
142                false => self.success_output,
143            },
144            force_failure_output: match no_capture {
145                true => Some(TestOutputDisplay::Never),
146                false => self.failure_output,
147            },
148            force_exec_fail_output: match no_capture {
149                true => Some(TestOutputDisplay::Immediate),
150                false => self.failure_output,
151            },
152        };
153
154        let counter_width = matches!(resolved.progress_display, ProgressDisplay::Counter)
155            .then_some(usize_decimal_char_width(self.run_count));
156
157        DisplayReporter {
158            inner: DisplayReporterImpl {
159                mode: self.mode,
160                default_filter: self.default_filter,
161                status_levels: resolved.status_levels,
162                no_capture,
163                verbose: self.verbose,
164                no_output_indent: self.no_output_indent,
165                counter_width,
166                styles,
167                theme_characters,
168                cancel_status: None,
169                unit_output: UnitOutputReporter::new(overrides, self.displayer_kind),
170                final_outputs: DebugIgnore(Vec::new()),
171                run_id_unique_prefix: None,
172            },
173            output,
174        }
175    }
176}
177
178/// Functionality to report test results to stderr, JUnit, and/or structured,
179/// machine-readable results to stdout.
180pub(crate) struct DisplayReporter<'a> {
181    inner: DisplayReporterImpl<'a>,
182    output: ReporterOutputImpl<'a>,
183}
184
185impl<'a> DisplayReporter<'a> {
186    pub(crate) fn tick(&mut self) {
187        self.output.tick(&self.inner.styles);
188    }
189
190    pub(crate) fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
191        match &mut self.output {
192            ReporterOutputImpl::Terminal {
193                progress_bar,
194                term_progress,
195            } => {
196                if let Some(term_progress) = term_progress {
197                    term_progress.update_progress(event);
198                }
199
200                if let Some(state) = progress_bar {
201                    // Write to a string that will be printed as a log line.
202                    let mut buf = String::new();
203                    self.inner
204                        .write_event_impl(event, &mut buf)
205                        .map_err(WriteEventError::Io)?;
206
207                    state.update_progress_bar(event, &self.inner.styles);
208                    state.write_buf(&buf);
209                    Ok(())
210                } else {
211                    // Write to a buffered stderr.
212                    let mut writer = BufWriter::new(std::io::stderr());
213                    self.inner
214                        .write_event_impl(event, &mut writer)
215                        .map_err(WriteEventError::Io)?;
216                    writer.flush().map_err(WriteEventError::Io)
217                }
218            }
219            ReporterOutputImpl::Writer(writer) => {
220                self.inner
221                    .write_event_impl(event, *writer)
222                    .map_err(WriteEventError::Io)?;
223                writer.write_str_flush().map_err(WriteEventError::Io)
224            }
225        }
226    }
227
228    pub(crate) fn finish(&mut self) {
229        self.output.finish_and_clear_bar();
230    }
231
232    /// Sets the unique prefix for the run ID.
233    ///
234    /// This is used to highlight the unique prefix portion of the run ID
235    /// in the `RunStarted` output when a recording session is active.
236    pub(crate) fn set_run_id_unique_prefix(&mut self, prefix: ShortestRunIdPrefix) {
237        self.inner.run_id_unique_prefix = Some(prefix);
238    }
239
240    /// Writes a replay header to the output.
241    ///
242    /// This is used by `ReplayReporter` to display replay-specific information
243    /// before processing recorded events.
244    pub(crate) fn write_replay_header(
245        &mut self,
246        header: &ReplayHeader,
247    ) -> Result<(), WriteEventError> {
248        self.write_impl(|writer, styles, _theme_chars| {
249            // Write "Replaying" line with unique prefix highlighting.
250            write!(writer, "{:>12} ", "Replaying".style(styles.pass))?;
251            let run_id_display = if let Some(prefix_info) = &header.unique_prefix {
252                // Highlight the unique prefix portion of the full run ID.
253                format!(
254                    "{}{}",
255                    prefix_info.prefix.style(styles.run_id_prefix),
256                    prefix_info.rest.style(styles.run_id_rest),
257                )
258            } else {
259                // No prefix info available, show the full ID without highlighting.
260                header.run_id.to_string().style(styles.count).to_string()
261            };
262            writeln!(writer, "recorded run {}", run_id_display)?;
263
264            // Write "Started" line with status.
265            let status_str = header.status.short_status_str();
266            write!(writer, "{:>12} ", "Started".style(styles.pass))?;
267            writeln!(
268                writer,
269                "{}  status: {}",
270                header.started_at.format("%Y-%m-%d %H:%M:%S"),
271                status_str.style(styles.count)
272            )?;
273
274            Ok(())
275        })
276    }
277
278    /// Returns an [`OutputLoadDecider`] for this reporter.
279    ///
280    /// The decider examines event metadata and the reporter's display
281    /// configuration to decide whether output should be loaded from the
282    /// archive during replay.
283    pub(crate) fn output_load_decider(&self) -> OutputLoadDecider {
284        OutputLoadDecider {
285            status_level: self.inner.status_levels.status_level,
286            overrides: self.inner.unit_output.overrides(),
287        }
288    }
289
290    /// Internal helper for writing through the output with access to styles.
291    fn write_impl<F>(&mut self, f: F) -> Result<(), WriteEventError>
292    where
293        F: FnOnce(&mut dyn WriteStr, &Styles, &ThemeCharacters) -> io::Result<()>,
294    {
295        match &mut self.output {
296            ReporterOutputImpl::Terminal { progress_bar, .. } => {
297                if let Some(state) = progress_bar {
298                    // Write to a string that will be printed as a log line.
299                    let mut buf = String::new();
300                    f(&mut buf, &self.inner.styles, &self.inner.theme_characters)
301                        .map_err(WriteEventError::Io)?;
302                    state.write_buf(&buf);
303                    Ok(())
304                } else {
305                    // Write to a buffered stderr.
306                    let mut writer = BufWriter::new(std::io::stderr());
307                    f(
308                        &mut writer,
309                        &self.inner.styles,
310                        &self.inner.theme_characters,
311                    )
312                    .map_err(WriteEventError::Io)?;
313                    writer.flush().map_err(WriteEventError::Io)
314                }
315            }
316            ReporterOutputImpl::Writer(writer) => {
317                f(*writer, &self.inner.styles, &self.inner.theme_characters)
318                    .map_err(WriteEventError::Io)?;
319                writer.write_str_flush().map_err(WriteEventError::Io)
320            }
321        }
322    }
323}
324
325/// Configuration needed to decide whether to load output during replay.
326///
327/// This captures the reporter's display configuration so the replay loop can
328/// skip decompressing output from the archive when it will never be shown. The
329/// decision is conservative: `LoadOutput::Load` is returned whenever there is
330/// any chance the output will be displayed, either immediately or at the end of
331/// the run.
332///
333/// Currently, replays only use a display reporter, and do not use JUnit or
334/// libtest reporters. If and when support for those is added to replay, this
335/// decider must be updated to account for their output requirements as well.
336#[derive(Debug)]
337pub struct OutputLoadDecider {
338    pub(super) status_level: StatusLevel,
339    pub(super) overrides: OutputDisplayOverrides,
340}
341
342impl OutputLoadDecider {
343    /// Decides whether output should be loaded for a given output event.
344    pub fn should_load_output(&self, kind: &OutputEventKind<RecordingSpec>) -> LoadOutput {
345        match kind {
346            OutputEventKind::SetupScriptFinished { run_status, .. } => {
347                Self::should_load_for_setup_script(&run_status.result)
348            }
349            OutputEventKind::TestAttemptFailedWillRetry { failure_output, .. } => {
350                let display = self.overrides.failure_output(*failure_output);
351                Self::should_load_for_retry(display, self.status_level)
352            }
353            OutputEventKind::TestFinished {
354                success_output,
355                failure_output,
356                run_statuses,
357                ..
358            } => self.should_load_for_test_finished(*success_output, *failure_output, run_statuses),
359        }
360    }
361
362    pub(super) fn should_load_for_test_finished(
363        &self,
364        success_output: TestOutputDisplay,
365        failure_output: TestOutputDisplay,
366        run_statuses: &ExecutionStatuses<RecordingSpec>,
367    ) -> LoadOutput {
368        let describe = run_statuses.describe();
369        let last_status = describe.last_status();
370
371        // Determine which display setting applies based on the result.
372        let display = self.overrides.resolve_test_output_display(
373            success_output,
374            failure_output,
375            &last_status.result,
376        );
377
378        Self::should_load_for_display(display)
379    }
380
381    /// Core decision logic for whether to load output for a setup script.
382    ///
383    /// The displayer always shows output for failing setup scripts and
384    /// never for successful ones.
385    ///
386    /// This method is factored out for testing.
387    pub(super) fn should_load_for_setup_script(result: &ExecutionResultDescription) -> LoadOutput {
388        // The displayer always shows output for failing setup scripts.
389        if result.is_success() {
390            LoadOutput::Skip
391        } else {
392            LoadOutput::Load
393        }
394    }
395
396    /// Core decision logic for whether to load output for a retry attempt.
397    ///
398    /// The displayer shows retry output iff `status_level >= Retry` and
399    /// the resolved failure output is immediate.
400    ///
401    /// This method is factored out for testing.
402    pub(super) fn should_load_for_retry(
403        display: TestOutputDisplay,
404        status_level: StatusLevel,
405    ) -> LoadOutput {
406        if display.is_immediate() && status_level >= StatusLevel::Retry {
407            LoadOutput::Load
408        } else {
409            LoadOutput::Skip
410        }
411    }
412
413    /// Core decision logic for whether to load output for a finished test.
414    ///
415    /// This method is factored out for testing.
416    pub(super) fn should_load_for_display(display: TestOutputDisplay) -> LoadOutput {
417        let is_immediate = display.is_immediate();
418        let is_final = display.is_final();
419
420        // We ignore cancel_status because we cannot know it without tracking it
421        // ourselves, and cancellation only hides output, never shows more.
422        // (This is verified by the cancellation_only_hides_output test).
423        if is_immediate || is_final {
424            LoadOutput::Load
425        } else {
426            LoadOutput::Skip
427        }
428    }
429}
430
431enum ReporterOutputImpl<'a> {
432    Terminal {
433        // Reporter-specific progress bar state. None if the progress bar is not
434        // enabled (which can include the terminal not being a TTY).
435        progress_bar: Option<Box<ProgressBarState>>,
436        // OSC 9 code progress reporting.
437        term_progress: Option<TerminalProgress>,
438    },
439    Writer(&'a mut (dyn WriteStr + Send)),
440}
441
442impl ReporterOutputImpl<'_> {
443    fn tick(&mut self, styles: &Styles) {
444        match self {
445            ReporterOutputImpl::Terminal {
446                progress_bar,
447                term_progress,
448            } => {
449                if let Some(state) = progress_bar {
450                    state.tick(styles);
451                }
452                if let Some(term_progress) = term_progress {
453                    // In this case, write the last value directly to stderr.
454                    // This is a very small amount of data so buffering is not
455                    // required. It also doesn't have newlines or any visible
456                    // text, so it can be directly written out to stderr without
457                    // going through the progress bar (which screws up
458                    // indicatif's calculations).
459                    eprint!("{}", term_progress.last_value())
460                }
461            }
462            ReporterOutputImpl::Writer(_) => {
463                // No ticking for writers.
464            }
465        }
466    }
467
468    fn finish_and_clear_bar(&self) {
469        match self {
470            ReporterOutputImpl::Terminal {
471                progress_bar,
472                term_progress,
473            } => {
474                if let Some(state) = progress_bar {
475                    state.finish_and_clear();
476                }
477                if let Some(term_progress) = term_progress {
478                    // The last value is expected to be Remove.
479                    eprint!("{}", term_progress.last_value())
480                }
481            }
482            ReporterOutputImpl::Writer(_) => {
483                // No progress bar to clear.
484            }
485        }
486    }
487
488    #[cfg(test)]
489    fn writer_mut(&mut self) -> Option<&mut (dyn WriteStr + Send)> {
490        match self {
491            Self::Writer(writer) => Some(*writer),
492            _ => None,
493        }
494    }
495}
496
497#[derive(Debug)]
498enum FinalOutput {
499    Skipped(#[expect(dead_code)] MismatchReason),
500    Executed {
501        run_statuses: ExecutionStatuses<LiveSpec>,
502        display_output: bool,
503    },
504}
505
506impl FinalOutput {
507    fn final_status_level(&self) -> FinalStatusLevel {
508        match self {
509            Self::Skipped(_) => FinalStatusLevel::Skip,
510            Self::Executed { run_statuses, .. } => run_statuses.describe().final_status_level(),
511        }
512    }
513}
514
515struct FinalOutputEntry<'a> {
516    stress_index: Option<StressIndex>,
517    counter: TestInstanceCounter,
518    instance: TestInstanceId<'a>,
519    output: FinalOutput,
520}
521
522/// Sort final output entries for display.
523///
524/// The sort key is:
525///
526/// 1. Final status level (reversed, so that failing tests are printed last).
527/// 2. Stress index.
528/// 3. Counter, but only when the counter is displayed.
529/// 4. Test instance.
530///
531/// Note that the counter comparison only matters between entries with the same
532/// status level. In practice, `TestInstanceCounter::Padded` is only used for
533/// `FinalOutput::Skipped` entries (which have `FinalStatusLevel::Skip`), while
534/// `TestInstanceCounter::Counter` is only used for `FinalOutput::Executed`
535/// entries (which never have `FinalStatusLevel::Skip`). So `Padded` and
536/// `Counter` are never compared with each other.
537fn sort_final_outputs(entries: &mut [FinalOutputEntry<'_>], include_counter: bool) {
538    entries.sort_unstable_by(|a, b| {
539        Reverse(a.output.final_status_level())
540            .cmp(&Reverse(b.output.final_status_level()))
541            .then_with(|| a.stress_index.cmp(&b.stress_index))
542            .then_with(|| {
543                if include_counter {
544                    a.counter.cmp(&b.counter)
545                } else {
546                    Ordering::Equal
547                }
548            })
549            .then_with(|| a.instance.cmp(&b.instance))
550    });
551}
552
553struct DisplayReporterImpl<'a> {
554    mode: NextestRunMode,
555    default_filter: CompiledDefaultFilter,
556    status_levels: StatusLevels,
557    no_capture: bool,
558    verbose: bool,
559    no_output_indent: bool,
560    // None if no counter is displayed. If a counter is displayed, this is the
561    // width of the total number of tests to run.
562    counter_width: Option<usize>,
563    styles: Box<Styles>,
564    theme_characters: ThemeCharacters,
565    cancel_status: Option<CancelReason>,
566    unit_output: UnitOutputReporter,
567    final_outputs: DebugIgnore<Vec<FinalOutputEntry<'a>>>,
568    // The unique prefix for the current run ID, if a recording session is active.
569    // Used for highlighting the run ID in RunStarted output.
570    run_id_unique_prefix: Option<ShortestRunIdPrefix>,
571}
572
573impl<'a> DisplayReporterImpl<'a> {
574    fn write_event_impl(
575        &mut self,
576        event: &TestEvent<'a>,
577        writer: &mut dyn WriteStr,
578    ) -> io::Result<()> {
579        match &event.kind {
580            TestEventKind::RunStarted {
581                test_list,
582                run_id,
583                profile_name,
584                cli_args: _,
585                stress_condition: _,
586            } => {
587                writeln!(writer, "{}", self.theme_characters.hbar(12))?;
588                write!(writer, "{:>12} ", "Nextest run".style(self.styles.pass))?;
589
590                // Display the run ID with unique prefix highlighting if a recording
591                // session is active, otherwise use plain styling.
592                let run_id_display = if let Some(prefix_info) = &self.run_id_unique_prefix {
593                    format!(
594                        "{}{}",
595                        prefix_info.prefix.style(self.styles.run_id_prefix),
596                        prefix_info.rest.style(self.styles.run_id_rest),
597                    )
598                } else {
599                    run_id.style(self.styles.count).to_string()
600                };
601
602                writeln!(
603                    writer,
604                    "ID {} with nextest profile: {}",
605                    run_id_display,
606                    profile_name.style(self.styles.count),
607                )?;
608
609                write!(writer, "{:>12} ", "Starting".style(self.styles.pass))?;
610
611                let count_style = self.styles.count;
612
613                let tests_str = plural::tests_str(self.mode, test_list.run_count());
614                let binaries_str = plural::binaries_str(test_list.listed_binary_count());
615
616                write!(
617                    writer,
618                    "{} {tests_str} across {} {binaries_str}",
619                    test_list.run_count().style(count_style),
620                    test_list.listed_binary_count().style(count_style),
621                )?;
622
623                write_skip_counts(
624                    self.mode,
625                    test_list.skip_counts(),
626                    &self.default_filter,
627                    &self.styles,
628                    writer,
629                )?;
630
631                writeln!(writer)?;
632            }
633            TestEventKind::StressSubRunStarted { progress } => {
634                write!(
635                    writer,
636                    "{}\n{:>12} ",
637                    self.theme_characters.hbar(12),
638                    "Stress test".style(self.styles.pass)
639                )?;
640
641                match progress {
642                    StressProgress::Count {
643                        total: StressCount::Count { count },
644                        elapsed,
645                        completed,
646                    } => {
647                        write!(
648                            writer,
649                            "iteration {}/{} ({} elapsed so far",
650                            (completed + 1).style(self.styles.count),
651                            count.style(self.styles.count),
652                            DisplayHhMmSs {
653                                duration: *elapsed,
654                                floor: true,
655                            }
656                            .style(self.styles.count),
657                        )?;
658                    }
659                    StressProgress::Count {
660                        total: StressCount::Infinite,
661                        elapsed,
662                        completed,
663                    } => {
664                        write!(
665                            writer,
666                            "iteration {} ({} elapsed so far",
667                            (completed + 1).style(self.styles.count),
668                            DisplayHhMmSs {
669                                duration: *elapsed,
670                                floor: true,
671                            }
672                            .style(self.styles.count),
673                        )?;
674                    }
675                    StressProgress::Time {
676                        total,
677                        elapsed,
678                        completed,
679                    } => {
680                        write!(
681                            writer,
682                            "iteration {} ({}/{} elapsed so far",
683                            (completed + 1).style(self.styles.count),
684                            DisplayHhMmSs {
685                                duration: *elapsed,
686                                floor: true,
687                            }
688                            .style(self.styles.count),
689                            DisplayHhMmSs {
690                                duration: *total,
691                                floor: true,
692                            }
693                            .style(self.styles.count),
694                        )?;
695                    }
696                }
697
698                if let Some(remaining) = progress.remaining() {
699                    match remaining {
700                        StressRemaining::Count(n) => {
701                            write!(
702                                writer,
703                                ", {} {} remaining",
704                                n.style(self.styles.count),
705                                plural::iterations_str(n.get()),
706                            )?;
707                        }
708                        StressRemaining::Infinite => {
709                            // There isn't anything to display here.
710                        }
711                        StressRemaining::Time(t) => {
712                            write!(
713                                writer,
714                                ", {} remaining",
715                                DisplayHhMmSs {
716                                    duration: t,
717                                    // Display the remaining time as a ceiling
718                                    // so that we show something like:
719                                    //
720                                    // 00:02:05/00:30:00 elapsed so far, 00:27:55 remaining
721                                    //
722                                    // rather than
723                                    //
724                                    // 00:02:05/00:30:00 elapsed so far, 00:27:54 remaining
725                                    floor: false,
726                                }
727                                .style(self.styles.count)
728                            )?;
729                        }
730                    }
731                }
732
733                writeln!(writer, ")")?;
734            }
735            TestEventKind::SetupScriptStarted {
736                stress_index,
737                index,
738                total,
739                script_id,
740                program,
741                args,
742                ..
743            } => {
744                writeln!(
745                    writer,
746                    "{:>12} [{:>9}] {}",
747                    "SETUP".style(self.styles.pass),
748                    // index + 1 so that it displays as e.g. "1/2" and "2/2".
749                    format!("{}/{}", index + 1, total),
750                    self.display_script_instance(*stress_index, script_id.clone(), program, args)
751                )?;
752            }
753            TestEventKind::SetupScriptSlow {
754                stress_index,
755                script_id,
756                program,
757                args,
758                elapsed,
759                will_terminate,
760            } => {
761                if !*will_terminate && self.status_levels.status_level >= StatusLevel::Slow {
762                    write!(writer, "{:>12} ", "SETUP SLOW".style(self.styles.skip))?;
763                } else if *will_terminate {
764                    write!(writer, "{:>12} ", "TERMINATING".style(self.styles.fail))?;
765                }
766
767                writeln!(
768                    writer,
769                    "{}{}",
770                    DisplaySlowDuration(*elapsed),
771                    self.display_script_instance(*stress_index, script_id.clone(), program, args)
772                )?;
773            }
774            TestEventKind::SetupScriptFinished {
775                stress_index,
776                script_id,
777                program,
778                args,
779                run_status,
780                ..
781            } => {
782                self.write_setup_script_status_line(
783                    *stress_index,
784                    script_id,
785                    program,
786                    args,
787                    run_status,
788                    writer,
789                )?;
790                // Always display failing setup script output if it exists. We
791                // may change this in the future.
792                if !run_status.result.is_success() {
793                    self.write_setup_script_execute_status(run_status, writer)?;
794                }
795            }
796            TestEventKind::TestStarted {
797                stress_index,
798                test_instance,
799                current_stats,
800                command_line,
801                ..
802            } => {
803                // In no-capture and verbose modes, print out a test start
804                // event.
805                if self.no_capture || self.verbose {
806                    // The spacing is to align test instances.
807                    writeln!(
808                        writer,
809                        "{:>12} [         ] {}",
810                        "START".style(self.styles.pass),
811                        self.display_test_instance(
812                            *stress_index,
813                            TestInstanceCounter::Counter {
814                                // --no-capture implies tests being run
815                                // serially, so the current test is the number
816                                // of finished tests plus one.
817                                current: current_stats.finished_count + 1,
818                                total: current_stats.initial_run_count,
819                            },
820                            *test_instance
821                        ),
822                    )?;
823                }
824
825                if self.verbose {
826                    self.write_command_line(command_line, writer)?;
827                }
828            }
829            TestEventKind::TestSlow {
830                stress_index,
831                test_instance,
832                retry_data,
833                elapsed,
834                will_terminate,
835            } => {
836                if !*will_terminate && self.status_levels.status_level >= StatusLevel::Slow {
837                    if retry_data.total_attempts > 1 {
838                        write!(
839                            writer,
840                            "{:>12} ",
841                            format!("TRY {} SLOW", retry_data.attempt).style(self.styles.skip)
842                        )?;
843                    } else {
844                        write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
845                    }
846                } else if *will_terminate {
847                    let (required_status_level, style) = if retry_data.is_last_attempt() {
848                        (StatusLevel::Fail, self.styles.fail)
849                    } else {
850                        (StatusLevel::Retry, self.styles.retry)
851                    };
852                    if retry_data.total_attempts > 1
853                        && self.status_levels.status_level > required_status_level
854                    {
855                        write!(
856                            writer,
857                            "{:>12} ",
858                            format!("TRY {} TRMNTG", retry_data.attempt).style(style)
859                        )?;
860                    } else {
861                        write!(writer, "{:>12} ", "TERMINATING".style(style))?;
862                    };
863                }
864
865                writeln!(
866                    writer,
867                    "{}{}",
868                    DisplaySlowDuration(*elapsed),
869                    self.display_test_instance(
870                        *stress_index,
871                        TestInstanceCounter::Padded,
872                        *test_instance
873                    )
874                )?;
875            }
876
877            TestEventKind::TestAttemptFailedWillRetry {
878                stress_index,
879                test_instance,
880                run_status,
881                delay_before_next_attempt,
882                failure_output,
883                running: _,
884            } => {
885                if self.status_levels.status_level >= StatusLevel::Retry {
886                    let try_status_string = format!(
887                        "TRY {} {}",
888                        run_status.retry_data.attempt,
889                        short_status_str(&run_status.result),
890                    );
891
892                    // Print the try status and time taken.
893                    write!(
894                        writer,
895                        "{:>12} {}",
896                        try_status_string.style(self.styles.retry),
897                        DisplayBracketedDuration(run_status.time_taken),
898                    )?;
899
900                    // Print the name of the test.
901                    writeln!(
902                        writer,
903                        "{}",
904                        self.display_test_instance(
905                            *stress_index,
906                            TestInstanceCounter::Padded,
907                            *test_instance
908                        )
909                    )?;
910
911                    // This test is guaranteed to have failed.
912                    assert!(
913                        !run_status.result.is_success(),
914                        "only failing tests are retried"
915                    );
916                    if self
917                        .unit_output
918                        .overrides()
919                        .failure_output(*failure_output)
920                        .is_immediate()
921                    {
922                        self.write_test_execute_status(run_status, true, writer)?;
923                    }
924
925                    // The final output doesn't show retries, so don't store this result in
926                    // final_outputs.
927
928                    if !delay_before_next_attempt.is_zero() {
929                        // Print a "DELAY {}/{}" line.
930                        let delay_string = format!(
931                            "DELAY {}/{}",
932                            run_status.retry_data.attempt + 1,
933                            run_status.retry_data.total_attempts,
934                        );
935                        write!(
936                            writer,
937                            "{:>12} {}",
938                            delay_string.style(self.styles.retry),
939                            DisplayDurationBy(*delay_before_next_attempt)
940                        )?;
941
942                        // Print the name of the test.
943                        writeln!(
944                            writer,
945                            "{}",
946                            self.display_test_instance(
947                                *stress_index,
948                                TestInstanceCounter::Padded,
949                                *test_instance
950                            )
951                        )?;
952                    }
953                }
954            }
955            TestEventKind::TestRetryStarted {
956                stress_index,
957                test_instance,
958                retry_data: RetryData { attempt, .. },
959                running: _,
960                command_line,
961            } => {
962                // In no-capture and verbose modes, print out a retry start event.
963                if self.no_capture || self.verbose {
964                    let retry_string = format!("TRY {attempt} START");
965                    writeln!(
966                        writer,
967                        "{:>12} [         ] {}",
968                        retry_string.style(self.styles.retry),
969                        self.display_test_instance(
970                            *stress_index,
971                            TestInstanceCounter::Padded,
972                            *test_instance
973                        )
974                    )?;
975                }
976
977                if self.verbose {
978                    self.write_command_line(command_line, writer)?;
979                }
980            }
981            TestEventKind::TestFinished {
982                stress_index,
983                test_instance,
984                success_output,
985                failure_output,
986                run_statuses,
987                current_stats,
988                ..
989            } => {
990                let describe = run_statuses.describe();
991                let last_status = run_statuses.last_status();
992                let test_output_display = self.unit_output.resolve_test_output_display(
993                    *success_output,
994                    *failure_output,
995                    &last_status.result,
996                );
997
998                let output_on_test_finished = self.status_levels.compute_output_on_test_finished(
999                    test_output_display,
1000                    self.cancel_status,
1001                    describe.status_level(),
1002                    describe.final_status_level(),
1003                    &last_status.result,
1004                );
1005
1006                let counter = TestInstanceCounter::Counter {
1007                    current: current_stats.finished_count,
1008                    total: current_stats.initial_run_count,
1009                };
1010
1011                if output_on_test_finished.write_status_line {
1012                    self.write_status_line(
1013                        *stress_index,
1014                        counter,
1015                        *test_instance,
1016                        describe,
1017                        writer,
1018                    )?;
1019                }
1020                if output_on_test_finished.show_immediate {
1021                    self.write_test_execute_status(last_status, false, writer)?;
1022                }
1023                if let OutputStoreFinal::Yes { display_output } =
1024                    output_on_test_finished.store_final
1025                {
1026                    self.final_outputs.push(FinalOutputEntry {
1027                        stress_index: *stress_index,
1028                        counter,
1029                        instance: *test_instance,
1030                        output: FinalOutput::Executed {
1031                            run_statuses: run_statuses.clone(),
1032                            display_output,
1033                        },
1034                    });
1035                }
1036            }
1037            TestEventKind::TestSkipped {
1038                stress_index,
1039                test_instance,
1040                reason,
1041            } => {
1042                if self.status_levels.status_level >= StatusLevel::Skip {
1043                    self.write_skip_line(*stress_index, *test_instance, writer)?;
1044                }
1045                if self.status_levels.final_status_level >= FinalStatusLevel::Skip {
1046                    self.final_outputs.push(FinalOutputEntry {
1047                        stress_index: *stress_index,
1048                        counter: TestInstanceCounter::Padded,
1049                        instance: *test_instance,
1050                        output: FinalOutput::Skipped(*reason),
1051                    });
1052                }
1053            }
1054            TestEventKind::RunBeginCancel {
1055                setup_scripts_running,
1056                current_stats,
1057                running,
1058            } => {
1059                self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1060
1061                write!(writer, "{:>12} ", "Cancelling".style(self.styles.fail))?;
1062                if let Some(reason) = current_stats.cancel_reason {
1063                    write!(
1064                        writer,
1065                        "due to {}: ",
1066                        reason.to_static_str().style(self.styles.fail)
1067                    )?;
1068                }
1069
1070                let immediately_terminating_text =
1071                    if current_stats.cancel_reason == Some(CancelReason::TestFailureImmediate) {
1072                        format!("immediately {} ", "terminating".style(self.styles.fail))
1073                    } else {
1074                        String::new()
1075                    };
1076
1077                // At the moment, we can have either setup scripts or tests running, but not both.
1078                if *setup_scripts_running > 0 {
1079                    let s = plural::setup_scripts_str(*setup_scripts_running);
1080                    write!(
1081                        writer,
1082                        "{immediately_terminating_text}{} {s} still running",
1083                        setup_scripts_running.style(self.styles.count),
1084                    )?;
1085                } else if *running > 0 {
1086                    let tests_str = plural::tests_str(self.mode, *running);
1087                    write!(
1088                        writer,
1089                        "{immediately_terminating_text}{} {tests_str} still running",
1090                        running.style(self.styles.count),
1091                    )?;
1092                }
1093                writeln!(writer)?;
1094            }
1095            TestEventKind::RunBeginKill {
1096                setup_scripts_running,
1097                current_stats,
1098                running,
1099            } => {
1100                self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1101
1102                write!(writer, "{:>12} ", "Killing".style(self.styles.fail),)?;
1103                if let Some(reason) = current_stats.cancel_reason {
1104                    write!(
1105                        writer,
1106                        "due to {}: ",
1107                        reason.to_static_str().style(self.styles.fail)
1108                    )?;
1109                }
1110
1111                // At the moment, we can have either setup scripts or tests running, but not both.
1112                if *setup_scripts_running > 0 {
1113                    let s = plural::setup_scripts_str(*setup_scripts_running);
1114                    write!(
1115                        writer,
1116                        ": {} {s} still running",
1117                        setup_scripts_running.style(self.styles.count),
1118                    )?;
1119                } else if *running > 0 {
1120                    let tests_str = plural::tests_str(self.mode, *running);
1121                    write!(
1122                        writer,
1123                        ": {} {tests_str} still running",
1124                        running.style(self.styles.count),
1125                    )?;
1126                }
1127                writeln!(writer)?;
1128            }
1129            TestEventKind::RunPaused {
1130                setup_scripts_running,
1131                running,
1132            } => {
1133                write!(
1134                    writer,
1135                    "{:>12} due to {}",
1136                    "Pausing".style(self.styles.pass),
1137                    "signal".style(self.styles.count)
1138                )?;
1139
1140                // At the moment, we can have either setup scripts or tests running, but not both.
1141                if *setup_scripts_running > 0 {
1142                    let s = plural::setup_scripts_str(*setup_scripts_running);
1143                    write!(
1144                        writer,
1145                        ": {} {s} running",
1146                        setup_scripts_running.style(self.styles.count),
1147                    )?;
1148                } else if *running > 0 {
1149                    let tests_str = plural::tests_str(self.mode, *running);
1150                    write!(
1151                        writer,
1152                        ": {} {tests_str} running",
1153                        running.style(self.styles.count),
1154                    )?;
1155                }
1156                writeln!(writer)?;
1157            }
1158            TestEventKind::RunContinued {
1159                setup_scripts_running,
1160                running,
1161            } => {
1162                write!(
1163                    writer,
1164                    "{:>12} due to {}",
1165                    "Continuing".style(self.styles.pass),
1166                    "signal".style(self.styles.count)
1167                )?;
1168
1169                // At the moment, we can have either setup scripts or tests running, but not both.
1170                if *setup_scripts_running > 0 {
1171                    let s = plural::setup_scripts_str(*setup_scripts_running);
1172                    write!(
1173                        writer,
1174                        ": {} {s} running",
1175                        setup_scripts_running.style(self.styles.count),
1176                    )?;
1177                } else if *running > 0 {
1178                    let tests_str = plural::tests_str(self.mode, *running);
1179                    write!(
1180                        writer,
1181                        ": {} {tests_str} running",
1182                        running.style(self.styles.count),
1183                    )?;
1184                }
1185                writeln!(writer)?;
1186            }
1187            TestEventKind::InfoStarted { total, run_stats } => {
1188                let info_style = if run_stats.has_failures() {
1189                    self.styles.fail
1190                } else {
1191                    self.styles.pass
1192                };
1193
1194                let hbar = self.theme_characters.hbar(12);
1195
1196                write!(writer, "{hbar}\n{}: ", "info".style(info_style))?;
1197
1198                // TODO: display setup_scripts_running as well
1199                writeln!(
1200                    writer,
1201                    "{} in {:.3?}s",
1202                    // Using "total" here for the number of running units is a
1203                    // slight fudge, but it prevents situations where (due to
1204                    // races with unit tasks exiting) the numbers don't exactly
1205                    // match up. It's also not dishonest -- there really are
1206                    // these many units currently running.
1207                    progress_bar_msg(run_stats, *total, &self.styles),
1208                    event.elapsed.as_secs_f64(),
1209                )?;
1210            }
1211            TestEventKind::InfoResponse {
1212                index,
1213                total,
1214                response,
1215            } => {
1216                self.write_info_response(*index, *total, response, writer)?;
1217            }
1218            TestEventKind::InfoFinished { missing } => {
1219                let hbar = self.theme_characters.hbar(12);
1220
1221                if *missing > 0 {
1222                    // This should ordinarily not happen, but it's possible if
1223                    // some of the unit futures are slow to respond.
1224                    writeln!(
1225                        writer,
1226                        "{}: missing {} responses",
1227                        "info".style(self.styles.skip),
1228                        missing.style(self.styles.count)
1229                    )?;
1230                }
1231
1232                writeln!(writer, "{hbar}")?;
1233            }
1234            TestEventKind::InputEnter {
1235                current_stats,
1236                running,
1237            } => {
1238                // Print everything that would be shown in the progress bar,
1239                // except for the bar itself.
1240                writeln!(
1241                    writer,
1242                    "{}",
1243                    progress_str(event.elapsed, current_stats, *running, &self.styles)
1244                )?;
1245            }
1246            TestEventKind::StressSubRunFinished {
1247                progress,
1248                sub_elapsed,
1249                sub_stats,
1250            } => {
1251                let stats_summary = sub_stats.summarize_final();
1252                let summary_style = match stats_summary {
1253                    FinalRunStats::Success => self.styles.pass,
1254                    FinalRunStats::NoTestsRun => self.styles.skip,
1255                    FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1256                        self.styles.fail
1257                    }
1258                };
1259
1260                write!(
1261                    writer,
1262                    "{:>12} {}",
1263                    "Stress test".style(summary_style),
1264                    DisplayBracketedDuration(*sub_elapsed),
1265                )?;
1266                match progress {
1267                    StressProgress::Count {
1268                        total: StressCount::Count { count },
1269                        elapsed: _,
1270                        completed,
1271                    } => {
1272                        write!(
1273                            writer,
1274                            "iteration {}/{}: ",
1275                            // We do not add +1 to completed here because it
1276                            // represents the number of stress runs actually
1277                            // completed.
1278                            completed.style(self.styles.count),
1279                            count.style(self.styles.count),
1280                        )?;
1281                    }
1282                    StressProgress::Count {
1283                        total: StressCount::Infinite,
1284                        elapsed: _,
1285                        completed,
1286                    } => {
1287                        write!(
1288                            writer,
1289                            "iteration {}: ",
1290                            // We do not add +1 to completed here because it
1291                            // represents the number of stress runs actually
1292                            // completed.
1293                            completed.style(self.styles.count),
1294                        )?;
1295                    }
1296                    StressProgress::Time {
1297                        total: _,
1298                        elapsed: _,
1299                        completed,
1300                    } => {
1301                        write!(
1302                            writer,
1303                            "iteration {}: ",
1304                            // We do not add +1 to completed here because it
1305                            // represents the number of stress runs actually
1306                            // completed.
1307                            completed.style(self.styles.count),
1308                        )?;
1309                    }
1310                }
1311
1312                write!(
1313                    writer,
1314                    "{}",
1315                    sub_stats.finished_count.style(self.styles.count)
1316                )?;
1317                if sub_stats.finished_count != sub_stats.initial_run_count {
1318                    write!(
1319                        writer,
1320                        "/{}",
1321                        sub_stats.initial_run_count.style(self.styles.count)
1322                    )?;
1323                }
1324
1325                // Both initial and finished counts must be 1 for the singular form.
1326                let tests_str = plural::tests_plural_if(
1327                    self.mode,
1328                    sub_stats.initial_run_count != 1 || sub_stats.finished_count != 1,
1329                );
1330
1331                let mut summary_str = String::new();
1332                write_summary_str(sub_stats, &self.styles, &mut summary_str);
1333                writeln!(writer, " {tests_str} run: {summary_str}")?;
1334            }
1335            TestEventKind::RunFinished {
1336                start_time: _start_time,
1337                elapsed,
1338                run_stats,
1339                outstanding_not_seen: tests_not_seen,
1340                ..
1341            } => {
1342                match run_stats {
1343                    RunFinishedStats::Single(run_stats) => {
1344                        let stats_summary = run_stats.summarize_final();
1345                        let summary_style = match stats_summary {
1346                            FinalRunStats::Success => self.styles.pass,
1347                            FinalRunStats::NoTestsRun => self.styles.skip,
1348                            FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1349                                self.styles.fail
1350                            }
1351                        };
1352                        write!(
1353                            writer,
1354                            "{}\n{:>12} ",
1355                            self.theme_characters.hbar(12),
1356                            "Summary".style(summary_style)
1357                        )?;
1358
1359                        // Next, print the total time taken.
1360                        // * > means right-align.
1361                        // * 8 is the number of characters to pad to.
1362                        // * .3 means print two digits after the decimal point.
1363                        write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1364
1365                        write!(
1366                            writer,
1367                            "{}",
1368                            run_stats.finished_count.style(self.styles.count)
1369                        )?;
1370                        if run_stats.finished_count != run_stats.initial_run_count {
1371                            write!(
1372                                writer,
1373                                "/{}",
1374                                run_stats.initial_run_count.style(self.styles.count)
1375                            )?;
1376                        }
1377
1378                        // Both initial and finished counts must be 1 for the singular form.
1379                        let tests_str = plural::tests_plural_if(
1380                            self.mode,
1381                            run_stats.initial_run_count != 1 || run_stats.finished_count != 1,
1382                        );
1383
1384                        let mut summary_str = String::new();
1385                        write_summary_str(run_stats, &self.styles, &mut summary_str);
1386                        writeln!(writer, " {tests_str} run: {summary_str}")?;
1387                    }
1388                    RunFinishedStats::Stress(stats) => {
1389                        let stats_summary = stats.summarize_final();
1390                        let summary_style = match stats_summary {
1391                            StressFinalRunStats::Success => self.styles.pass,
1392                            StressFinalRunStats::NoTestsRun => self.styles.skip,
1393                            StressFinalRunStats::Cancelled | StressFinalRunStats::Failed => {
1394                                self.styles.fail
1395                            }
1396                        };
1397
1398                        write!(
1399                            writer,
1400                            "{}\n{:>12} ",
1401                            self.theme_characters.hbar(12),
1402                            "Summary".style(summary_style),
1403                        )?;
1404
1405                        // Next, print the total time taken.
1406                        // * > means right-align.
1407                        // * 8 is the number of characters to pad to.
1408                        // * .3 means print two digits after the decimal point.
1409                        write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1410
1411                        write!(
1412                            writer,
1413                            "{}",
1414                            stats.completed.current.style(self.styles.count),
1415                        )?;
1416                        let iterations_str = if let Some(total) = stats.completed.total {
1417                            write!(writer, "/{}", total.style(self.styles.count))?;
1418                            plural::iterations_str(total.get())
1419                        } else {
1420                            plural::iterations_str(stats.completed.current)
1421                        };
1422                        write!(
1423                            writer,
1424                            " stress run {iterations_str}: {} {}",
1425                            stats.success_count.style(self.styles.count),
1426                            "passed".style(self.styles.pass),
1427                        )?;
1428                        if stats.failed_count > 0 {
1429                            write!(
1430                                writer,
1431                                ", {} {}",
1432                                stats.failed_count.style(self.styles.count),
1433                                "failed".style(self.styles.fail),
1434                            )?;
1435                        }
1436
1437                        match stats.last_final_stats {
1438                            FinalRunStats::Cancelled { reason, kind: _ } => {
1439                                if let Some(reason) = reason {
1440                                    write!(
1441                                        writer,
1442                                        "; cancelled due to {}",
1443                                        reason.to_static_str().style(self.styles.fail),
1444                                    )?;
1445                                }
1446                            }
1447                            FinalRunStats::Failed { .. }
1448                            | FinalRunStats::Success
1449                            | FinalRunStats::NoTestsRun => {}
1450                        }
1451
1452                        writeln!(writer)?;
1453                    }
1454                }
1455
1456                // Don't print out test outputs after Ctrl-C, but *do* print them after SIGTERM or
1457                // SIGHUP since those tend to be automated tasks performing kills.
1458                if self.cancel_status < Some(CancelReason::Interrupt) {
1459                    // Sort the final outputs for a friendlier experience.
1460                    sort_final_outputs(&mut self.final_outputs, self.counter_width.is_some());
1461
1462                    for entry in &*self.final_outputs {
1463                        match &entry.output {
1464                            FinalOutput::Skipped(_) => {
1465                                self.write_skip_line(entry.stress_index, entry.instance, writer)?;
1466                            }
1467                            FinalOutput::Executed {
1468                                run_statuses,
1469                                display_output,
1470                            } => {
1471                                let last_status = run_statuses.last_status();
1472
1473                                self.write_final_status_line(
1474                                    entry.stress_index,
1475                                    entry.counter,
1476                                    entry.instance,
1477                                    run_statuses.describe(),
1478                                    writer,
1479                                )?;
1480                                if *display_output {
1481                                    self.write_test_execute_status(last_status, false, writer)?;
1482                                }
1483                            }
1484                        }
1485                    }
1486                }
1487
1488                if let Some(not_seen) = tests_not_seen
1489                    && not_seen.total_not_seen > 0
1490                {
1491                    writeln!(
1492                        writer,
1493                        "{:>12} {} outstanding {} not seen during this rerun:",
1494                        "Note".style(self.styles.skip),
1495                        not_seen.total_not_seen.style(self.styles.count),
1496                        plural::tests_str(self.mode, not_seen.total_not_seen),
1497                    )?;
1498
1499                    for t in &not_seen.not_seen {
1500                        let display = DisplayTestInstance::new(
1501                            None,
1502                            None,
1503                            t.as_ref(),
1504                            &self.styles.list_styles,
1505                        );
1506                        writeln!(writer, "             {}", display)?;
1507                    }
1508
1509                    let remaining = not_seen
1510                        .total_not_seen
1511                        .saturating_sub(not_seen.not_seen.len());
1512                    if remaining > 0 {
1513                        writeln!(
1514                            writer,
1515                            "             ... and {} more {}",
1516                            remaining.style(self.styles.count),
1517                            plural::tests_str(self.mode, remaining),
1518                        )?;
1519                    }
1520                }
1521
1522                // Print out warnings at the end, if any.
1523                write_final_warnings(self.mode, run_stats.final_stats(), &self.styles, writer)?;
1524            }
1525        }
1526
1527        Ok(())
1528    }
1529
1530    fn write_skip_line(
1531        &self,
1532        stress_index: Option<StressIndex>,
1533        test_instance: TestInstanceId<'a>,
1534        writer: &mut dyn WriteStr,
1535    ) -> io::Result<()> {
1536        write!(writer, "{:>12} ", "SKIP".style(self.styles.skip))?;
1537        // same spacing   [   0.034s]
1538        writeln!(
1539            writer,
1540            "[         ] {}",
1541            self.display_test_instance(stress_index, TestInstanceCounter::Padded, test_instance)
1542        )?;
1543
1544        Ok(())
1545    }
1546
1547    fn write_setup_script_status_line(
1548        &self,
1549        stress_index: Option<StressIndex>,
1550        script_id: &ScriptId,
1551        command: &str,
1552        args: &[String],
1553        status: &SetupScriptExecuteStatus<LiveSpec>,
1554        writer: &mut dyn WriteStr,
1555    ) -> io::Result<()> {
1556        match status.result {
1557            ExecutionResultDescription::Pass => {
1558                write!(writer, "{:>12} ", "SETUP PASS".style(self.styles.pass))?;
1559            }
1560            ExecutionResultDescription::Leak { result } => match result {
1561                LeakTimeoutResult::Pass => {
1562                    write!(writer, "{:>12} ", "SETUP LEAK".style(self.styles.skip))?;
1563                }
1564                LeakTimeoutResult::Fail => {
1565                    write!(writer, "{:>12} ", "SETUP LKFAIL".style(self.styles.fail))?;
1566                }
1567            },
1568            ref other => {
1569                let status_str = short_status_str(other);
1570                write!(
1571                    writer,
1572                    "{:>12} ",
1573                    format!("SETUP {status_str}").style(self.styles.fail),
1574                )?;
1575            }
1576        }
1577
1578        writeln!(
1579            writer,
1580            "{}{}",
1581            DisplayBracketedDuration(status.time_taken),
1582            self.display_script_instance(stress_index, script_id.clone(), command, args)
1583        )?;
1584
1585        Ok(())
1586    }
1587
1588    fn write_status_line(
1589        &self,
1590        stress_index: Option<StressIndex>,
1591        counter: TestInstanceCounter,
1592        test_instance: TestInstanceId<'a>,
1593        describe: ExecutionDescription<'_, LiveSpec>,
1594        writer: &mut dyn WriteStr,
1595    ) -> io::Result<()> {
1596        self.write_status_line_impl(
1597            stress_index,
1598            counter,
1599            test_instance,
1600            describe,
1601            StatusLineKind::Intermediate,
1602            writer,
1603        )
1604    }
1605
1606    fn write_final_status_line(
1607        &self,
1608        stress_index: Option<StressIndex>,
1609        counter: TestInstanceCounter,
1610        test_instance: TestInstanceId<'a>,
1611        describe: ExecutionDescription<'_, LiveSpec>,
1612        writer: &mut dyn WriteStr,
1613    ) -> io::Result<()> {
1614        self.write_status_line_impl(
1615            stress_index,
1616            counter,
1617            test_instance,
1618            describe,
1619            StatusLineKind::Final,
1620            writer,
1621        )
1622    }
1623
1624    fn write_status_line_impl(
1625        &self,
1626        stress_index: Option<StressIndex>,
1627        counter: TestInstanceCounter,
1628        test_instance: TestInstanceId<'a>,
1629        describe: ExecutionDescription<'_, LiveSpec>,
1630        kind: StatusLineKind,
1631        writer: &mut dyn WriteStr,
1632    ) -> io::Result<()> {
1633        let last_status = describe.last_status();
1634
1635        // Write the status prefix (e.g., "PASS", "FAIL", "FLAKY 2/3").
1636        self.write_status_line_prefix(describe, kind, writer)?;
1637
1638        // Write the duration and test instance.
1639        writeln!(
1640            writer,
1641            "{}{}",
1642            DisplayBracketedDuration(last_status.time_taken),
1643            self.display_test_instance(stress_index, counter, test_instance),
1644        )?;
1645
1646        // For Windows aborts, print out the exception code on a separate line.
1647        if let ExecutionResultDescription::Fail {
1648            failure: FailureDescription::Abort { ref abort },
1649            leaked: _,
1650        } = last_status.result
1651        {
1652            write_windows_abort_line(abort, &self.styles, writer)?;
1653        }
1654
1655        Ok(())
1656    }
1657
1658    fn write_status_line_prefix(
1659        &self,
1660        describe: ExecutionDescription<'_, LiveSpec>,
1661        kind: StatusLineKind,
1662        writer: &mut dyn WriteStr,
1663    ) -> io::Result<()> {
1664        let last_status = describe.last_status();
1665        match describe {
1666            ExecutionDescription::Success { .. } => {
1667                // Exhaustive match on (is_slow, result) to catch missing cases
1668                // at compile time. For intermediate status lines, is_slow is
1669                // ignored (shown via separate SLOW lines during execution).
1670                match (kind, last_status.is_slow, &last_status.result) {
1671                    // Final + slow variants.
1672                    (StatusLineKind::Final, true, ExecutionResultDescription::Pass) => {
1673                        write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
1674                    }
1675                    (
1676                        StatusLineKind::Final,
1677                        true,
1678                        ExecutionResultDescription::Leak {
1679                            result: LeakTimeoutResult::Pass,
1680                        },
1681                    ) => {
1682                        write!(writer, "{:>12} ", "SLOW + LEAK".style(self.styles.skip))?;
1683                    }
1684                    (
1685                        StatusLineKind::Final,
1686                        true,
1687                        ExecutionResultDescription::Timeout {
1688                            result: SlowTimeoutResult::Pass,
1689                        },
1690                    ) => {
1691                        write!(writer, "{:>12} ", "SLOW+TMPASS".style(self.styles.skip))?;
1692                    }
1693                    // Non-slow variants (or intermediate where is_slow is ignored).
1694                    (_, _, ExecutionResultDescription::Pass) => {
1695                        write!(writer, "{:>12} ", "PASS".style(self.styles.pass))?;
1696                    }
1697                    (
1698                        _,
1699                        _,
1700                        ExecutionResultDescription::Leak {
1701                            result: LeakTimeoutResult::Pass,
1702                        },
1703                    ) => {
1704                        write!(writer, "{:>12} ", "LEAK".style(self.styles.skip))?;
1705                    }
1706                    (
1707                        _,
1708                        _,
1709                        ExecutionResultDescription::Timeout {
1710                            result: SlowTimeoutResult::Pass,
1711                        },
1712                    ) => {
1713                        write!(writer, "{:>12} ", "TIMEOUT-PASS".style(self.styles.skip))?;
1714                    }
1715                    // These are failure cases and cannot appear in Success.
1716                    (
1717                        _,
1718                        _,
1719                        ExecutionResultDescription::Leak {
1720                            result: LeakTimeoutResult::Fail,
1721                        },
1722                    )
1723                    | (
1724                        _,
1725                        _,
1726                        ExecutionResultDescription::Timeout {
1727                            result: SlowTimeoutResult::Fail,
1728                        },
1729                    )
1730                    | (_, _, ExecutionResultDescription::Fail { .. })
1731                    | (_, _, ExecutionResultDescription::ExecFail) => {
1732                        unreachable!(
1733                            "success description cannot have failure result: {:?}",
1734                            last_status.result
1735                        )
1736                    }
1737                }
1738            }
1739            ExecutionDescription::Flaky { .. } => {
1740                // Use the skip color to also represent a flaky test.
1741                let status = match kind {
1742                    StatusLineKind::Intermediate => {
1743                        format!("TRY {} PASS", last_status.retry_data.attempt)
1744                    }
1745                    StatusLineKind::Final => {
1746                        format!(
1747                            "FLAKY {}/{}",
1748                            last_status.retry_data.attempt, last_status.retry_data.total_attempts
1749                        )
1750                    }
1751                };
1752                write!(writer, "{:>12} ", status.style(self.styles.skip))?;
1753            }
1754            ExecutionDescription::Failure { .. } => {
1755                if last_status.retry_data.attempt == 1 {
1756                    write!(
1757                        writer,
1758                        "{:>12} ",
1759                        status_str(&last_status.result).style(self.styles.fail)
1760                    )?;
1761                } else {
1762                    let status_str = short_status_str(&last_status.result);
1763                    write!(
1764                        writer,
1765                        "{:>12} ",
1766                        format!("TRY {} {}", last_status.retry_data.attempt, status_str)
1767                            .style(self.styles.fail)
1768                    )?;
1769                }
1770            }
1771        }
1772        Ok(())
1773    }
1774
1775    fn display_test_instance(
1776        &self,
1777        stress_index: Option<StressIndex>,
1778        counter: TestInstanceCounter,
1779        instance: TestInstanceId<'a>,
1780    ) -> DisplayTestInstance<'_> {
1781        let counter_index = match (counter, self.counter_width) {
1782            (TestInstanceCounter::Counter { current, total }, Some(_)) => {
1783                Some(DisplayCounterIndex::new_counter(current, total))
1784            }
1785            (TestInstanceCounter::Padded, Some(counter_width)) => Some(
1786                DisplayCounterIndex::new_padded(self.theme_characters.hbar_char(), counter_width),
1787            ),
1788            (TestInstanceCounter::None, _) | (_, None) => None,
1789        };
1790
1791        DisplayTestInstance::new(
1792            stress_index,
1793            counter_index,
1794            instance,
1795            &self.styles.list_styles,
1796        )
1797    }
1798
1799    fn write_command_line(
1800        &self,
1801        command_line: &[String],
1802        writer: &mut dyn WriteStr,
1803    ) -> io::Result<()> {
1804        // Indent under START (13 spaces + "command").
1805        writeln!(
1806            writer,
1807            "{:>20}: {}",
1808            "command".style(self.styles.count),
1809            shell_words::join(command_line),
1810        )
1811    }
1812
1813    fn display_script_instance(
1814        &self,
1815        stress_index: Option<StressIndex>,
1816        script_id: ScriptId,
1817        command: &str,
1818        args: &[String],
1819    ) -> DisplayScriptInstance {
1820        DisplayScriptInstance::new(
1821            stress_index,
1822            script_id,
1823            command,
1824            args,
1825            self.styles.script_id,
1826            self.styles.count,
1827        )
1828    }
1829
1830    fn write_info_response(
1831        &self,
1832        index: usize,
1833        total: usize,
1834        response: &InfoResponse<'_>,
1835        writer: &mut dyn WriteStr,
1836    ) -> io::Result<()> {
1837        if index > 0 {
1838            // Show a shorter hbar than the hbar surrounding the info started
1839            // and finished lines.
1840            writeln!(writer, "{}", self.theme_characters.hbar(8))?;
1841        }
1842
1843        // "status: " is 8 characters. Pad "{}/{}:" such that it also gets to
1844        // the 8 characters.
1845        //
1846        // The width to be printed out is index width + total width + 1 for '/'
1847        // + 1 for ':' + 1 for the space after that.
1848        let count_width = usize_decimal_char_width(index + 1) + usize_decimal_char_width(total) + 3;
1849        let padding = 8usize.saturating_sub(count_width);
1850
1851        write!(
1852            writer,
1853            "\n* {}/{}: {:padding$}",
1854            // index is 0-based, so add 1 to make it 1-based.
1855            (index + 1).style(self.styles.count),
1856            total.style(self.styles.count),
1857            "",
1858        )?;
1859
1860        // Indent everything a bit to make it clear that this is a
1861        // response.
1862        let mut writer = indented(writer).with_str("  ").skip_initial();
1863
1864        match response {
1865            InfoResponse::SetupScript(SetupScriptInfoResponse {
1866                stress_index,
1867                script_id,
1868                program,
1869                args,
1870                state,
1871                output,
1872            }) => {
1873                // Write the setup script name.
1874                writeln!(
1875                    writer,
1876                    "{}",
1877                    self.display_script_instance(*stress_index, script_id.clone(), program, args)
1878                )?;
1879
1880                // Write the state of the script.
1881                self.write_unit_state(
1882                    UnitKind::Script,
1883                    "",
1884                    state,
1885                    output.has_errors(),
1886                    &mut writer,
1887                )?;
1888
1889                // Write the output of the script.
1890                if state.has_valid_output() {
1891                    self.unit_output.write_child_execution_output(
1892                        &self.styles,
1893                        &self.output_spec_for_info(UnitKind::Script),
1894                        output,
1895                        &mut writer,
1896                    )?;
1897                }
1898            }
1899            InfoResponse::Test(TestInfoResponse {
1900                stress_index,
1901                test_instance,
1902                retry_data,
1903                state,
1904                output,
1905            }) => {
1906                // Write the test name.
1907                writeln!(
1908                    writer,
1909                    "{}",
1910                    self.display_test_instance(
1911                        *stress_index,
1912                        TestInstanceCounter::None,
1913                        *test_instance
1914                    )
1915                )?;
1916
1917                // We want to show an attached attempt string either if this is
1918                // a DelayBeforeNextAttempt message or if this is a retry. (This
1919                // is a bit abstraction-breaking, but what good UI isn't?)
1920                let show_attempt_str = (retry_data.attempt > 1 && retry_data.total_attempts > 1)
1921                    || matches!(state, UnitState::DelayBeforeNextAttempt { .. });
1922                let attempt_str = if show_attempt_str {
1923                    format!(
1924                        "(attempt {}/{}) ",
1925                        retry_data.attempt, retry_data.total_attempts
1926                    )
1927                } else {
1928                    String::new()
1929                };
1930
1931                // Write the state of the test.
1932                self.write_unit_state(
1933                    UnitKind::Test,
1934                    &attempt_str,
1935                    state,
1936                    output.has_errors(),
1937                    &mut writer,
1938                )?;
1939
1940                // Write the output of the test.
1941                if state.has_valid_output() {
1942                    self.unit_output.write_child_execution_output(
1943                        &self.styles,
1944                        &self.output_spec_for_info(UnitKind::Test),
1945                        output,
1946                        &mut writer,
1947                    )?;
1948                }
1949            }
1950        }
1951
1952        writer.write_str_flush()?;
1953        let inner_writer = writer.into_inner();
1954
1955        // Add a newline at the end to visually separate the responses.
1956        writeln!(inner_writer)?;
1957
1958        Ok(())
1959    }
1960
1961    fn write_unit_state(
1962        &self,
1963        kind: UnitKind,
1964        attempt_str: &str,
1965        state: &UnitState,
1966        output_has_errors: bool,
1967        writer: &mut dyn WriteStr,
1968    ) -> io::Result<()> {
1969        let status_str = "status".style(self.styles.count);
1970        match state {
1971            UnitState::Running {
1972                pid,
1973                time_taken,
1974                slow_after,
1975            } => {
1976                let running_style = if output_has_errors {
1977                    self.styles.fail
1978                } else if slow_after.is_some() {
1979                    self.styles.skip
1980                } else {
1981                    self.styles.pass
1982                };
1983                write!(
1984                    writer,
1985                    "{status_str}: {attempt_str}{} {} for {:.3?}s as PID {}",
1986                    DisplayUnitKind::new(self.mode, kind),
1987                    "running".style(running_style),
1988                    time_taken.as_secs_f64(),
1989                    pid.style(self.styles.count),
1990                )?;
1991                if let Some(slow_after) = slow_after {
1992                    write!(
1993                        writer,
1994                        " (marked slow after {:.3?}s)",
1995                        slow_after.as_secs_f64()
1996                    )?;
1997                }
1998                writeln!(writer)?;
1999            }
2000            UnitState::Exiting {
2001                pid,
2002                time_taken,
2003                slow_after,
2004                tentative_result,
2005                waiting_duration,
2006                remaining,
2007            } => {
2008                write!(
2009                    writer,
2010                    "{status_str}: {attempt_str}{} ",
2011                    DisplayUnitKind::new(self.mode, kind)
2012                )?;
2013
2014                let tentative_desc = tentative_result.map(ExecutionResultDescription::from);
2015                self.write_info_execution_result(
2016                    tentative_desc.as_ref(),
2017                    slow_after.is_some(),
2018                    writer,
2019                )?;
2020                write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2021                if let Some(slow_after) = slow_after {
2022                    write!(
2023                        writer,
2024                        " (marked slow after {:.3?}s)",
2025                        slow_after.as_secs_f64()
2026                    )?;
2027                }
2028                writeln!(writer)?;
2029
2030                // Don't need to print the waiting duration for leak detection
2031                // if it's relatively small.
2032                if *waiting_duration >= Duration::from_secs(1) {
2033                    writeln!(
2034                        writer,
2035                        "{}:   spent {:.3?}s waiting for {} PID {} to shut down, \
2036                         will mark as leaky after another {:.3?}s",
2037                        "note".style(self.styles.count),
2038                        waiting_duration.as_secs_f64(),
2039                        DisplayUnitKind::new(self.mode, kind),
2040                        pid.style(self.styles.count),
2041                        remaining.as_secs_f64(),
2042                    )?;
2043                }
2044            }
2045            UnitState::Terminating(state) => {
2046                self.write_terminating_state(kind, attempt_str, state, writer)?;
2047            }
2048            UnitState::Exited {
2049                result,
2050                time_taken,
2051                slow_after,
2052            } => {
2053                write!(
2054                    writer,
2055                    "{status_str}: {attempt_str}{} ",
2056                    DisplayUnitKind::new(self.mode, kind)
2057                )?;
2058                let result_desc = ExecutionResultDescription::from(*result);
2059                self.write_info_execution_result(Some(&result_desc), slow_after.is_some(), writer)?;
2060                write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2061                if let Some(slow_after) = slow_after {
2062                    write!(
2063                        writer,
2064                        " (marked slow after {:.3?}s)",
2065                        slow_after.as_secs_f64()
2066                    )?;
2067                }
2068                writeln!(writer)?;
2069            }
2070            UnitState::DelayBeforeNextAttempt {
2071                previous_result,
2072                previous_slow,
2073                waiting_duration,
2074                remaining,
2075            } => {
2076                write!(
2077                    writer,
2078                    "{status_str}: {attempt_str}{} ",
2079                    DisplayUnitKind::new(self.mode, kind)
2080                )?;
2081                let previous_desc = ExecutionResultDescription::from(*previous_result);
2082                self.write_info_execution_result(Some(&previous_desc), *previous_slow, writer)?;
2083                writeln!(
2084                    writer,
2085                    ", currently {} before next attempt",
2086                    "waiting".style(self.styles.count)
2087                )?;
2088                writeln!(
2089                    writer,
2090                    "{}:   waited {:.3?}s so far, will wait another {:.3?}s before retrying {}",
2091                    "note".style(self.styles.count),
2092                    waiting_duration.as_secs_f64(),
2093                    remaining.as_secs_f64(),
2094                    DisplayUnitKind::new(self.mode, kind),
2095                )?;
2096            }
2097        }
2098
2099        Ok(())
2100    }
2101
2102    fn write_terminating_state(
2103        &self,
2104        kind: UnitKind,
2105        attempt_str: &str,
2106        state: &UnitTerminatingState,
2107        writer: &mut dyn WriteStr,
2108    ) -> io::Result<()> {
2109        let UnitTerminatingState {
2110            pid,
2111            time_taken,
2112            reason,
2113            method,
2114            waiting_duration,
2115            remaining,
2116        } = state;
2117
2118        writeln!(
2119            writer,
2120            "{}: {attempt_str}{} {} PID {} due to {} ({} ran for {:.3?}s)",
2121            "status".style(self.styles.count),
2122            "terminating".style(self.styles.fail),
2123            DisplayUnitKind::new(self.mode, kind),
2124            pid.style(self.styles.count),
2125            reason.style(self.styles.count),
2126            DisplayUnitKind::new(self.mode, kind),
2127            time_taken.as_secs_f64(),
2128        )?;
2129
2130        match method {
2131            #[cfg(unix)]
2132            UnitTerminateMethod::Signal(signal) => {
2133                writeln!(
2134                    writer,
2135                    "{}:   sent {} to process group; spent {:.3?}s waiting for {} to exit, \
2136                     will SIGKILL after another {:.3?}s",
2137                    "note".style(self.styles.count),
2138                    signal,
2139                    waiting_duration.as_secs_f64(),
2140                    DisplayUnitKind::new(self.mode, kind),
2141                    remaining.as_secs_f64(),
2142                )?;
2143            }
2144            #[cfg(windows)]
2145            UnitTerminateMethod::JobObject => {
2146                writeln!(
2147                    writer,
2148                    // Job objects are like SIGKILL -- they terminate
2149                    // immediately. No need to show the waiting duration or
2150                    // remaining time.
2151                    "{}:   instructed job object to terminate",
2152                    "note".style(self.styles.count),
2153                )?;
2154            }
2155            #[cfg(windows)]
2156            UnitTerminateMethod::Wait => {
2157                writeln!(
2158                    writer,
2159                    "{}:   waiting for {} to exit on its own; spent {:.3?}s, will terminate \
2160                     job object after another {:.3?}s",
2161                    "note".style(self.styles.count),
2162                    DisplayUnitKind::new(self.mode, kind),
2163                    waiting_duration.as_secs_f64(),
2164                    remaining.as_secs_f64(),
2165                )?;
2166            }
2167            #[cfg(test)]
2168            UnitTerminateMethod::Fake => {
2169                // This is only used in tests.
2170                writeln!(
2171                    writer,
2172                    "{}:   fake termination method; spent {:.3?}s waiting for {} to exit, \
2173                     will kill after another {:.3?}s",
2174                    "note".style(self.styles.count),
2175                    waiting_duration.as_secs_f64(),
2176                    DisplayUnitKind::new(self.mode, kind),
2177                    remaining.as_secs_f64(),
2178                )?;
2179            }
2180        }
2181
2182        Ok(())
2183    }
2184
2185    // TODO: this should be unified with write_exit_status above -- we need a
2186    // general, short description of what's happened to both an in-progress and
2187    // a final unit.
2188    fn write_info_execution_result(
2189        &self,
2190        result: Option<&ExecutionResultDescription>,
2191        is_slow: bool,
2192        writer: &mut dyn WriteStr,
2193    ) -> io::Result<()> {
2194        match result {
2195            Some(ExecutionResultDescription::Pass) => {
2196                let style = if is_slow {
2197                    self.styles.skip
2198                } else {
2199                    self.styles.pass
2200                };
2201
2202                write!(writer, "{}", "passed".style(style))
2203            }
2204            Some(ExecutionResultDescription::Leak {
2205                result: LeakTimeoutResult::Pass,
2206            }) => write!(
2207                writer,
2208                "{}",
2209                "passed with leaked handles".style(self.styles.skip)
2210            ),
2211            Some(ExecutionResultDescription::Leak {
2212                result: LeakTimeoutResult::Fail,
2213            }) => write!(
2214                writer,
2215                "{}: exited with code 0, but leaked handles",
2216                "failed".style(self.styles.fail),
2217            ),
2218            Some(ExecutionResultDescription::Timeout {
2219                result: SlowTimeoutResult::Pass,
2220            }) => {
2221                write!(writer, "{}", "passed with timeout".style(self.styles.skip))
2222            }
2223            Some(ExecutionResultDescription::Timeout {
2224                result: SlowTimeoutResult::Fail,
2225            }) => {
2226                write!(writer, "{}", "timed out".style(self.styles.fail))
2227            }
2228            Some(ExecutionResultDescription::Fail {
2229                failure: FailureDescription::Abort { abort },
2230                // TODO: show leaked info here like in FailureDescription::ExitCode
2231                // below?
2232                leaked: _,
2233            }) => {
2234                // The errors are shown in the output.
2235                write!(writer, "{}", "aborted".style(self.styles.fail))?;
2236                // AbortDescription is platform-independent and contains display
2237                // info. Note that Windows descriptions are handled separately,
2238                // in write_windows_abort_suffix.
2239                if let AbortDescription::UnixSignal { signal, name } = abort {
2240                    write!(writer, " with signal {}", signal.style(self.styles.count))?;
2241                    if let Some(s) = name {
2242                        write!(writer, ": SIG{s}")?;
2243                    }
2244                }
2245                Ok(())
2246            }
2247            Some(ExecutionResultDescription::Fail {
2248                failure: FailureDescription::ExitCode { code },
2249                leaked,
2250            }) => {
2251                if *leaked {
2252                    write!(
2253                        writer,
2254                        "{} with exit code {}, and leaked handles",
2255                        "failed".style(self.styles.fail),
2256                        code.style(self.styles.count),
2257                    )
2258                } else {
2259                    write!(
2260                        writer,
2261                        "{} with exit code {}",
2262                        "failed".style(self.styles.fail),
2263                        code.style(self.styles.count),
2264                    )
2265                }
2266            }
2267            Some(ExecutionResultDescription::ExecFail) => {
2268                write!(writer, "{}", "failed to execute".style(self.styles.fail))
2269            }
2270            None => {
2271                write!(
2272                    writer,
2273                    "{} with unknown status",
2274                    "failed".style(self.styles.fail)
2275                )
2276            }
2277        }
2278    }
2279
2280    fn write_setup_script_execute_status(
2281        &self,
2282        run_status: &SetupScriptExecuteStatus<LiveSpec>,
2283        writer: &mut dyn WriteStr,
2284    ) -> io::Result<()> {
2285        let spec = self.output_spec_for_finished(&run_status.result, false);
2286        self.unit_output.write_child_execution_output(
2287            &self.styles,
2288            &spec,
2289            &run_status.output,
2290            writer,
2291        )?;
2292
2293        if show_finished_status_info_line(&run_status.result) {
2294            write!(
2295                writer,
2296                // Align with output.
2297                "    (script ",
2298            )?;
2299            self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2300            writeln!(writer, ")\n")?;
2301        }
2302
2303        Ok(())
2304    }
2305
2306    fn write_test_execute_status(
2307        &self,
2308        run_status: &ExecuteStatus<LiveSpec>,
2309        is_retry: bool,
2310        writer: &mut dyn WriteStr,
2311    ) -> io::Result<()> {
2312        let spec = self.output_spec_for_finished(&run_status.result, is_retry);
2313        self.unit_output.write_child_execution_output(
2314            &self.styles,
2315            &spec,
2316            &run_status.output,
2317            writer,
2318        )?;
2319
2320        if show_finished_status_info_line(&run_status.result) {
2321            write!(
2322                writer,
2323                // Align with output.
2324                "    (test ",
2325            )?;
2326            self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2327            writeln!(writer, ")\n")?;
2328        }
2329
2330        Ok(())
2331    }
2332
2333    fn output_spec_for_finished(
2334        &self,
2335        result: &ExecutionResultDescription,
2336        is_retry: bool,
2337    ) -> ChildOutputSpec {
2338        let header_style = if is_retry {
2339            self.styles.retry
2340        } else {
2341            match result {
2342                ExecutionResultDescription::Pass => self.styles.pass,
2343                ExecutionResultDescription::Leak {
2344                    result: LeakTimeoutResult::Pass,
2345                } => self.styles.skip,
2346                ExecutionResultDescription::Leak {
2347                    result: LeakTimeoutResult::Fail,
2348                } => self.styles.fail,
2349                ExecutionResultDescription::Timeout {
2350                    result: SlowTimeoutResult::Pass,
2351                } => self.styles.skip,
2352                ExecutionResultDescription::Timeout {
2353                    result: SlowTimeoutResult::Fail,
2354                } => self.styles.fail,
2355                ExecutionResultDescription::Fail { .. } => self.styles.fail,
2356                ExecutionResultDescription::ExecFail => self.styles.fail,
2357            }
2358        };
2359
2360        // Adding an hbar at the end gives the text a bit of visual weight that
2361        // makes it look more balanced. Align it with the end of the header to
2362        // provide a visual transition from status lines (PASS/FAIL etc) to
2363        // indented output.
2364        //
2365        // With indentation, the output looks like:
2366        //
2367        //         FAIL [ .... ]
2368        //   stdout ───
2369        //     <test stdout>
2370        //   stderr ───
2371        //     <test stderr>
2372        //
2373        // Without indentation:
2374        //
2375        //         FAIL [ .... ]
2376        // ── stdout ──
2377        // <test stdout>
2378        // ── stderr ──
2379        // <test stderr>
2380        let (six_char_start, six_char_end, eight_char_start, eight_char_end, output_indent) =
2381            if self.no_output_indent {
2382                (
2383                    self.theme_characters.hbar(2),
2384                    self.theme_characters.hbar(2),
2385                    self.theme_characters.hbar(1),
2386                    self.theme_characters.hbar(1),
2387                    "",
2388                )
2389            } else {
2390                (
2391                    " ".to_owned(),
2392                    self.theme_characters.hbar(3),
2393                    " ".to_owned(),
2394                    self.theme_characters.hbar(1),
2395                    "    ",
2396                )
2397            };
2398
2399        let stdout_header = format!(
2400            "{} {} {}",
2401            six_char_start.style(header_style),
2402            "stdout".style(header_style),
2403            six_char_end.style(header_style),
2404        );
2405        let stderr_header = format!(
2406            "{} {} {}",
2407            six_char_start.style(header_style),
2408            "stderr".style(header_style),
2409            six_char_end.style(header_style),
2410        );
2411        let combined_header = format!(
2412            "{} {} {}",
2413            six_char_start.style(header_style),
2414            "output".style(header_style),
2415            six_char_end.style(header_style),
2416        );
2417        let exec_fail_header = format!(
2418            "{} {} {}",
2419            eight_char_start.style(header_style),
2420            "execfail".style(header_style),
2421            eight_char_end.style(header_style),
2422        );
2423
2424        ChildOutputSpec {
2425            kind: UnitKind::Test,
2426            stdout_header,
2427            stderr_header,
2428            combined_header,
2429            exec_fail_header,
2430            output_indent,
2431        }
2432    }
2433
2434    // Info response queries are more compact and so have a somewhat different
2435    // output format. But at some point we should consider using the same format
2436    // for both regular test output and info responses.
2437    fn output_spec_for_info(&self, kind: UnitKind) -> ChildOutputSpec {
2438        let stdout_header = format!("{}:", "stdout".style(self.styles.count));
2439        let stderr_header = format!("{}:", "stderr".style(self.styles.count));
2440        let combined_header = format!("{}:", "output".style(self.styles.count));
2441        let exec_fail_header = format!("{}:", "errors".style(self.styles.count));
2442
2443        ChildOutputSpec {
2444            kind,
2445            stdout_header,
2446            stderr_header,
2447            combined_header,
2448            exec_fail_header,
2449            output_indent: "  ",
2450        }
2451    }
2452}
2453
2454#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2455enum TestInstanceCounter {
2456    Counter { current: usize, total: usize },
2457    Padded,
2458    None,
2459}
2460
2461/// Whether a status line is an intermediate line (during execution) or a final
2462/// line (in the summary).
2463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2464enum StatusLineKind {
2465    /// Intermediate status line shown during test execution.
2466    Intermediate,
2467    /// Final status line shown in the summary.
2468    Final,
2469}
2470
2471const LIBTEST_PANIC_EXIT_CODE: i32 = 101;
2472
2473// Whether to show a status line for finished units (after STDOUT:/STDERR:).
2474// This does not apply to info responses which have their own logic.
2475fn show_finished_status_info_line(result: &ExecutionResultDescription) -> bool {
2476    // Don't show the status line if the exit code is the default from cargo test panicking.
2477    match result {
2478        ExecutionResultDescription::Pass => false,
2479        ExecutionResultDescription::Leak {
2480            result: LeakTimeoutResult::Pass,
2481        } => {
2482            // Show the leaked-handles message.
2483            true
2484        }
2485        ExecutionResultDescription::Leak {
2486            result: LeakTimeoutResult::Fail,
2487        } => {
2488            // This is a confusing state without the message at the end.
2489            true
2490        }
2491        ExecutionResultDescription::Fail {
2492            failure: FailureDescription::ExitCode { code },
2493            leaked,
2494        } => {
2495            // Don't show the status line if the exit code is the default from
2496            // cargo test panicking, and if there were no leaked handles.
2497            *code != LIBTEST_PANIC_EXIT_CODE && !leaked
2498        }
2499        ExecutionResultDescription::Fail {
2500            failure: FailureDescription::Abort { .. },
2501            leaked: _,
2502        } => {
2503            // Showing a line at the end aids in clarity.
2504            true
2505        }
2506        ExecutionResultDescription::ExecFail => {
2507            // This is already shown as an error so there's no reason to show it
2508            // again.
2509            false
2510        }
2511        ExecutionResultDescription::Timeout { .. } => {
2512            // Show this to be clear what happened.
2513            true
2514        }
2515    }
2516}
2517
2518fn status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2519    // Max 12 characters here.
2520    match result {
2521        ExecutionResultDescription::Fail {
2522            failure:
2523                FailureDescription::Abort {
2524                    abort: AbortDescription::UnixSignal { signal, name },
2525                },
2526            leaked: _,
2527        } => match name {
2528            Some(s) => format!("SIG{s}").into(),
2529            None => format!("ABORT SIG {signal}").into(),
2530        },
2531        ExecutionResultDescription::Fail {
2532            failure:
2533                FailureDescription::Abort {
2534                    abort: AbortDescription::WindowsNtStatus { .. },
2535                }
2536                | FailureDescription::Abort {
2537                    abort: AbortDescription::WindowsJobObject,
2538                },
2539            leaked: _,
2540        } => {
2541            // Going to print out the full error message on the following line -- just "ABORT" will
2542            // do for now.
2543            "ABORT".into()
2544        }
2545        ExecutionResultDescription::Fail {
2546            failure: FailureDescription::ExitCode { .. },
2547            leaked: true,
2548        } => "FAIL + LEAK".into(),
2549        ExecutionResultDescription::Fail {
2550            failure: FailureDescription::ExitCode { .. },
2551            leaked: false,
2552        } => "FAIL".into(),
2553        ExecutionResultDescription::ExecFail => "XFAIL".into(),
2554        ExecutionResultDescription::Pass => "PASS".into(),
2555        ExecutionResultDescription::Leak {
2556            result: LeakTimeoutResult::Pass,
2557        } => "LEAK".into(),
2558        ExecutionResultDescription::Leak {
2559            result: LeakTimeoutResult::Fail,
2560        } => "LEAK-FAIL".into(),
2561        ExecutionResultDescription::Timeout {
2562            result: SlowTimeoutResult::Pass,
2563        } => "TIMEOUT-PASS".into(),
2564        ExecutionResultDescription::Timeout {
2565            result: SlowTimeoutResult::Fail,
2566        } => "TIMEOUT".into(),
2567    }
2568}
2569
2570fn short_status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2571    // Use shorter strings for this (max 6 characters).
2572    match result {
2573        ExecutionResultDescription::Fail {
2574            failure:
2575                FailureDescription::Abort {
2576                    abort: AbortDescription::UnixSignal { signal, name },
2577                },
2578            leaked: _,
2579        } => match name {
2580            Some(s) => s.to_string().into(),
2581            None => format!("SIG {signal}").into(),
2582        },
2583        ExecutionResultDescription::Fail {
2584            failure:
2585                FailureDescription::Abort {
2586                    abort: AbortDescription::WindowsNtStatus { .. },
2587                }
2588                | FailureDescription::Abort {
2589                    abort: AbortDescription::WindowsJobObject,
2590                },
2591            leaked: _,
2592        } => {
2593            // Going to print out the full error message on the following line -- just "ABORT" will
2594            // do for now.
2595            "ABORT".into()
2596        }
2597        ExecutionResultDescription::Fail {
2598            failure: FailureDescription::ExitCode { .. },
2599            leaked: true,
2600        } => "FL+LK".into(),
2601        ExecutionResultDescription::Fail {
2602            failure: FailureDescription::ExitCode { .. },
2603            leaked: false,
2604        } => "FAIL".into(),
2605        ExecutionResultDescription::ExecFail => "XFAIL".into(),
2606        ExecutionResultDescription::Pass => "PASS".into(),
2607        ExecutionResultDescription::Leak {
2608            result: LeakTimeoutResult::Pass,
2609        } => "LEAK".into(),
2610        ExecutionResultDescription::Leak {
2611            result: LeakTimeoutResult::Fail,
2612        } => "LKFAIL".into(),
2613        ExecutionResultDescription::Timeout {
2614            result: SlowTimeoutResult::Pass,
2615        } => "TMPASS".into(),
2616        ExecutionResultDescription::Timeout {
2617            result: SlowTimeoutResult::Fail,
2618        } => "TMT".into(),
2619    }
2620}
2621
2622/// Writes a supplementary line for Windows abort statuses.
2623///
2624/// For Unix signals, this is a no-op since the signal info is displayed inline.
2625fn write_windows_abort_line(
2626    status: &AbortDescription,
2627    styles: &Styles,
2628    writer: &mut dyn WriteStr,
2629) -> io::Result<()> {
2630    match status {
2631        AbortDescription::UnixSignal { .. } => {
2632            // Unix signal info is displayed inline, no separate line needed.
2633            Ok(())
2634        }
2635        AbortDescription::WindowsNtStatus { code, message } => {
2636            // For subsequent lines, use an indented displayer with {:>12}
2637            // (ensuring that message lines are aligned).
2638            const INDENT: &str = "           - ";
2639            let mut indented = indented(writer).with_str(INDENT).skip_initial();
2640            // Format code as 10 characters ("0x" + 8 hex digits) for uniformity.
2641            let code_str = format!("{:#010x}", code.style(styles.count));
2642            let status_str = match message {
2643                Some(msg) => format!("{code_str}: {msg}"),
2644                None => code_str,
2645            };
2646            writeln!(
2647                indented,
2648                "{:>12} {} {}",
2649                "-",
2650                "with code".style(styles.fail),
2651                status_str,
2652            )?;
2653            indented.write_str_flush()
2654        }
2655        AbortDescription::WindowsJobObject => {
2656            writeln!(
2657                writer,
2658                "{:>12} {} via {}",
2659                "-",
2660                "terminated".style(styles.fail),
2661                "job object".style(styles.count),
2662            )
2663        }
2664    }
2665}
2666
2667#[cfg(test)]
2668mod tests {
2669    use super::*;
2670    use crate::{
2671        errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
2672        reporter::{
2673            ShowProgress,
2674            events::{
2675                ChildExecutionOutputDescription, ExecutionResult, FailureStatus,
2676                UnitTerminateReason,
2677            },
2678        },
2679        test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
2680    };
2681    use bytes::Bytes;
2682    use chrono::Local;
2683    use nextest_metadata::{RustBinaryId, TestCaseName};
2684    use quick_junit::ReportUuid;
2685    use smol_str::SmolStr;
2686    use std::{num::NonZero, sync::Arc};
2687
2688    /// Creates a test reporter with default settings and calls the given function with it.
2689    ///
2690    /// Returns the output written to the reporter.
2691    fn with_reporter<'a, F>(f: F, out: &'a mut String)
2692    where
2693        F: FnOnce(DisplayReporter<'a>),
2694    {
2695        with_reporter_impl(f, out, false)
2696    }
2697
2698    /// Creates a test reporter with verbose mode enabled.
2699    fn with_verbose_reporter<'a, F>(f: F, out: &'a mut String)
2700    where
2701        F: FnOnce(DisplayReporter<'a>),
2702    {
2703        with_reporter_impl(f, out, true)
2704    }
2705
2706    fn with_reporter_impl<'a, F>(f: F, out: &'a mut String, verbose: bool)
2707    where
2708        F: FnOnce(DisplayReporter<'a>),
2709    {
2710        let builder = DisplayReporterBuilder {
2711            mode: NextestRunMode::Test,
2712            default_filter: CompiledDefaultFilter::for_default_config(),
2713            display_config: DisplayConfig {
2714                show_progress: ShowProgress::Counter,
2715                no_capture: true,
2716                status_level: Some(StatusLevel::Fail),
2717                final_status_level: Some(FinalStatusLevel::Fail),
2718                profile_status_level: StatusLevel::Fail,
2719                profile_final_status_level: FinalStatusLevel::Fail,
2720            },
2721            run_count: 5000,
2722            success_output: Some(TestOutputDisplay::Immediate),
2723            failure_output: Some(TestOutputDisplay::Immediate),
2724            should_colorize: false,
2725            verbose,
2726            no_output_indent: false,
2727            max_progress_running: MaxProgressRunning::default(),
2728            show_term_progress: ShowTerminalProgress::No,
2729            displayer_kind: DisplayerKind::Live,
2730        };
2731
2732        let output = ReporterOutput::Writer {
2733            writer: out,
2734            use_unicode: true,
2735        };
2736        let reporter = builder.build(output);
2737        f(reporter);
2738    }
2739
2740    fn make_split_output(
2741        result: Option<ExecutionResult>,
2742        stdout: &str,
2743        stderr: &str,
2744    ) -> ChildExecutionOutputDescription<LiveSpec> {
2745        ChildExecutionOutput::Output {
2746            result,
2747            output: ChildOutput::Split(ChildSplitOutput {
2748                stdout: Some(Bytes::from(stdout.to_owned()).into()),
2749                stderr: Some(Bytes::from(stderr.to_owned()).into()),
2750            }),
2751            errors: None,
2752        }
2753        .into()
2754    }
2755
2756    fn make_split_output_with_errors(
2757        result: Option<ExecutionResult>,
2758        stdout: &str,
2759        stderr: &str,
2760        errors: Vec<ChildError>,
2761    ) -> ChildExecutionOutputDescription<LiveSpec> {
2762        ChildExecutionOutput::Output {
2763            result,
2764            output: ChildOutput::Split(ChildSplitOutput {
2765                stdout: Some(Bytes::from(stdout.to_owned()).into()),
2766                stderr: Some(Bytes::from(stderr.to_owned()).into()),
2767            }),
2768            errors: ErrorList::new("testing split output", errors),
2769        }
2770        .into()
2771    }
2772
2773    fn make_combined_output_with_errors(
2774        result: Option<ExecutionResult>,
2775        output: &str,
2776        errors: Vec<ChildError>,
2777    ) -> ChildExecutionOutputDescription<LiveSpec> {
2778        ChildExecutionOutput::Output {
2779            result,
2780            output: ChildOutput::Combined {
2781                output: Bytes::from(output.to_owned()).into(),
2782            },
2783            errors: ErrorList::new("testing split output", errors),
2784        }
2785        .into()
2786    }
2787
2788    /// Helper to build a passing `FinalOutput`.
2789    fn make_pass_output() -> FinalOutput {
2790        let status = ExecuteStatus {
2791            retry_data: RetryData {
2792                attempt: 1,
2793                total_attempts: 1,
2794            },
2795            output: make_split_output(Some(ExecutionResult::Pass), "", ""),
2796            result: ExecutionResultDescription::Pass,
2797            start_time: Local::now().into(),
2798            time_taken: Duration::from_secs(1),
2799            is_slow: false,
2800            delay_before_start: Duration::ZERO,
2801            error_summary: None,
2802            output_error_slice: None,
2803        };
2804        FinalOutput::Executed {
2805            run_statuses: ExecutionStatuses::new(vec![status]),
2806            display_output: false,
2807        }
2808    }
2809
2810    /// Helper to build a failing `FinalOutput`.
2811    fn make_fail_output() -> FinalOutput {
2812        let result = ExecutionResult::Fail {
2813            failure_status: FailureStatus::ExitCode(1),
2814            leaked: false,
2815        };
2816        let status = ExecuteStatus {
2817            retry_data: RetryData {
2818                attempt: 1,
2819                total_attempts: 1,
2820            },
2821            output: make_split_output(Some(result), "", ""),
2822            result: ExecutionResultDescription::from(result),
2823            start_time: Local::now().into(),
2824            time_taken: Duration::from_secs(1),
2825            is_slow: false,
2826            delay_before_start: Duration::ZERO,
2827            error_summary: None,
2828            output_error_slice: None,
2829        };
2830        FinalOutput::Executed {
2831            run_statuses: ExecutionStatuses::new(vec![status]),
2832            display_output: false,
2833        }
2834    }
2835
2836    /// Helper to build a skipped `FinalOutput`.
2837    fn make_skip_output() -> FinalOutput {
2838        FinalOutput::Skipped(MismatchReason::Ignored)
2839    }
2840
2841    /// Extract `(binary_id, test_name)` pairs from sorted entries for assertion.
2842    fn extract_ids<'a>(entries: &[FinalOutputEntry<'a>]) -> Vec<(&'a str, &'a str)> {
2843        entries
2844            .iter()
2845            .map(|e| (e.instance.binary_id.as_str(), e.instance.test_name.as_str()))
2846            .collect()
2847    }
2848
2849    #[test]
2850    fn final_status_line() {
2851        let binary_id = RustBinaryId::new("my-binary-id");
2852        let test_name = TestCaseName::new("test1");
2853        let test_instance = TestInstanceId {
2854            binary_id: &binary_id,
2855            test_name: &test_name,
2856        };
2857
2858        let fail_result_internal = ExecutionResult::Fail {
2859            failure_status: FailureStatus::ExitCode(1),
2860            leaked: false,
2861        };
2862        let fail_result = ExecutionResultDescription::from(fail_result_internal);
2863
2864        let fail_status = ExecuteStatus {
2865            retry_data: RetryData {
2866                attempt: 1,
2867                total_attempts: 2,
2868            },
2869            // output is not relevant here.
2870            output: make_split_output(Some(fail_result_internal), "", ""),
2871            result: fail_result.clone(),
2872            start_time: Local::now().into(),
2873            time_taken: Duration::from_secs(1),
2874            is_slow: false,
2875            delay_before_start: Duration::ZERO,
2876            error_summary: None,
2877            output_error_slice: None,
2878        };
2879        let fail_describe = ExecutionDescription::Failure {
2880            first_status: &fail_status,
2881            last_status: &fail_status,
2882            retries: &[],
2883        };
2884
2885        let flaky_status = ExecuteStatus {
2886            retry_data: RetryData {
2887                attempt: 2,
2888                total_attempts: 2,
2889            },
2890            // output is not relevant here.
2891            output: make_split_output(Some(fail_result_internal), "", ""),
2892            result: ExecutionResultDescription::Pass,
2893            start_time: Local::now().into(),
2894            time_taken: Duration::from_secs(2),
2895            is_slow: false,
2896            delay_before_start: Duration::ZERO,
2897            error_summary: None,
2898            output_error_slice: None,
2899        };
2900
2901        // Make an `ExecutionStatuses` with a failure and a success, indicating flakiness.
2902        let statuses = ExecutionStatuses::new(vec![fail_status.clone(), flaky_status]);
2903        let flaky_describe = statuses.describe();
2904
2905        let mut out = String::new();
2906
2907        with_reporter(
2908            |mut reporter| {
2909                // TODO: write a bunch more outputs here.
2910                reporter
2911                    .inner
2912                    .write_final_status_line(
2913                        None,
2914                        TestInstanceCounter::None,
2915                        test_instance,
2916                        fail_describe,
2917                        reporter.output.writer_mut().unwrap(),
2918                    )
2919                    .unwrap();
2920
2921                reporter
2922                    .inner
2923                    .write_final_status_line(
2924                        Some(StressIndex {
2925                            current: 1,
2926                            total: None,
2927                        }),
2928                        TestInstanceCounter::Padded,
2929                        test_instance,
2930                        flaky_describe,
2931                        reporter.output.writer_mut().unwrap(),
2932                    )
2933                    .unwrap();
2934
2935                reporter
2936                    .inner
2937                    .write_final_status_line(
2938                        Some(StressIndex {
2939                            current: 2,
2940                            total: Some(NonZero::new(3).unwrap()),
2941                        }),
2942                        TestInstanceCounter::Counter {
2943                            current: 20,
2944                            total: 5000,
2945                        },
2946                        test_instance,
2947                        flaky_describe,
2948                        reporter.output.writer_mut().unwrap(),
2949                    )
2950                    .unwrap();
2951            },
2952            &mut out,
2953        );
2954
2955        insta::assert_snapshot!("final_status_output", out,);
2956    }
2957
2958    #[test]
2959    fn status_line_all_variants() {
2960        let binary_id = RustBinaryId::new("my-binary-id");
2961        let test_name = TestCaseName::new("test_name");
2962        let test_instance = TestInstanceId {
2963            binary_id: &binary_id,
2964            test_name: &test_name,
2965        };
2966
2967        // --- Success result types ---
2968        let pass_result_internal = ExecutionResult::Pass;
2969        let pass_result = ExecutionResultDescription::from(pass_result_internal);
2970
2971        let leak_pass_result_internal = ExecutionResult::Leak {
2972            result: LeakTimeoutResult::Pass,
2973        };
2974        let leak_pass_result = ExecutionResultDescription::from(leak_pass_result_internal);
2975
2976        let timeout_pass_result_internal = ExecutionResult::Timeout {
2977            result: SlowTimeoutResult::Pass,
2978        };
2979        let timeout_pass_result = ExecutionResultDescription::from(timeout_pass_result_internal);
2980
2981        // --- Failure result types ---
2982        let fail_result_internal = ExecutionResult::Fail {
2983            failure_status: FailureStatus::ExitCode(1),
2984            leaked: false,
2985        };
2986        let fail_result = ExecutionResultDescription::from(fail_result_internal);
2987
2988        let fail_leak_result_internal = ExecutionResult::Fail {
2989            failure_status: FailureStatus::ExitCode(1),
2990            leaked: true,
2991        };
2992        let fail_leak_result = ExecutionResultDescription::from(fail_leak_result_internal);
2993
2994        let exec_fail_result_internal = ExecutionResult::ExecFail;
2995        let exec_fail_result = ExecutionResultDescription::from(exec_fail_result_internal);
2996
2997        let leak_fail_result_internal = ExecutionResult::Leak {
2998            result: LeakTimeoutResult::Fail,
2999        };
3000        let leak_fail_result = ExecutionResultDescription::from(leak_fail_result_internal);
3001
3002        let timeout_fail_result_internal = ExecutionResult::Timeout {
3003            result: SlowTimeoutResult::Fail,
3004        };
3005        let timeout_fail_result = ExecutionResultDescription::from(timeout_fail_result_internal);
3006
3007        // Construct abort results directly as ExecutionResultDescription (platform-independent).
3008        let abort_unix_result = ExecutionResultDescription::Fail {
3009            failure: FailureDescription::Abort {
3010                abort: AbortDescription::UnixSignal {
3011                    signal: 11,
3012                    name: Some("SEGV".into()),
3013                },
3014            },
3015            leaked: false,
3016        };
3017        let abort_windows_result = ExecutionResultDescription::Fail {
3018            failure: FailureDescription::Abort {
3019                abort: AbortDescription::WindowsNtStatus {
3020                    // STATUS_ACCESS_VIOLATION = 0xC0000005
3021                    code: 0xC0000005_u32 as i32,
3022                    message: Some("Access violation".into()),
3023                },
3024            },
3025            leaked: false,
3026        };
3027
3028        // --- Success statuses (is_slow = false) ---
3029        let pass_status = ExecuteStatus {
3030            retry_data: RetryData {
3031                attempt: 1,
3032                total_attempts: 1,
3033            },
3034            output: make_split_output(Some(pass_result_internal), "", ""),
3035            result: pass_result.clone(),
3036            start_time: Local::now().into(),
3037            time_taken: Duration::from_secs(1),
3038            is_slow: false,
3039            delay_before_start: Duration::ZERO,
3040            error_summary: None,
3041            output_error_slice: None,
3042        };
3043
3044        let leak_pass_status = ExecuteStatus {
3045            retry_data: RetryData {
3046                attempt: 1,
3047                total_attempts: 1,
3048            },
3049            output: make_split_output(Some(leak_pass_result_internal), "", ""),
3050            result: leak_pass_result.clone(),
3051            start_time: Local::now().into(),
3052            time_taken: Duration::from_secs(2),
3053            is_slow: false,
3054            delay_before_start: Duration::ZERO,
3055            error_summary: None,
3056            output_error_slice: None,
3057        };
3058
3059        let timeout_pass_status = ExecuteStatus {
3060            retry_data: RetryData {
3061                attempt: 1,
3062                total_attempts: 1,
3063            },
3064            output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3065            result: timeout_pass_result.clone(),
3066            start_time: Local::now().into(),
3067            time_taken: Duration::from_secs(240),
3068            is_slow: false,
3069            delay_before_start: Duration::ZERO,
3070            error_summary: None,
3071            output_error_slice: None,
3072        };
3073
3074        // --- Success statuses (is_slow = true) ---
3075        let pass_slow_status = ExecuteStatus {
3076            retry_data: RetryData {
3077                attempt: 1,
3078                total_attempts: 1,
3079            },
3080            output: make_split_output(Some(pass_result_internal), "", ""),
3081            result: pass_result.clone(),
3082            start_time: Local::now().into(),
3083            time_taken: Duration::from_secs(30),
3084            is_slow: true,
3085            delay_before_start: Duration::ZERO,
3086            error_summary: None,
3087            output_error_slice: None,
3088        };
3089
3090        let leak_pass_slow_status = ExecuteStatus {
3091            retry_data: RetryData {
3092                attempt: 1,
3093                total_attempts: 1,
3094            },
3095            output: make_split_output(Some(leak_pass_result_internal), "", ""),
3096            result: leak_pass_result.clone(),
3097            start_time: Local::now().into(),
3098            time_taken: Duration::from_secs(30),
3099            is_slow: true,
3100            delay_before_start: Duration::ZERO,
3101            error_summary: None,
3102            output_error_slice: None,
3103        };
3104
3105        let timeout_pass_slow_status = ExecuteStatus {
3106            retry_data: RetryData {
3107                attempt: 1,
3108                total_attempts: 1,
3109            },
3110            output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3111            result: timeout_pass_result.clone(),
3112            start_time: Local::now().into(),
3113            time_taken: Duration::from_secs(300),
3114            is_slow: true,
3115            delay_before_start: Duration::ZERO,
3116            error_summary: None,
3117            output_error_slice: None,
3118        };
3119
3120        // --- Flaky statuses ---
3121        let flaky_first_status = ExecuteStatus {
3122            retry_data: RetryData {
3123                attempt: 1,
3124                total_attempts: 2,
3125            },
3126            output: make_split_output(Some(fail_result_internal), "", ""),
3127            result: fail_result.clone(),
3128            start_time: Local::now().into(),
3129            time_taken: Duration::from_secs(1),
3130            is_slow: false,
3131            delay_before_start: Duration::ZERO,
3132            error_summary: None,
3133            output_error_slice: None,
3134        };
3135        let flaky_last_status = ExecuteStatus {
3136            retry_data: RetryData {
3137                attempt: 2,
3138                total_attempts: 2,
3139            },
3140            output: make_split_output(Some(pass_result_internal), "", ""),
3141            result: pass_result.clone(),
3142            start_time: Local::now().into(),
3143            time_taken: Duration::from_secs(1),
3144            is_slow: false,
3145            delay_before_start: Duration::ZERO,
3146            error_summary: None,
3147            output_error_slice: None,
3148        };
3149
3150        // --- First-attempt failure statuses ---
3151        let fail_status = ExecuteStatus {
3152            retry_data: RetryData {
3153                attempt: 1,
3154                total_attempts: 1,
3155            },
3156            output: make_split_output(Some(fail_result_internal), "", ""),
3157            result: fail_result.clone(),
3158            start_time: Local::now().into(),
3159            time_taken: Duration::from_secs(1),
3160            is_slow: false,
3161            delay_before_start: Duration::ZERO,
3162            error_summary: None,
3163            output_error_slice: None,
3164        };
3165
3166        let fail_leak_status = ExecuteStatus {
3167            retry_data: RetryData {
3168                attempt: 1,
3169                total_attempts: 1,
3170            },
3171            output: make_split_output(Some(fail_leak_result_internal), "", ""),
3172            result: fail_leak_result.clone(),
3173            start_time: Local::now().into(),
3174            time_taken: Duration::from_secs(1),
3175            is_slow: false,
3176            delay_before_start: Duration::ZERO,
3177            error_summary: None,
3178            output_error_slice: None,
3179        };
3180
3181        let exec_fail_status = ExecuteStatus {
3182            retry_data: RetryData {
3183                attempt: 1,
3184                total_attempts: 1,
3185            },
3186            output: make_split_output(Some(exec_fail_result_internal), "", ""),
3187            result: exec_fail_result.clone(),
3188            start_time: Local::now().into(),
3189            time_taken: Duration::from_secs(1),
3190            is_slow: false,
3191            delay_before_start: Duration::ZERO,
3192            error_summary: None,
3193            output_error_slice: None,
3194        };
3195
3196        let leak_fail_status = ExecuteStatus {
3197            retry_data: RetryData {
3198                attempt: 1,
3199                total_attempts: 1,
3200            },
3201            output: make_split_output(Some(leak_fail_result_internal), "", ""),
3202            result: leak_fail_result.clone(),
3203            start_time: Local::now().into(),
3204            time_taken: Duration::from_secs(1),
3205            is_slow: false,
3206            delay_before_start: Duration::ZERO,
3207            error_summary: None,
3208            output_error_slice: None,
3209        };
3210
3211        let timeout_fail_status = ExecuteStatus {
3212            retry_data: RetryData {
3213                attempt: 1,
3214                total_attempts: 1,
3215            },
3216            output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3217            result: timeout_fail_result.clone(),
3218            start_time: Local::now().into(),
3219            time_taken: Duration::from_secs(60),
3220            is_slow: false,
3221            delay_before_start: Duration::ZERO,
3222            error_summary: None,
3223            output_error_slice: None,
3224        };
3225
3226        let abort_unix_status = ExecuteStatus {
3227            retry_data: RetryData {
3228                attempt: 1,
3229                total_attempts: 1,
3230            },
3231            output: make_split_output(None, "", ""),
3232            result: abort_unix_result.clone(),
3233            start_time: Local::now().into(),
3234            time_taken: Duration::from_secs(1),
3235            is_slow: false,
3236            delay_before_start: Duration::ZERO,
3237            error_summary: None,
3238            output_error_slice: None,
3239        };
3240
3241        let abort_windows_status = ExecuteStatus {
3242            retry_data: RetryData {
3243                attempt: 1,
3244                total_attempts: 1,
3245            },
3246            output: make_split_output(None, "", ""),
3247            result: abort_windows_result.clone(),
3248            start_time: Local::now().into(),
3249            time_taken: Duration::from_secs(1),
3250            is_slow: false,
3251            delay_before_start: Duration::ZERO,
3252            error_summary: None,
3253            output_error_slice: None,
3254        };
3255
3256        // --- Retry failure statuses ---
3257        let fail_retry_status = ExecuteStatus {
3258            retry_data: RetryData {
3259                attempt: 2,
3260                total_attempts: 2,
3261            },
3262            output: make_split_output(Some(fail_result_internal), "", ""),
3263            result: fail_result.clone(),
3264            start_time: Local::now().into(),
3265            time_taken: Duration::from_secs(1),
3266            is_slow: false,
3267            delay_before_start: Duration::ZERO,
3268            error_summary: None,
3269            output_error_slice: None,
3270        };
3271
3272        let fail_leak_retry_status = ExecuteStatus {
3273            retry_data: RetryData {
3274                attempt: 2,
3275                total_attempts: 2,
3276            },
3277            output: make_split_output(Some(fail_leak_result_internal), "", ""),
3278            result: fail_leak_result.clone(),
3279            start_time: Local::now().into(),
3280            time_taken: Duration::from_secs(1),
3281            is_slow: false,
3282            delay_before_start: Duration::ZERO,
3283            error_summary: None,
3284            output_error_slice: None,
3285        };
3286
3287        let leak_fail_retry_status = ExecuteStatus {
3288            retry_data: RetryData {
3289                attempt: 2,
3290                total_attempts: 2,
3291            },
3292            output: make_split_output(Some(leak_fail_result_internal), "", ""),
3293            result: leak_fail_result.clone(),
3294            start_time: Local::now().into(),
3295            time_taken: Duration::from_secs(1),
3296            is_slow: false,
3297            delay_before_start: Duration::ZERO,
3298            error_summary: None,
3299            output_error_slice: None,
3300        };
3301
3302        let timeout_fail_retry_status = ExecuteStatus {
3303            retry_data: RetryData {
3304                attempt: 2,
3305                total_attempts: 2,
3306            },
3307            output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3308            result: timeout_fail_result.clone(),
3309            start_time: Local::now().into(),
3310            time_taken: Duration::from_secs(60),
3311            is_slow: false,
3312            delay_before_start: Duration::ZERO,
3313            error_summary: None,
3314            output_error_slice: None,
3315        };
3316
3317        // --- Build descriptions ---
3318        let pass_describe = ExecutionDescription::Success {
3319            single_status: &pass_status,
3320        };
3321        let leak_pass_describe = ExecutionDescription::Success {
3322            single_status: &leak_pass_status,
3323        };
3324        let timeout_pass_describe = ExecutionDescription::Success {
3325            single_status: &timeout_pass_status,
3326        };
3327        let pass_slow_describe = ExecutionDescription::Success {
3328            single_status: &pass_slow_status,
3329        };
3330        let leak_pass_slow_describe = ExecutionDescription::Success {
3331            single_status: &leak_pass_slow_status,
3332        };
3333        let timeout_pass_slow_describe = ExecutionDescription::Success {
3334            single_status: &timeout_pass_slow_status,
3335        };
3336        let flaky_describe = ExecutionDescription::Flaky {
3337            last_status: &flaky_last_status,
3338            prior_statuses: std::slice::from_ref(&flaky_first_status),
3339        };
3340        let fail_describe = ExecutionDescription::Failure {
3341            first_status: &fail_status,
3342            last_status: &fail_status,
3343            retries: &[],
3344        };
3345        let fail_leak_describe = ExecutionDescription::Failure {
3346            first_status: &fail_leak_status,
3347            last_status: &fail_leak_status,
3348            retries: &[],
3349        };
3350        let exec_fail_describe = ExecutionDescription::Failure {
3351            first_status: &exec_fail_status,
3352            last_status: &exec_fail_status,
3353            retries: &[],
3354        };
3355        let leak_fail_describe = ExecutionDescription::Failure {
3356            first_status: &leak_fail_status,
3357            last_status: &leak_fail_status,
3358            retries: &[],
3359        };
3360        let timeout_fail_describe = ExecutionDescription::Failure {
3361            first_status: &timeout_fail_status,
3362            last_status: &timeout_fail_status,
3363            retries: &[],
3364        };
3365        let abort_unix_describe = ExecutionDescription::Failure {
3366            first_status: &abort_unix_status,
3367            last_status: &abort_unix_status,
3368            retries: &[],
3369        };
3370        let abort_windows_describe = ExecutionDescription::Failure {
3371            first_status: &abort_windows_status,
3372            last_status: &abort_windows_status,
3373            retries: &[],
3374        };
3375        let fail_retry_describe = ExecutionDescription::Failure {
3376            first_status: &fail_status,
3377            last_status: &fail_retry_status,
3378            retries: std::slice::from_ref(&fail_retry_status),
3379        };
3380        let fail_leak_retry_describe = ExecutionDescription::Failure {
3381            first_status: &fail_leak_status,
3382            last_status: &fail_leak_retry_status,
3383            retries: std::slice::from_ref(&fail_leak_retry_status),
3384        };
3385        let leak_fail_retry_describe = ExecutionDescription::Failure {
3386            first_status: &leak_fail_status,
3387            last_status: &leak_fail_retry_status,
3388            retries: std::slice::from_ref(&leak_fail_retry_status),
3389        };
3390        let timeout_fail_retry_describe = ExecutionDescription::Failure {
3391            first_status: &timeout_fail_status,
3392            last_status: &timeout_fail_retry_status,
3393            retries: std::slice::from_ref(&timeout_fail_retry_status),
3394        };
3395
3396        // Collect all test cases: (label, description).
3397        // The label helps identify each case in the snapshot.
3398        let test_cases: Vec<(&str, ExecutionDescription<'_, LiveSpec>)> = vec![
3399            // Success variants (is_slow = false).
3400            ("pass", pass_describe),
3401            ("leak pass", leak_pass_describe),
3402            ("timeout pass", timeout_pass_describe),
3403            // Success variants (is_slow = true) - only different for Final.
3404            ("pass slow", pass_slow_describe),
3405            ("leak pass slow", leak_pass_slow_describe),
3406            ("timeout pass slow", timeout_pass_slow_describe),
3407            // Flaky variant.
3408            ("flaky", flaky_describe),
3409            // First-attempt failure variants.
3410            ("fail", fail_describe),
3411            ("fail leak", fail_leak_describe),
3412            ("exec fail", exec_fail_describe),
3413            ("leak fail", leak_fail_describe),
3414            ("timeout fail", timeout_fail_describe),
3415            ("abort unix", abort_unix_describe),
3416            ("abort windows", abort_windows_describe),
3417            // Retry failure variants.
3418            ("fail retry", fail_retry_describe),
3419            ("fail leak retry", fail_leak_retry_describe),
3420            ("leak fail retry", leak_fail_retry_describe),
3421            ("timeout fail retry", timeout_fail_retry_describe),
3422        ];
3423
3424        let mut out = String::new();
3425        let mut counter = 0usize;
3426
3427        with_reporter(
3428            |mut reporter| {
3429                let writer = reporter.output.writer_mut().unwrap();
3430
3431                // Loop over both StatusLineKind variants.
3432                for (kind_name, kind) in [
3433                    ("intermediate", StatusLineKind::Intermediate),
3434                    ("final", StatusLineKind::Final),
3435                ] {
3436                    writeln!(writer, "=== {kind_name} ===").unwrap();
3437
3438                    for (label, describe) in &test_cases {
3439                        counter += 1;
3440                        let test_counter = TestInstanceCounter::Counter {
3441                            current: counter,
3442                            total: 100,
3443                        };
3444
3445                        // Write label as a comment for clarity in snapshot.
3446                        writeln!(writer, "# {label}: ").unwrap();
3447
3448                        reporter
3449                            .inner
3450                            .write_status_line_impl(
3451                                None,
3452                                test_counter,
3453                                test_instance,
3454                                *describe,
3455                                kind,
3456                                writer,
3457                            )
3458                            .unwrap();
3459                    }
3460                }
3461            },
3462            &mut out,
3463        );
3464
3465        insta::assert_snapshot!("status_line_all_variants", out);
3466    }
3467
3468    #[test]
3469    fn test_summary_line() {
3470        let run_id = ReportUuid::nil();
3471        let mut out = String::new();
3472
3473        with_reporter(
3474            |mut reporter| {
3475                // Test single run with all passing tests
3476                let run_stats_success = RunStats {
3477                    initial_run_count: 5,
3478                    finished_count: 5,
3479                    setup_scripts_initial_count: 0,
3480                    setup_scripts_finished_count: 0,
3481                    setup_scripts_passed: 0,
3482                    setup_scripts_failed: 0,
3483                    setup_scripts_exec_failed: 0,
3484                    setup_scripts_timed_out: 0,
3485                    passed: 5,
3486                    passed_slow: 0,
3487                    passed_timed_out: 0,
3488                    flaky: 0,
3489                    failed: 0,
3490                    failed_slow: 0,
3491                    failed_timed_out: 0,
3492                    leaky: 0,
3493                    leaky_failed: 0,
3494                    exec_failed: 0,
3495                    skipped: 0,
3496                    cancel_reason: None,
3497                };
3498
3499                reporter
3500                    .write_event(&TestEvent {
3501                        timestamp: Local::now().into(),
3502                        elapsed: Duration::ZERO,
3503                        kind: TestEventKind::RunFinished {
3504                            run_id,
3505                            start_time: Local::now().into(),
3506                            elapsed: Duration::from_secs(2),
3507                            run_stats: RunFinishedStats::Single(run_stats_success),
3508                            outstanding_not_seen: None,
3509                        },
3510                    })
3511                    .unwrap();
3512
3513                // Test single run with mixed results
3514                let run_stats_mixed = RunStats {
3515                    initial_run_count: 10,
3516                    finished_count: 8,
3517                    setup_scripts_initial_count: 1,
3518                    setup_scripts_finished_count: 1,
3519                    setup_scripts_passed: 1,
3520                    setup_scripts_failed: 0,
3521                    setup_scripts_exec_failed: 0,
3522                    setup_scripts_timed_out: 0,
3523                    passed: 5,
3524                    passed_slow: 1,
3525                    passed_timed_out: 2,
3526                    flaky: 1,
3527                    failed: 2,
3528                    failed_slow: 0,
3529                    failed_timed_out: 1,
3530                    leaky: 1,
3531                    leaky_failed: 0,
3532                    exec_failed: 1,
3533                    skipped: 2,
3534                    cancel_reason: Some(CancelReason::Signal),
3535                };
3536
3537                reporter
3538                    .write_event(&TestEvent {
3539                        timestamp: Local::now().into(),
3540                        elapsed: Duration::ZERO,
3541                        kind: TestEventKind::RunFinished {
3542                            run_id,
3543                            start_time: Local::now().into(),
3544                            elapsed: Duration::from_millis(15750),
3545                            run_stats: RunFinishedStats::Single(run_stats_mixed),
3546                            outstanding_not_seen: None,
3547                        },
3548                    })
3549                    .unwrap();
3550
3551                // Test stress run with success
3552                let stress_stats_success = StressRunStats {
3553                    completed: StressIndex {
3554                        current: 25,
3555                        total: Some(NonZero::new(50).unwrap()),
3556                    },
3557                    success_count: 25,
3558                    failed_count: 0,
3559                    last_final_stats: FinalRunStats::Success,
3560                };
3561
3562                reporter
3563                    .write_event(&TestEvent {
3564                        timestamp: Local::now().into(),
3565                        elapsed: Duration::ZERO,
3566                        kind: TestEventKind::RunFinished {
3567                            run_id,
3568                            start_time: Local::now().into(),
3569                            elapsed: Duration::from_secs(120),
3570                            run_stats: RunFinishedStats::Stress(stress_stats_success),
3571                            outstanding_not_seen: None,
3572                        },
3573                    })
3574                    .unwrap();
3575
3576                // Test stress run with failures and cancellation
3577                let stress_stats_failed = StressRunStats {
3578                    completed: StressIndex {
3579                        current: 15,
3580                        total: None, // Unlimited iterations
3581                    },
3582                    success_count: 12,
3583                    failed_count: 3,
3584                    last_final_stats: FinalRunStats::Cancelled {
3585                        reason: Some(CancelReason::Interrupt),
3586                        kind: RunStatsFailureKind::SetupScript,
3587                    },
3588                };
3589
3590                reporter
3591                    .write_event(&TestEvent {
3592                        timestamp: Local::now().into(),
3593                        elapsed: Duration::ZERO,
3594                        kind: TestEventKind::RunFinished {
3595                            run_id,
3596                            start_time: Local::now().into(),
3597                            elapsed: Duration::from_millis(45250),
3598                            run_stats: RunFinishedStats::Stress(stress_stats_failed),
3599                            outstanding_not_seen: None,
3600                        },
3601                    })
3602                    .unwrap();
3603
3604                // Test no tests run case
3605                let run_stats_empty = RunStats {
3606                    initial_run_count: 0,
3607                    finished_count: 0,
3608                    setup_scripts_initial_count: 0,
3609                    setup_scripts_finished_count: 0,
3610                    setup_scripts_passed: 0,
3611                    setup_scripts_failed: 0,
3612                    setup_scripts_exec_failed: 0,
3613                    setup_scripts_timed_out: 0,
3614                    passed: 0,
3615                    passed_slow: 0,
3616                    passed_timed_out: 0,
3617                    flaky: 0,
3618                    failed: 0,
3619                    failed_slow: 0,
3620                    failed_timed_out: 0,
3621                    leaky: 0,
3622                    leaky_failed: 0,
3623                    exec_failed: 0,
3624                    skipped: 0,
3625                    cancel_reason: None,
3626                };
3627
3628                reporter
3629                    .write_event(&TestEvent {
3630                        timestamp: Local::now().into(),
3631                        elapsed: Duration::ZERO,
3632                        kind: TestEventKind::RunFinished {
3633                            run_id,
3634                            start_time: Local::now().into(),
3635                            elapsed: Duration::from_millis(100),
3636                            run_stats: RunFinishedStats::Single(run_stats_empty),
3637                            outstanding_not_seen: None,
3638                        },
3639                    })
3640                    .unwrap();
3641            },
3642            &mut out,
3643        );
3644
3645        insta::assert_snapshot!("summary_line_output", out,);
3646    }
3647
3648    // ---
3649
3650    /// Send an information response to the reporter and return the output.
3651    #[test]
3652    fn test_info_response() {
3653        let args = vec!["arg1".to_string(), "arg2".to_string()];
3654        let binary_id = RustBinaryId::new("my-binary-id");
3655        let test_name1 = TestCaseName::new("test1");
3656        let test_name2 = TestCaseName::new("test2");
3657        let test_name3 = TestCaseName::new("test3");
3658        let test_name4 = TestCaseName::new("test4");
3659
3660        let mut out = String::new();
3661
3662        with_reporter(
3663            |mut reporter| {
3664                // Info started event.
3665                reporter
3666                    .write_event(&TestEvent {
3667                        timestamp: Local::now().into(),
3668                        elapsed: Duration::ZERO,
3669                        kind: TestEventKind::InfoStarted {
3670                            total: 30,
3671                            run_stats: RunStats {
3672                                initial_run_count: 40,
3673                                finished_count: 20,
3674                                setup_scripts_initial_count: 1,
3675                                setup_scripts_finished_count: 1,
3676                                setup_scripts_passed: 1,
3677                                setup_scripts_failed: 0,
3678                                setup_scripts_exec_failed: 0,
3679                                setup_scripts_timed_out: 0,
3680                                passed: 17,
3681                                passed_slow: 4,
3682                                passed_timed_out: 3,
3683                                flaky: 2,
3684                                failed: 2,
3685                                failed_slow: 1,
3686                                failed_timed_out: 1,
3687                                leaky: 1,
3688                                leaky_failed: 2,
3689                                exec_failed: 1,
3690                                skipped: 5,
3691                                cancel_reason: None,
3692                            },
3693                        },
3694                    })
3695                    .unwrap();
3696
3697                // A basic setup script.
3698                reporter
3699                    .write_event(&TestEvent {
3700                        timestamp: Local::now().into(),
3701                        elapsed: Duration::ZERO,
3702                        kind: TestEventKind::InfoResponse {
3703                            index: 0,
3704                            total: 20,
3705                            // Technically, you won't get setup script and test responses in the
3706                            // same response, but it's easiest to test in this manner.
3707                            response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3708                                stress_index: None,
3709                                script_id: ScriptId::new(SmolStr::new("setup")).unwrap(),
3710                                program: "setup".to_owned(),
3711                                args: args.clone(),
3712                                state: UnitState::Running {
3713                                    pid: 4567,
3714                                    time_taken: Duration::from_millis(1234),
3715                                    slow_after: None,
3716                                },
3717                                output: make_split_output(
3718                                    None,
3719                                    "script stdout 1",
3720                                    "script stderr 1",
3721                                ),
3722                            }),
3723                        },
3724                    })
3725                    .unwrap();
3726
3727                // A setup script with a slow warning, combined output, and an
3728                // execution failure.
3729                reporter
3730                    .write_event(&TestEvent {
3731                        timestamp: Local::now().into(),
3732                        elapsed: Duration::ZERO,
3733                        kind: TestEventKind::InfoResponse {
3734                            index: 1,
3735                            total: 20,
3736                            response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3737                                stress_index: None,
3738                                script_id: ScriptId::new(SmolStr::new("setup-slow")).unwrap(),
3739                                program: "setup-slow".to_owned(),
3740                                args: args.clone(),
3741                                state: UnitState::Running {
3742                                    pid: 4568,
3743                                    time_taken: Duration::from_millis(1234),
3744                                    slow_after: Some(Duration::from_millis(1000)),
3745                                },
3746                                output: make_combined_output_with_errors(
3747                                    None,
3748                                    "script output 2\n",
3749                                    vec![ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3750                                        std::io::Error::other("read stdout error"),
3751                                    )))],
3752                                ),
3753                            }),
3754                        },
3755                    })
3756                    .unwrap();
3757
3758                // A setup script that's terminating and has multiple errors.
3759                reporter
3760                    .write_event(&TestEvent {
3761                        timestamp: Local::now().into(),
3762                        elapsed: Duration::ZERO,
3763                        kind: TestEventKind::InfoResponse {
3764                            index: 2,
3765                            total: 20,
3766                            response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3767                                stress_index: None,
3768                                script_id: ScriptId::new(SmolStr::new("setup-terminating"))
3769                                    .unwrap(),
3770                                program: "setup-terminating".to_owned(),
3771                                args: args.clone(),
3772                                state: UnitState::Terminating(UnitTerminatingState {
3773                                    pid: 5094,
3774                                    time_taken: Duration::from_millis(1234),
3775                                    reason: UnitTerminateReason::Signal,
3776                                    method: UnitTerminateMethod::Fake,
3777                                    waiting_duration: Duration::from_millis(6789),
3778                                    remaining: Duration::from_millis(9786),
3779                                }),
3780                                output: make_split_output_with_errors(
3781                                    None,
3782                                    "script output 3\n",
3783                                    "script stderr 3\n",
3784                                    vec![
3785                                        ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3786                                            std::io::Error::other("read stdout error"),
3787                                        ))),
3788                                        ChildError::Fd(ChildFdError::ReadStderr(Arc::new(
3789                                            std::io::Error::other("read stderr error"),
3790                                        ))),
3791                                    ],
3792                                ),
3793                            }),
3794                        },
3795                    })
3796                    .unwrap();
3797
3798                // A setup script that's about to exit along with a start error
3799                // (this is not a real situation but we're just testing out
3800                // various cases).
3801                reporter
3802                    .write_event(&TestEvent {
3803                        timestamp: Local::now().into(),
3804                        elapsed: Duration::ZERO,
3805                        kind: TestEventKind::InfoResponse {
3806                            index: 3,
3807                            total: 20,
3808                            response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3809                                stress_index: Some(StressIndex {
3810                                    current: 0,
3811                                    total: None,
3812                                }),
3813                                script_id: ScriptId::new(SmolStr::new("setup-exiting")).unwrap(),
3814                                program: "setup-exiting".to_owned(),
3815                                args: args.clone(),
3816                                state: UnitState::Exiting {
3817                                    pid: 9987,
3818                                    time_taken: Duration::from_millis(1234),
3819                                    slow_after: Some(Duration::from_millis(1000)),
3820                                    // Even if exit_status is 0, the presence of
3821                                    // exec-fail errors should be considered
3822                                    // part of the output.
3823                                    tentative_result: Some(ExecutionResult::ExecFail),
3824                                    waiting_duration: Duration::from_millis(10467),
3825                                    remaining: Duration::from_millis(335),
3826                                },
3827                                output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3828                                    Arc::new(std::io::Error::other("exec error")),
3829                                ))
3830                                .into(),
3831                            }),
3832                        },
3833                    })
3834                    .unwrap();
3835
3836                // A setup script that has exited.
3837                reporter
3838                    .write_event(&TestEvent {
3839                        timestamp: Local::now().into(),
3840                        elapsed: Duration::ZERO,
3841                        kind: TestEventKind::InfoResponse {
3842                            index: 4,
3843                            total: 20,
3844                            response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3845                                stress_index: Some(StressIndex {
3846                                    current: 1,
3847                                    total: Some(NonZero::new(3).unwrap()),
3848                                }),
3849                                script_id: ScriptId::new(SmolStr::new("setup-exited")).unwrap(),
3850                                program: "setup-exited".to_owned(),
3851                                args: args.clone(),
3852                                state: UnitState::Exited {
3853                                    result: ExecutionResult::Fail {
3854                                        failure_status: FailureStatus::ExitCode(1),
3855                                        leaked: true,
3856                                    },
3857                                    time_taken: Duration::from_millis(9999),
3858                                    slow_after: Some(Duration::from_millis(3000)),
3859                                },
3860                                output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3861                                    Arc::new(std::io::Error::other("exec error")),
3862                                ))
3863                                .into(),
3864                            }),
3865                        },
3866                    })
3867                    .unwrap();
3868
3869                // A test is running.
3870                reporter
3871                    .write_event(&TestEvent {
3872                        timestamp: Local::now().into(),
3873                        elapsed: Duration::ZERO,
3874                        kind: TestEventKind::InfoResponse {
3875                            index: 5,
3876                            total: 20,
3877                            response: InfoResponse::Test(TestInfoResponse {
3878                                stress_index: None,
3879                                test_instance: TestInstanceId {
3880                                    binary_id: &binary_id,
3881                                    test_name: &test_name1,
3882                                },
3883                                retry_data: RetryData {
3884                                    attempt: 1,
3885                                    total_attempts: 1,
3886                                },
3887                                state: UnitState::Running {
3888                                    pid: 12345,
3889                                    time_taken: Duration::from_millis(400),
3890                                    slow_after: None,
3891                                },
3892                                output: make_split_output(None, "abc", "def"),
3893                            }),
3894                        },
3895                    })
3896                    .unwrap();
3897
3898                // A test is being terminated due to a timeout.
3899                reporter
3900                    .write_event(&TestEvent {
3901                        timestamp: Local::now().into(),
3902                        elapsed: Duration::ZERO,
3903                        kind: TestEventKind::InfoResponse {
3904                            index: 6,
3905                            total: 20,
3906                            response: InfoResponse::Test(TestInfoResponse {
3907                                stress_index: Some(StressIndex {
3908                                    current: 0,
3909                                    total: None,
3910                                }),
3911                                test_instance: TestInstanceId {
3912                                    binary_id: &binary_id,
3913                                    test_name: &test_name2,
3914                                },
3915                                retry_data: RetryData {
3916                                    attempt: 2,
3917                                    total_attempts: 3,
3918                                },
3919                                state: UnitState::Terminating(UnitTerminatingState {
3920                                    pid: 12346,
3921                                    time_taken: Duration::from_millis(99999),
3922                                    reason: UnitTerminateReason::Timeout,
3923                                    method: UnitTerminateMethod::Fake,
3924                                    waiting_duration: Duration::from_millis(6789),
3925                                    remaining: Duration::from_millis(9786),
3926                                }),
3927                                output: make_split_output(None, "abc", "def"),
3928                            }),
3929                        },
3930                    })
3931                    .unwrap();
3932
3933                // A test is exiting.
3934                reporter
3935                    .write_event(&TestEvent {
3936                        timestamp: Local::now().into(),
3937                        elapsed: Duration::ZERO,
3938                        kind: TestEventKind::InfoResponse {
3939                            index: 7,
3940                            total: 20,
3941                            response: InfoResponse::Test(TestInfoResponse {
3942                                stress_index: None,
3943                                test_instance: TestInstanceId {
3944                                    binary_id: &binary_id,
3945                                    test_name: &test_name3,
3946                                },
3947                                retry_data: RetryData {
3948                                    attempt: 2,
3949                                    total_attempts: 3,
3950                                },
3951                                state: UnitState::Exiting {
3952                                    pid: 99999,
3953                                    time_taken: Duration::from_millis(99999),
3954                                    slow_after: Some(Duration::from_millis(33333)),
3955                                    tentative_result: None,
3956                                    waiting_duration: Duration::from_millis(1),
3957                                    remaining: Duration::from_millis(999),
3958                                },
3959                                output: make_split_output(None, "abc", "def"),
3960                            }),
3961                        },
3962                    })
3963                    .unwrap();
3964
3965                // A test has exited.
3966                reporter
3967                    .write_event(&TestEvent {
3968                        timestamp: Local::now().into(),
3969                        elapsed: Duration::ZERO,
3970                        kind: TestEventKind::InfoResponse {
3971                            index: 8,
3972                            total: 20,
3973                            response: InfoResponse::Test(TestInfoResponse {
3974                                stress_index: Some(StressIndex {
3975                                    current: 1,
3976                                    total: Some(NonZero::new(3).unwrap()),
3977                                }),
3978                                test_instance: TestInstanceId {
3979                                    binary_id: &binary_id,
3980                                    test_name: &test_name4,
3981                                },
3982                                retry_data: RetryData {
3983                                    attempt: 1,
3984                                    total_attempts: 5,
3985                                },
3986                                state: UnitState::Exited {
3987                                    result: ExecutionResult::Pass,
3988                                    time_taken: Duration::from_millis(99999),
3989                                    slow_after: Some(Duration::from_millis(33333)),
3990                                },
3991                                output: make_combined_output_with_errors(
3992                                    Some(ExecutionResult::Pass),
3993                                    "abc\ndef\nghi\n",
3994                                    vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
3995                                        std::io::Error::other("error waiting"),
3996                                    )))],
3997                                ),
3998                            }),
3999                        },
4000                    })
4001                    .unwrap();
4002
4003                // Delay before next attempt.
4004                reporter
4005                    .write_event(&TestEvent {
4006                        timestamp: Local::now().into(),
4007                        elapsed: Duration::ZERO,
4008                        kind: TestEventKind::InfoResponse {
4009                            index: 9,
4010                            total: 20,
4011                            response: InfoResponse::Test(TestInfoResponse {
4012                                stress_index: None,
4013                                test_instance: TestInstanceId {
4014                                    binary_id: &binary_id,
4015                                    test_name: &test_name4,
4016                                },
4017                                retry_data: RetryData {
4018                                    // Note that even though attempt is 1, we
4019                                    // still show it in the UI in this special
4020                                    // case.
4021                                    attempt: 1,
4022                                    total_attempts: 5,
4023                                },
4024                                state: UnitState::DelayBeforeNextAttempt {
4025                                    previous_result: ExecutionResult::ExecFail,
4026                                    previous_slow: true,
4027                                    waiting_duration: Duration::from_millis(1234),
4028                                    remaining: Duration::from_millis(5678),
4029                                },
4030                                // In reality, the output isn't available at this point,
4031                                // and it shouldn't be shown.
4032                                output: make_combined_output_with_errors(
4033                                    Some(ExecutionResult::Pass),
4034                                    "*** THIS OUTPUT SHOULD BE IGNORED",
4035                                    vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
4036                                        std::io::Error::other(
4037                                            "*** THIS ERROR SHOULD ALSO BE IGNORED",
4038                                        ),
4039                                    )))],
4040                                ),
4041                            }),
4042                        },
4043                    })
4044                    .unwrap();
4045
4046                reporter
4047                    .write_event(&TestEvent {
4048                        timestamp: Local::now().into(),
4049                        elapsed: Duration::ZERO,
4050                        kind: TestEventKind::InfoFinished { missing: 2 },
4051                    })
4052                    .unwrap();
4053            },
4054            &mut out,
4055        );
4056
4057        insta::assert_snapshot!("info_response_output", out,);
4058    }
4059
4060    #[test]
4061    fn verbose_command_line() {
4062        let binary_id = RustBinaryId::new("my-binary-id");
4063        let test_name = TestCaseName::new("test_name");
4064        let test_with_spaces = TestCaseName::new("test_with_spaces");
4065        let test_special_chars = TestCaseName::new("test_special_chars");
4066        let test_retry = TestCaseName::new("test_retry");
4067        let mut out = String::new();
4068
4069        with_verbose_reporter(
4070            |mut reporter| {
4071                let current_stats = RunStats {
4072                    initial_run_count: 10,
4073                    finished_count: 0,
4074                    ..Default::default()
4075                };
4076
4077                // Test a simple command.
4078                reporter
4079                    .write_event(&TestEvent {
4080                        timestamp: Local::now().into(),
4081                        elapsed: Duration::ZERO,
4082                        kind: TestEventKind::TestStarted {
4083                            stress_index: None,
4084                            test_instance: TestInstanceId {
4085                                binary_id: &binary_id,
4086                                test_name: &test_name,
4087                            },
4088                            current_stats,
4089                            running: 1,
4090                            command_line: vec![
4091                                "/path/to/binary".to_string(),
4092                                "--exact".to_string(),
4093                                "test_name".to_string(),
4094                            ],
4095                        },
4096                    })
4097                    .unwrap();
4098
4099                // Test a command with arguments that need quoting.
4100                reporter
4101                    .write_event(&TestEvent {
4102                        timestamp: Local::now().into(),
4103                        elapsed: Duration::ZERO,
4104                        kind: TestEventKind::TestStarted {
4105                            stress_index: None,
4106                            test_instance: TestInstanceId {
4107                                binary_id: &binary_id,
4108                                test_name: &test_with_spaces,
4109                            },
4110                            current_stats,
4111                            running: 2,
4112                            command_line: vec![
4113                                "/path/to/binary".to_string(),
4114                                "--exact".to_string(),
4115                                "test with spaces".to_string(),
4116                                "--flag=value".to_string(),
4117                            ],
4118                        },
4119                    })
4120                    .unwrap();
4121
4122                // Test a command with special characters.
4123                reporter
4124                    .write_event(&TestEvent {
4125                        timestamp: Local::now().into(),
4126                        elapsed: Duration::ZERO,
4127                        kind: TestEventKind::TestStarted {
4128                            stress_index: None,
4129                            test_instance: TestInstanceId {
4130                                binary_id: &binary_id,
4131                                test_name: &test_special_chars,
4132                            },
4133                            current_stats,
4134                            running: 3,
4135                            command_line: vec![
4136                                "/path/to/binary".to_string(),
4137                                "test\"with\"quotes".to_string(),
4138                                "test'with'single".to_string(),
4139                            ],
4140                        },
4141                    })
4142                    .unwrap();
4143
4144                // Test a retry (attempt 2) - should show "TRY 2 START".
4145                reporter
4146                    .write_event(&TestEvent {
4147                        timestamp: Local::now().into(),
4148                        elapsed: Duration::ZERO,
4149                        kind: TestEventKind::TestRetryStarted {
4150                            stress_index: None,
4151                            test_instance: TestInstanceId {
4152                                binary_id: &binary_id,
4153                                test_name: &test_retry,
4154                            },
4155                            retry_data: RetryData {
4156                                attempt: 2,
4157                                total_attempts: 3,
4158                            },
4159                            running: 1,
4160                            command_line: vec![
4161                                "/path/to/binary".to_string(),
4162                                "--exact".to_string(),
4163                                "test_retry".to_string(),
4164                            ],
4165                        },
4166                    })
4167                    .unwrap();
4168
4169                // Test a retry (attempt 3) - should show "TRY 3 START".
4170                reporter
4171                    .write_event(&TestEvent {
4172                        timestamp: Local::now().into(),
4173                        elapsed: Duration::ZERO,
4174                        kind: TestEventKind::TestRetryStarted {
4175                            stress_index: None,
4176                            test_instance: TestInstanceId {
4177                                binary_id: &binary_id,
4178                                test_name: &test_retry,
4179                            },
4180                            retry_data: RetryData {
4181                                attempt: 3,
4182                                total_attempts: 3,
4183                            },
4184                            running: 1,
4185                            command_line: vec![
4186                                "/path/to/binary".to_string(),
4187                                "--exact".to_string(),
4188                                "test_retry".to_string(),
4189                            ],
4190                        },
4191                    })
4192                    .unwrap();
4193            },
4194            &mut out,
4195        );
4196
4197        insta::assert_snapshot!("verbose_command_line", out);
4198    }
4199
4200    #[test]
4201    fn no_capture_settings() {
4202        // Ensure that output settings are ignored with no-capture.
4203        let mut out = String::new();
4204
4205        with_reporter(
4206            |reporter| {
4207                assert!(reporter.inner.no_capture, "no_capture is true");
4208                let overrides = reporter.inner.unit_output.overrides();
4209                assert_eq!(
4210                    overrides.force_failure_output,
4211                    Some(TestOutputDisplay::Never),
4212                    "failure output is never, overriding other settings"
4213                );
4214                assert_eq!(
4215                    overrides.force_success_output,
4216                    Some(TestOutputDisplay::Never),
4217                    "success output is never, overriding other settings"
4218                );
4219                assert_eq!(
4220                    reporter.inner.status_levels.status_level,
4221                    StatusLevel::Pass,
4222                    "status level is pass, overriding other settings"
4223                );
4224            },
4225            &mut out,
4226        );
4227    }
4228
4229    #[test]
4230    fn sort_final_outputs_counter_flag() {
4231        // Sorting the same entries with and without the counter flag should
4232        // produce different orders when counter order and instance order
4233        // diverge. The fourth entry shares a counter value with the third,
4234        // exercising the instance tiebreaker within the same counter.
4235        let binary_a = RustBinaryId::new("aaa");
4236        let binary_b = RustBinaryId::new("bbb");
4237        let test_x = TestCaseName::new("test_x");
4238        let test_y = TestCaseName::new("test_y");
4239
4240        let mut entries = vec![
4241            FinalOutputEntry {
4242                stress_index: None,
4243                counter: TestInstanceCounter::Counter {
4244                    current: 99,
4245                    total: 100,
4246                },
4247                instance: TestInstanceId {
4248                    binary_id: &binary_b,
4249                    test_name: &test_y,
4250                },
4251                output: make_pass_output(),
4252            },
4253            FinalOutputEntry {
4254                stress_index: None,
4255                counter: TestInstanceCounter::Counter {
4256                    current: 1,
4257                    total: 100,
4258                },
4259                instance: TestInstanceId {
4260                    binary_id: &binary_a,
4261                    test_name: &test_y,
4262                },
4263                output: make_pass_output(),
4264            },
4265            FinalOutputEntry {
4266                stress_index: None,
4267                counter: TestInstanceCounter::Counter {
4268                    current: 50,
4269                    total: 100,
4270                },
4271                instance: TestInstanceId {
4272                    binary_id: &binary_a,
4273                    test_name: &test_x,
4274                },
4275                output: make_pass_output(),
4276            },
4277            FinalOutputEntry {
4278                stress_index: None,
4279                counter: TestInstanceCounter::Counter {
4280                    current: 50,
4281                    total: 100,
4282                },
4283                instance: TestInstanceId {
4284                    binary_id: &binary_b,
4285                    test_name: &test_x,
4286                },
4287                output: make_pass_output(),
4288            },
4289        ];
4290
4291        // Without the counter being shown, sort purely by instance.
4292        sort_final_outputs(&mut entries, false);
4293        assert_eq!(
4294            extract_ids(&entries),
4295            vec![
4296                ("aaa", "test_x"),
4297                ("aaa", "test_y"),
4298                ("bbb", "test_x"),
4299                ("bbb", "test_y"),
4300            ],
4301            "without counter, sort is purely by instance"
4302        );
4303
4304        // With the counter being shown, sort by counter first (1, 50, 50, 99),
4305        // then by instance within the same counter value.
4306        sort_final_outputs(&mut entries, true);
4307        assert_eq!(
4308            extract_ids(&entries),
4309            vec![
4310                ("aaa", "test_y"),
4311                ("aaa", "test_x"),
4312                ("bbb", "test_x"),
4313                ("bbb", "test_y"),
4314            ],
4315            "with counter, sort by counter first, then by instance as tiebreaker"
4316        );
4317    }
4318
4319    #[test]
4320    fn sort_final_outputs_mixed_status_levels() {
4321        // Status level is the primary sort key regardless of counter setting.
4322        // Reverse ordering: skip (highest level) first, pass, fail (lowest
4323        // level) last.
4324        let binary_a = RustBinaryId::new("aaa");
4325        let binary_b = RustBinaryId::new("bbb");
4326        let binary_c = RustBinaryId::new("ccc");
4327        let test_1 = TestCaseName::new("test_1");
4328
4329        let mut entries = vec![
4330            FinalOutputEntry {
4331                stress_index: None,
4332                counter: TestInstanceCounter::Counter {
4333                    current: 1,
4334                    total: 100,
4335                },
4336                instance: TestInstanceId {
4337                    binary_id: &binary_a,
4338                    test_name: &test_1,
4339                },
4340                output: make_fail_output(),
4341            },
4342            FinalOutputEntry {
4343                stress_index: None,
4344                counter: TestInstanceCounter::Counter {
4345                    current: 2,
4346                    total: 100,
4347                },
4348                instance: TestInstanceId {
4349                    binary_id: &binary_b,
4350                    test_name: &test_1,
4351                },
4352                output: make_skip_output(),
4353            },
4354            FinalOutputEntry {
4355                stress_index: None,
4356                counter: TestInstanceCounter::Counter {
4357                    current: 3,
4358                    total: 100,
4359                },
4360                instance: TestInstanceId {
4361                    binary_id: &binary_c,
4362                    test_name: &test_1,
4363                },
4364                output: make_pass_output(),
4365            },
4366        ];
4367
4368        // Pass first, then Skip, then Fail last.
4369        sort_final_outputs(&mut entries, false);
4370        assert_eq!(
4371            extract_ids(&entries),
4372            vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4373            "pass first, then skip, then fail (reversed status level)"
4374        );
4375
4376        // Shuffle and re-sort with the counter. This results in the same order
4377        // since the status level is more important than the counter.
4378        entries.swap(0, 2);
4379        sort_final_outputs(&mut entries, true);
4380        assert_eq!(
4381            extract_ids(&entries),
4382            vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4383            "with counter, status level still dominates"
4384        );
4385    }
4386
4387    #[test]
4388    fn sort_final_outputs_stress_indexes() {
4389        // Stress index is the secondary sort key after status level.
4390        let binary_a = RustBinaryId::new("aaa");
4391        let test_1 = TestCaseName::new("test_1");
4392        let test_2 = TestCaseName::new("test_2");
4393
4394        let mut entries = vec![
4395            FinalOutputEntry {
4396                stress_index: Some(StressIndex {
4397                    current: 2,
4398                    total: None,
4399                }),
4400                counter: TestInstanceCounter::Counter {
4401                    current: 1,
4402                    total: 100,
4403                },
4404                instance: TestInstanceId {
4405                    binary_id: &binary_a,
4406                    test_name: &test_1,
4407                },
4408                output: make_pass_output(),
4409            },
4410            FinalOutputEntry {
4411                stress_index: Some(StressIndex {
4412                    current: 0,
4413                    total: None,
4414                }),
4415                counter: TestInstanceCounter::Counter {
4416                    current: 3,
4417                    total: 100,
4418                },
4419                instance: TestInstanceId {
4420                    binary_id: &binary_a,
4421                    test_name: &test_2,
4422                },
4423                output: make_pass_output(),
4424            },
4425            FinalOutputEntry {
4426                stress_index: Some(StressIndex {
4427                    current: 0,
4428                    total: None,
4429                }),
4430                counter: TestInstanceCounter::Counter {
4431                    current: 2,
4432                    total: 100,
4433                },
4434                instance: TestInstanceId {
4435                    binary_id: &binary_a,
4436                    test_name: &test_1,
4437                },
4438                output: make_pass_output(),
4439            },
4440        ];
4441
4442        sort_final_outputs(&mut entries, false);
4443        assert_eq!(
4444            extract_ids(&entries),
4445            vec![("aaa", "test_1"), ("aaa", "test_2"), ("aaa", "test_1")],
4446            "stress index 0 entries come before stress index 2"
4447        );
4448        // Verify the stress indexes are in order.
4449        let stress_indexes: Vec<_> = entries
4450            .iter()
4451            .map(|e| e.stress_index.unwrap().current)
4452            .collect();
4453        assert_eq!(
4454            stress_indexes,
4455            vec![0, 0, 2],
4456            "stress indexes are sorted correctly"
4457        );
4458    }
4459}
4460
4461#[cfg(all(windows, test))]
4462mod windows_tests {
4463    use super::*;
4464    use crate::reporter::events::AbortDescription;
4465    use windows_sys::Win32::{
4466        Foundation::{STATUS_CONTROL_C_EXIT, STATUS_CONTROL_STACK_VIOLATION},
4467        Globalization::SetThreadUILanguage,
4468    };
4469
4470    #[test]
4471    fn test_write_windows_abort_line() {
4472        unsafe {
4473            // Set the thread UI language to US English for consistent output.
4474            SetThreadUILanguage(0x0409);
4475        }
4476
4477        insta::assert_snapshot!(
4478            "ctrl_c_code",
4479            to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_C_EXIT))
4480        );
4481        insta::assert_snapshot!(
4482            "stack_violation_code",
4483            to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_STACK_VIOLATION)),
4484        );
4485        insta::assert_snapshot!("job_object", to_abort_line(AbortStatus::JobObject));
4486    }
4487
4488    #[track_caller]
4489    fn to_abort_line(status: AbortStatus) -> String {
4490        let mut buf = String::new();
4491        let description = AbortDescription::from(status);
4492        write_windows_abort_line(&description, &Styles::default(), &mut buf).unwrap();
4493        buf
4494    }
4495}