1use 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::Reverse,
55 io::{self, BufWriter, IsTerminal, Write},
56 time::Duration,
57};
58
59#[derive(Copy, Clone, Debug, Eq, PartialEq)]
61pub(crate) enum DisplayerKind {
62 Live,
64
65 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) test_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.test_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 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.test_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
178pub(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 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 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 pub(crate) fn set_run_id_unique_prefix(&mut self, prefix: ShortestRunIdPrefix) {
237 self.inner.run_id_unique_prefix = Some(prefix);
238 }
239
240 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!(writer, "{:>12} ", "Replaying".style(styles.pass))?;
251 let run_id_display = if let Some(prefix_info) = &header.unique_prefix {
252 format!(
254 "{}{}",
255 prefix_info.prefix.style(styles.run_id_prefix),
256 prefix_info.rest.style(styles.run_id_rest),
257 )
258 } else {
259 header.run_id.to_string().style(styles.count).to_string()
261 };
262 writeln!(writer, "recorded run {}", run_id_display)?;
263
264 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 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 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 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 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#[derive(Debug)]
337pub struct OutputLoadDecider {
338 pub(super) status_level: StatusLevel,
339 pub(super) overrides: OutputDisplayOverrides,
340}
341
342impl OutputLoadDecider {
343 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 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 pub(super) fn should_load_for_setup_script(result: &ExecutionResultDescription) -> LoadOutput {
388 if result.is_success() {
390 LoadOutput::Skip
391 } else {
392 LoadOutput::Load
393 }
394 }
395
396 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 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 if is_immediate || is_final {
424 LoadOutput::Load
425 } else {
426 LoadOutput::Skip
427 }
428 }
429}
430
431enum ReporterOutputImpl<'a> {
432 Terminal {
433 progress_bar: Option<Box<ProgressBarState>>,
436 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 eprint!("{}", term_progress.last_value())
460 }
461 }
462 ReporterOutputImpl::Writer(_) => {
463 }
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 eprint!("{}", term_progress.last_value())
480 }
481 }
482 ReporterOutputImpl::Writer(_) => {
483 }
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
522impl<'a> PartialEq for FinalOutputEntry<'a> {
523 fn eq(&self, other: &Self) -> bool {
524 self.cmp(other) == std::cmp::Ordering::Equal
525 }
526}
527
528impl<'a> Eq for FinalOutputEntry<'a> {}
529
530impl<'a> PartialOrd for FinalOutputEntry<'a> {
531 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
532 Some(self.cmp(other))
533 }
534}
535
536impl<'a> Ord for FinalOutputEntry<'a> {
537 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
538 (
541 Reverse(self.output.final_status_level()),
542 self.stress_index,
543 self.counter,
544 self.instance,
545 )
546 .cmp(&(
547 Reverse(other.output.final_status_level()),
548 other.stress_index,
549 other.counter,
550 other.instance,
551 ))
552 }
553}
554
555struct DisplayReporterImpl<'a> {
556 mode: NextestRunMode,
557 default_filter: CompiledDefaultFilter,
558 status_levels: StatusLevels,
559 no_capture: bool,
560 verbose: bool,
561 no_output_indent: bool,
562 counter_width: Option<usize>,
565 styles: Box<Styles>,
566 theme_characters: ThemeCharacters,
567 cancel_status: Option<CancelReason>,
568 unit_output: UnitOutputReporter,
569 final_outputs: DebugIgnore<Vec<FinalOutputEntry<'a>>>,
570 run_id_unique_prefix: Option<ShortestRunIdPrefix>,
573}
574
575impl<'a> DisplayReporterImpl<'a> {
576 fn write_event_impl(
577 &mut self,
578 event: &TestEvent<'a>,
579 writer: &mut dyn WriteStr,
580 ) -> io::Result<()> {
581 match &event.kind {
582 TestEventKind::RunStarted {
583 test_list,
584 run_id,
585 profile_name,
586 cli_args: _,
587 stress_condition: _,
588 } => {
589 writeln!(writer, "{}", self.theme_characters.hbar(12))?;
590 write!(writer, "{:>12} ", "Nextest run".style(self.styles.pass))?;
591
592 let run_id_display = if let Some(prefix_info) = &self.run_id_unique_prefix {
595 format!(
596 "{}{}",
597 prefix_info.prefix.style(self.styles.run_id_prefix),
598 prefix_info.rest.style(self.styles.run_id_rest),
599 )
600 } else {
601 run_id.style(self.styles.count).to_string()
602 };
603
604 writeln!(
605 writer,
606 "ID {} with nextest profile: {}",
607 run_id_display,
608 profile_name.style(self.styles.count),
609 )?;
610
611 write!(writer, "{:>12} ", "Starting".style(self.styles.pass))?;
612
613 let count_style = self.styles.count;
614
615 let tests_str = plural::tests_str(self.mode, test_list.run_count());
616 let binaries_str = plural::binaries_str(test_list.listed_binary_count());
617
618 write!(
619 writer,
620 "{} {tests_str} across {} {binaries_str}",
621 test_list.run_count().style(count_style),
622 test_list.listed_binary_count().style(count_style),
623 )?;
624
625 write_skip_counts(
626 self.mode,
627 test_list.skip_counts(),
628 &self.default_filter,
629 &self.styles,
630 writer,
631 )?;
632
633 writeln!(writer)?;
634 }
635 TestEventKind::StressSubRunStarted { progress } => {
636 write!(
637 writer,
638 "{}\n{:>12} ",
639 self.theme_characters.hbar(12),
640 "Stress test".style(self.styles.pass)
641 )?;
642
643 match progress {
644 StressProgress::Count {
645 total: StressCount::Count { count },
646 elapsed,
647 completed,
648 } => {
649 write!(
650 writer,
651 "iteration {}/{} ({} elapsed so far",
652 (completed + 1).style(self.styles.count),
653 count.style(self.styles.count),
654 DisplayHhMmSs {
655 duration: *elapsed,
656 floor: true,
657 }
658 .style(self.styles.count),
659 )?;
660 }
661 StressProgress::Count {
662 total: StressCount::Infinite,
663 elapsed,
664 completed,
665 } => {
666 write!(
667 writer,
668 "iteration {} ({} elapsed so far",
669 (completed + 1).style(self.styles.count),
670 DisplayHhMmSs {
671 duration: *elapsed,
672 floor: true,
673 }
674 .style(self.styles.count),
675 )?;
676 }
677 StressProgress::Time {
678 total,
679 elapsed,
680 completed,
681 } => {
682 write!(
683 writer,
684 "iteration {} ({}/{} elapsed so far",
685 (completed + 1).style(self.styles.count),
686 DisplayHhMmSs {
687 duration: *elapsed,
688 floor: true,
689 }
690 .style(self.styles.count),
691 DisplayHhMmSs {
692 duration: *total,
693 floor: true,
694 }
695 .style(self.styles.count),
696 )?;
697 }
698 }
699
700 if let Some(remaining) = progress.remaining() {
701 match remaining {
702 StressRemaining::Count(n) => {
703 write!(
704 writer,
705 ", {} {} remaining",
706 n.style(self.styles.count),
707 plural::iterations_str(n.get()),
708 )?;
709 }
710 StressRemaining::Infinite => {
711 }
713 StressRemaining::Time(t) => {
714 write!(
715 writer,
716 ", {} remaining",
717 DisplayHhMmSs {
718 duration: t,
719 floor: false,
728 }
729 .style(self.styles.count)
730 )?;
731 }
732 }
733 }
734
735 writeln!(writer, ")")?;
736 }
737 TestEventKind::SetupScriptStarted {
738 stress_index,
739 index,
740 total,
741 script_id,
742 program,
743 args,
744 ..
745 } => {
746 writeln!(
747 writer,
748 "{:>12} [{:>9}] {}",
749 "SETUP".style(self.styles.pass),
750 format!("{}/{}", index + 1, total),
752 self.display_script_instance(*stress_index, script_id.clone(), program, args)
753 )?;
754 }
755 TestEventKind::SetupScriptSlow {
756 stress_index,
757 script_id,
758 program,
759 args,
760 elapsed,
761 will_terminate,
762 } => {
763 if !*will_terminate && self.status_levels.status_level >= StatusLevel::Slow {
764 write!(writer, "{:>12} ", "SETUP SLOW".style(self.styles.skip))?;
765 } else if *will_terminate {
766 write!(writer, "{:>12} ", "TERMINATING".style(self.styles.fail))?;
767 }
768
769 writeln!(
770 writer,
771 "{}{}",
772 DisplaySlowDuration(*elapsed),
773 self.display_script_instance(*stress_index, script_id.clone(), program, args)
774 )?;
775 }
776 TestEventKind::SetupScriptFinished {
777 stress_index,
778 script_id,
779 program,
780 args,
781 run_status,
782 ..
783 } => {
784 self.write_setup_script_status_line(
785 *stress_index,
786 script_id,
787 program,
788 args,
789 run_status,
790 writer,
791 )?;
792 if !run_status.result.is_success() {
795 self.write_setup_script_execute_status(run_status, writer)?;
796 }
797 }
798 TestEventKind::TestStarted {
799 stress_index,
800 test_instance,
801 current_stats,
802 command_line,
803 ..
804 } => {
805 if self.no_capture || self.verbose {
808 writeln!(
810 writer,
811 "{:>12} [ ] {}",
812 "START".style(self.styles.pass),
813 self.display_test_instance(
814 *stress_index,
815 TestInstanceCounter::Counter {
816 current: current_stats.finished_count + 1,
820 total: current_stats.initial_run_count,
821 },
822 *test_instance
823 ),
824 )?;
825 }
826
827 if self.verbose {
828 self.write_command_line(command_line, writer)?;
829 }
830 }
831 TestEventKind::TestSlow {
832 stress_index,
833 test_instance,
834 retry_data,
835 elapsed,
836 will_terminate,
837 } => {
838 if !*will_terminate && self.status_levels.status_level >= StatusLevel::Slow {
839 if retry_data.total_attempts > 1 {
840 write!(
841 writer,
842 "{:>12} ",
843 format!("TRY {} SLOW", retry_data.attempt).style(self.styles.skip)
844 )?;
845 } else {
846 write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
847 }
848 } else if *will_terminate {
849 let (required_status_level, style) = if retry_data.is_last_attempt() {
850 (StatusLevel::Fail, self.styles.fail)
851 } else {
852 (StatusLevel::Retry, self.styles.retry)
853 };
854 if retry_data.total_attempts > 1
855 && self.status_levels.status_level > required_status_level
856 {
857 write!(
858 writer,
859 "{:>12} ",
860 format!("TRY {} TRMNTG", retry_data.attempt).style(style)
861 )?;
862 } else {
863 write!(writer, "{:>12} ", "TERMINATING".style(style))?;
864 };
865 }
866
867 writeln!(
868 writer,
869 "{}{}",
870 DisplaySlowDuration(*elapsed),
871 self.display_test_instance(
872 *stress_index,
873 TestInstanceCounter::Padded,
874 *test_instance
875 )
876 )?;
877 }
878
879 TestEventKind::TestAttemptFailedWillRetry {
880 stress_index,
881 test_instance,
882 run_status,
883 delay_before_next_attempt,
884 failure_output,
885 running: _,
886 } => {
887 if self.status_levels.status_level >= StatusLevel::Retry {
888 let try_status_string = format!(
889 "TRY {} {}",
890 run_status.retry_data.attempt,
891 short_status_str(&run_status.result),
892 );
893
894 write!(
896 writer,
897 "{:>12} {}",
898 try_status_string.style(self.styles.retry),
899 DisplayBracketedDuration(run_status.time_taken),
900 )?;
901
902 writeln!(
904 writer,
905 "{}",
906 self.display_test_instance(
907 *stress_index,
908 TestInstanceCounter::Padded,
909 *test_instance
910 )
911 )?;
912
913 assert!(
915 !run_status.result.is_success(),
916 "only failing tests are retried"
917 );
918 if self
919 .unit_output
920 .overrides()
921 .failure_output(*failure_output)
922 .is_immediate()
923 {
924 self.write_test_execute_status(run_status, true, writer)?;
925 }
926
927 if !delay_before_next_attempt.is_zero() {
931 let delay_string = format!(
933 "DELAY {}/{}",
934 run_status.retry_data.attempt + 1,
935 run_status.retry_data.total_attempts,
936 );
937 write!(
938 writer,
939 "{:>12} {}",
940 delay_string.style(self.styles.retry),
941 DisplayDurationBy(*delay_before_next_attempt)
942 )?;
943
944 writeln!(
946 writer,
947 "{}",
948 self.display_test_instance(
949 *stress_index,
950 TestInstanceCounter::Padded,
951 *test_instance
952 )
953 )?;
954 }
955 }
956 }
957 TestEventKind::TestRetryStarted {
958 stress_index,
959 test_instance,
960 retry_data: RetryData { attempt, .. },
961 running: _,
962 command_line,
963 } => {
964 if self.no_capture || self.verbose {
966 let retry_string = format!("TRY {attempt} START");
967 writeln!(
968 writer,
969 "{:>12} [ ] {}",
970 retry_string.style(self.styles.retry),
971 self.display_test_instance(
972 *stress_index,
973 TestInstanceCounter::Padded,
974 *test_instance
975 )
976 )?;
977 }
978
979 if self.verbose {
980 self.write_command_line(command_line, writer)?;
981 }
982 }
983 TestEventKind::TestFinished {
984 stress_index,
985 test_instance,
986 success_output,
987 failure_output,
988 run_statuses,
989 current_stats,
990 ..
991 } => {
992 let describe = run_statuses.describe();
993 let last_status = run_statuses.last_status();
994 let test_output_display = self.unit_output.resolve_test_output_display(
995 *success_output,
996 *failure_output,
997 &last_status.result,
998 );
999
1000 let output_on_test_finished = self.status_levels.compute_output_on_test_finished(
1001 test_output_display,
1002 self.cancel_status,
1003 describe.status_level(),
1004 describe.final_status_level(),
1005 &last_status.result,
1006 );
1007
1008 let counter = TestInstanceCounter::Counter {
1009 current: current_stats.finished_count,
1010 total: current_stats.initial_run_count,
1011 };
1012
1013 if output_on_test_finished.write_status_line {
1014 self.write_status_line(
1015 *stress_index,
1016 counter,
1017 *test_instance,
1018 describe,
1019 writer,
1020 )?;
1021 }
1022 if output_on_test_finished.show_immediate {
1023 self.write_test_execute_status(last_status, false, writer)?;
1024 }
1025 if let OutputStoreFinal::Yes { display_output } =
1026 output_on_test_finished.store_final
1027 {
1028 self.final_outputs.push(FinalOutputEntry {
1029 stress_index: *stress_index,
1030 counter,
1031 instance: *test_instance,
1032 output: FinalOutput::Executed {
1033 run_statuses: run_statuses.clone(),
1034 display_output,
1035 },
1036 });
1037 }
1038 }
1039 TestEventKind::TestSkipped {
1040 stress_index,
1041 test_instance,
1042 reason,
1043 } => {
1044 if self.status_levels.status_level >= StatusLevel::Skip {
1045 self.write_skip_line(*stress_index, *test_instance, writer)?;
1046 }
1047 if self.status_levels.final_status_level >= FinalStatusLevel::Skip {
1048 self.final_outputs.push(FinalOutputEntry {
1049 stress_index: *stress_index,
1050 counter: TestInstanceCounter::Padded,
1051 instance: *test_instance,
1052 output: FinalOutput::Skipped(*reason),
1053 });
1054 }
1055 }
1056 TestEventKind::RunBeginCancel {
1057 setup_scripts_running,
1058 current_stats,
1059 running,
1060 } => {
1061 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1062
1063 write!(writer, "{:>12} ", "Cancelling".style(self.styles.fail))?;
1064 if let Some(reason) = current_stats.cancel_reason {
1065 write!(
1066 writer,
1067 "due to {}: ",
1068 reason.to_static_str().style(self.styles.fail)
1069 )?;
1070 }
1071
1072 let immediately_terminating_text =
1073 if current_stats.cancel_reason == Some(CancelReason::TestFailureImmediate) {
1074 format!("immediately {} ", "terminating".style(self.styles.fail))
1075 } else {
1076 String::new()
1077 };
1078
1079 if *setup_scripts_running > 0 {
1081 let s = plural::setup_scripts_str(*setup_scripts_running);
1082 write!(
1083 writer,
1084 "{immediately_terminating_text}{} {s} still running",
1085 setup_scripts_running.style(self.styles.count),
1086 )?;
1087 } else if *running > 0 {
1088 let tests_str = plural::tests_str(self.mode, *running);
1089 write!(
1090 writer,
1091 "{immediately_terminating_text}{} {tests_str} still running",
1092 running.style(self.styles.count),
1093 )?;
1094 }
1095 writeln!(writer)?;
1096 }
1097 TestEventKind::RunBeginKill {
1098 setup_scripts_running,
1099 current_stats,
1100 running,
1101 } => {
1102 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1103
1104 write!(writer, "{:>12} ", "Killing".style(self.styles.fail),)?;
1105 if let Some(reason) = current_stats.cancel_reason {
1106 write!(
1107 writer,
1108 "due to {}: ",
1109 reason.to_static_str().style(self.styles.fail)
1110 )?;
1111 }
1112
1113 if *setup_scripts_running > 0 {
1115 let s = plural::setup_scripts_str(*setup_scripts_running);
1116 write!(
1117 writer,
1118 ": {} {s} still running",
1119 setup_scripts_running.style(self.styles.count),
1120 )?;
1121 } else if *running > 0 {
1122 let tests_str = plural::tests_str(self.mode, *running);
1123 write!(
1124 writer,
1125 ": {} {tests_str} still running",
1126 running.style(self.styles.count),
1127 )?;
1128 }
1129 writeln!(writer)?;
1130 }
1131 TestEventKind::RunPaused {
1132 setup_scripts_running,
1133 running,
1134 } => {
1135 write!(
1136 writer,
1137 "{:>12} due to {}",
1138 "Pausing".style(self.styles.pass),
1139 "signal".style(self.styles.count)
1140 )?;
1141
1142 if *setup_scripts_running > 0 {
1144 let s = plural::setup_scripts_str(*setup_scripts_running);
1145 write!(
1146 writer,
1147 ": {} {s} running",
1148 setup_scripts_running.style(self.styles.count),
1149 )?;
1150 } else if *running > 0 {
1151 let tests_str = plural::tests_str(self.mode, *running);
1152 write!(
1153 writer,
1154 ": {} {tests_str} running",
1155 running.style(self.styles.count),
1156 )?;
1157 }
1158 writeln!(writer)?;
1159 }
1160 TestEventKind::RunContinued {
1161 setup_scripts_running,
1162 running,
1163 } => {
1164 write!(
1165 writer,
1166 "{:>12} due to {}",
1167 "Continuing".style(self.styles.pass),
1168 "signal".style(self.styles.count)
1169 )?;
1170
1171 if *setup_scripts_running > 0 {
1173 let s = plural::setup_scripts_str(*setup_scripts_running);
1174 write!(
1175 writer,
1176 ": {} {s} running",
1177 setup_scripts_running.style(self.styles.count),
1178 )?;
1179 } else if *running > 0 {
1180 let tests_str = plural::tests_str(self.mode, *running);
1181 write!(
1182 writer,
1183 ": {} {tests_str} running",
1184 running.style(self.styles.count),
1185 )?;
1186 }
1187 writeln!(writer)?;
1188 }
1189 TestEventKind::InfoStarted { total, run_stats } => {
1190 let info_style = if run_stats.has_failures() {
1191 self.styles.fail
1192 } else {
1193 self.styles.pass
1194 };
1195
1196 let hbar = self.theme_characters.hbar(12);
1197
1198 write!(writer, "{hbar}\n{}: ", "info".style(info_style))?;
1199
1200 writeln!(
1202 writer,
1203 "{} in {:.3?}s",
1204 progress_bar_msg(run_stats, *total, &self.styles),
1210 event.elapsed.as_secs_f64(),
1211 )?;
1212 }
1213 TestEventKind::InfoResponse {
1214 index,
1215 total,
1216 response,
1217 } => {
1218 self.write_info_response(*index, *total, response, writer)?;
1219 }
1220 TestEventKind::InfoFinished { missing } => {
1221 let hbar = self.theme_characters.hbar(12);
1222
1223 if *missing > 0 {
1224 writeln!(
1227 writer,
1228 "{}: missing {} responses",
1229 "info".style(self.styles.skip),
1230 missing.style(self.styles.count)
1231 )?;
1232 }
1233
1234 writeln!(writer, "{hbar}")?;
1235 }
1236 TestEventKind::InputEnter {
1237 current_stats,
1238 running,
1239 } => {
1240 writeln!(
1243 writer,
1244 "{}",
1245 progress_str(event.elapsed, current_stats, *running, &self.styles)
1246 )?;
1247 }
1248 TestEventKind::StressSubRunFinished {
1249 progress,
1250 sub_elapsed,
1251 sub_stats,
1252 } => {
1253 let stats_summary = sub_stats.summarize_final();
1254 let summary_style = match stats_summary {
1255 FinalRunStats::Success => self.styles.pass,
1256 FinalRunStats::NoTestsRun => self.styles.skip,
1257 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1258 self.styles.fail
1259 }
1260 };
1261
1262 write!(
1263 writer,
1264 "{:>12} {}",
1265 "Stress test".style(summary_style),
1266 DisplayBracketedDuration(*sub_elapsed),
1267 )?;
1268 match progress {
1269 StressProgress::Count {
1270 total: StressCount::Count { count },
1271 elapsed: _,
1272 completed,
1273 } => {
1274 write!(
1275 writer,
1276 "iteration {}/{}: ",
1277 completed.style(self.styles.count),
1281 count.style(self.styles.count),
1282 )?;
1283 }
1284 StressProgress::Count {
1285 total: StressCount::Infinite,
1286 elapsed: _,
1287 completed,
1288 } => {
1289 write!(
1290 writer,
1291 "iteration {}: ",
1292 completed.style(self.styles.count),
1296 )?;
1297 }
1298 StressProgress::Time {
1299 total: _,
1300 elapsed: _,
1301 completed,
1302 } => {
1303 write!(
1304 writer,
1305 "iteration {}: ",
1306 completed.style(self.styles.count),
1310 )?;
1311 }
1312 }
1313
1314 write!(
1315 writer,
1316 "{}",
1317 sub_stats.finished_count.style(self.styles.count)
1318 )?;
1319 if sub_stats.finished_count != sub_stats.initial_run_count {
1320 write!(
1321 writer,
1322 "/{}",
1323 sub_stats.initial_run_count.style(self.styles.count)
1324 )?;
1325 }
1326
1327 let tests_str = plural::tests_plural_if(
1329 self.mode,
1330 sub_stats.initial_run_count != 1 || sub_stats.finished_count != 1,
1331 );
1332
1333 let mut summary_str = String::new();
1334 write_summary_str(sub_stats, &self.styles, &mut summary_str);
1335 writeln!(writer, " {tests_str} run: {summary_str}")?;
1336 }
1337 TestEventKind::RunFinished {
1338 start_time: _start_time,
1339 elapsed,
1340 run_stats,
1341 outstanding_not_seen: tests_not_seen,
1342 ..
1343 } => {
1344 match run_stats {
1345 RunFinishedStats::Single(run_stats) => {
1346 let stats_summary = run_stats.summarize_final();
1347 let summary_style = match stats_summary {
1348 FinalRunStats::Success => self.styles.pass,
1349 FinalRunStats::NoTestsRun => self.styles.skip,
1350 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1351 self.styles.fail
1352 }
1353 };
1354 write!(
1355 writer,
1356 "{}\n{:>12} ",
1357 self.theme_characters.hbar(12),
1358 "Summary".style(summary_style)
1359 )?;
1360
1361 write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1366
1367 write!(
1368 writer,
1369 "{}",
1370 run_stats.finished_count.style(self.styles.count)
1371 )?;
1372 if run_stats.finished_count != run_stats.initial_run_count {
1373 write!(
1374 writer,
1375 "/{}",
1376 run_stats.initial_run_count.style(self.styles.count)
1377 )?;
1378 }
1379
1380 let tests_str = plural::tests_plural_if(
1382 self.mode,
1383 run_stats.initial_run_count != 1 || run_stats.finished_count != 1,
1384 );
1385
1386 let mut summary_str = String::new();
1387 write_summary_str(run_stats, &self.styles, &mut summary_str);
1388 writeln!(writer, " {tests_str} run: {summary_str}")?;
1389 }
1390 RunFinishedStats::Stress(stats) => {
1391 let stats_summary = stats.summarize_final();
1392 let summary_style = match stats_summary {
1393 StressFinalRunStats::Success => self.styles.pass,
1394 StressFinalRunStats::NoTestsRun => self.styles.skip,
1395 StressFinalRunStats::Cancelled | StressFinalRunStats::Failed => {
1396 self.styles.fail
1397 }
1398 };
1399
1400 write!(
1401 writer,
1402 "{}\n{:>12} ",
1403 self.theme_characters.hbar(12),
1404 "Summary".style(summary_style),
1405 )?;
1406
1407 write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1412
1413 write!(
1414 writer,
1415 "{}",
1416 stats.completed.current.style(self.styles.count),
1417 )?;
1418 let iterations_str = if let Some(total) = stats.completed.total {
1419 write!(writer, "/{}", total.style(self.styles.count))?;
1420 plural::iterations_str(total.get())
1421 } else {
1422 plural::iterations_str(stats.completed.current)
1423 };
1424 write!(
1425 writer,
1426 " stress run {iterations_str}: {} {}",
1427 stats.success_count.style(self.styles.count),
1428 "passed".style(self.styles.pass),
1429 )?;
1430 if stats.failed_count > 0 {
1431 write!(
1432 writer,
1433 ", {} {}",
1434 stats.failed_count.style(self.styles.count),
1435 "failed".style(self.styles.fail),
1436 )?;
1437 }
1438
1439 match stats.last_final_stats {
1440 FinalRunStats::Cancelled { reason, kind: _ } => {
1441 if let Some(reason) = reason {
1442 write!(
1443 writer,
1444 "; cancelled due to {}",
1445 reason.to_static_str().style(self.styles.fail),
1446 )?;
1447 }
1448 }
1449 FinalRunStats::Failed { .. }
1450 | FinalRunStats::Success
1451 | FinalRunStats::NoTestsRun => {}
1452 }
1453
1454 writeln!(writer)?;
1455 }
1456 }
1457
1458 if self.cancel_status < Some(CancelReason::Interrupt) {
1461 self.final_outputs.sort_unstable();
1463
1464 for entry in &*self.final_outputs {
1465 match &entry.output {
1466 FinalOutput::Skipped(_) => {
1467 self.write_skip_line(entry.stress_index, entry.instance, writer)?;
1468 }
1469 FinalOutput::Executed {
1470 run_statuses,
1471 display_output,
1472 } => {
1473 let last_status = run_statuses.last_status();
1474
1475 self.write_final_status_line(
1476 entry.stress_index,
1477 entry.counter,
1478 entry.instance,
1479 run_statuses.describe(),
1480 writer,
1481 )?;
1482 if *display_output {
1483 self.write_test_execute_status(last_status, false, writer)?;
1484 }
1485 }
1486 }
1487 }
1488 }
1489
1490 if let Some(not_seen) = tests_not_seen
1491 && not_seen.total_not_seen > 0
1492 {
1493 writeln!(
1494 writer,
1495 "{:>12} {} outstanding {} not seen during this rerun:",
1496 "Note".style(self.styles.skip),
1497 not_seen.total_not_seen.style(self.styles.count),
1498 plural::tests_str(self.mode, not_seen.total_not_seen),
1499 )?;
1500
1501 for t in ¬_seen.not_seen {
1502 let display = DisplayTestInstance::new(
1503 None,
1504 None,
1505 t.as_ref(),
1506 &self.styles.list_styles,
1507 );
1508 writeln!(writer, " {}", display)?;
1509 }
1510
1511 let remaining = not_seen
1512 .total_not_seen
1513 .saturating_sub(not_seen.not_seen.len());
1514 if remaining > 0 {
1515 writeln!(
1516 writer,
1517 " ... and {} more {}",
1518 remaining.style(self.styles.count),
1519 plural::tests_str(self.mode, remaining),
1520 )?;
1521 }
1522 }
1523
1524 write_final_warnings(self.mode, run_stats.final_stats(), &self.styles, writer)?;
1526 }
1527 }
1528
1529 Ok(())
1530 }
1531
1532 fn write_skip_line(
1533 &self,
1534 stress_index: Option<StressIndex>,
1535 test_instance: TestInstanceId<'a>,
1536 writer: &mut dyn WriteStr,
1537 ) -> io::Result<()> {
1538 write!(writer, "{:>12} ", "SKIP".style(self.styles.skip))?;
1539 writeln!(
1541 writer,
1542 "[ ] {}",
1543 self.display_test_instance(stress_index, TestInstanceCounter::Padded, test_instance)
1544 )?;
1545
1546 Ok(())
1547 }
1548
1549 fn write_setup_script_status_line(
1550 &self,
1551 stress_index: Option<StressIndex>,
1552 script_id: &ScriptId,
1553 command: &str,
1554 args: &[String],
1555 status: &SetupScriptExecuteStatus<LiveSpec>,
1556 writer: &mut dyn WriteStr,
1557 ) -> io::Result<()> {
1558 match status.result {
1559 ExecutionResultDescription::Pass => {
1560 write!(writer, "{:>12} ", "SETUP PASS".style(self.styles.pass))?;
1561 }
1562 ExecutionResultDescription::Leak { result } => match result {
1563 LeakTimeoutResult::Pass => {
1564 write!(writer, "{:>12} ", "SETUP LEAK".style(self.styles.skip))?;
1565 }
1566 LeakTimeoutResult::Fail => {
1567 write!(writer, "{:>12} ", "SETUP LKFAIL".style(self.styles.fail))?;
1568 }
1569 },
1570 ref other => {
1571 let status_str = short_status_str(other);
1572 write!(
1573 writer,
1574 "{:>12} ",
1575 format!("SETUP {status_str}").style(self.styles.fail),
1576 )?;
1577 }
1578 }
1579
1580 writeln!(
1581 writer,
1582 "{}{}",
1583 DisplayBracketedDuration(status.time_taken),
1584 self.display_script_instance(stress_index, script_id.clone(), command, args)
1585 )?;
1586
1587 Ok(())
1588 }
1589
1590 fn write_status_line(
1591 &self,
1592 stress_index: Option<StressIndex>,
1593 counter: TestInstanceCounter,
1594 test_instance: TestInstanceId<'a>,
1595 describe: ExecutionDescription<'_, LiveSpec>,
1596 writer: &mut dyn WriteStr,
1597 ) -> io::Result<()> {
1598 self.write_status_line_impl(
1599 stress_index,
1600 counter,
1601 test_instance,
1602 describe,
1603 StatusLineKind::Intermediate,
1604 writer,
1605 )
1606 }
1607
1608 fn write_final_status_line(
1609 &self,
1610 stress_index: Option<StressIndex>,
1611 counter: TestInstanceCounter,
1612 test_instance: TestInstanceId<'a>,
1613 describe: ExecutionDescription<'_, LiveSpec>,
1614 writer: &mut dyn WriteStr,
1615 ) -> io::Result<()> {
1616 self.write_status_line_impl(
1617 stress_index,
1618 counter,
1619 test_instance,
1620 describe,
1621 StatusLineKind::Final,
1622 writer,
1623 )
1624 }
1625
1626 fn write_status_line_impl(
1627 &self,
1628 stress_index: Option<StressIndex>,
1629 counter: TestInstanceCounter,
1630 test_instance: TestInstanceId<'a>,
1631 describe: ExecutionDescription<'_, LiveSpec>,
1632 kind: StatusLineKind,
1633 writer: &mut dyn WriteStr,
1634 ) -> io::Result<()> {
1635 let last_status = describe.last_status();
1636
1637 self.write_status_line_prefix(describe, kind, writer)?;
1639
1640 writeln!(
1642 writer,
1643 "{}{}",
1644 DisplayBracketedDuration(last_status.time_taken),
1645 self.display_test_instance(stress_index, counter, test_instance),
1646 )?;
1647
1648 if let ExecutionResultDescription::Fail {
1650 failure: FailureDescription::Abort { ref abort },
1651 leaked: _,
1652 } = last_status.result
1653 {
1654 write_windows_abort_line(abort, &self.styles, writer)?;
1655 }
1656
1657 Ok(())
1658 }
1659
1660 fn write_status_line_prefix(
1661 &self,
1662 describe: ExecutionDescription<'_, LiveSpec>,
1663 kind: StatusLineKind,
1664 writer: &mut dyn WriteStr,
1665 ) -> io::Result<()> {
1666 let last_status = describe.last_status();
1667 match describe {
1668 ExecutionDescription::Success { .. } => {
1669 match (kind, last_status.is_slow, &last_status.result) {
1673 (StatusLineKind::Final, true, ExecutionResultDescription::Pass) => {
1675 write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
1676 }
1677 (
1678 StatusLineKind::Final,
1679 true,
1680 ExecutionResultDescription::Leak {
1681 result: LeakTimeoutResult::Pass,
1682 },
1683 ) => {
1684 write!(writer, "{:>12} ", "SLOW + LEAK".style(self.styles.skip))?;
1685 }
1686 (
1687 StatusLineKind::Final,
1688 true,
1689 ExecutionResultDescription::Timeout {
1690 result: SlowTimeoutResult::Pass,
1691 },
1692 ) => {
1693 write!(writer, "{:>12} ", "SLOW+TMPASS".style(self.styles.skip))?;
1694 }
1695 (_, _, ExecutionResultDescription::Pass) => {
1697 write!(writer, "{:>12} ", "PASS".style(self.styles.pass))?;
1698 }
1699 (
1700 _,
1701 _,
1702 ExecutionResultDescription::Leak {
1703 result: LeakTimeoutResult::Pass,
1704 },
1705 ) => {
1706 write!(writer, "{:>12} ", "LEAK".style(self.styles.skip))?;
1707 }
1708 (
1709 _,
1710 _,
1711 ExecutionResultDescription::Timeout {
1712 result: SlowTimeoutResult::Pass,
1713 },
1714 ) => {
1715 write!(writer, "{:>12} ", "TIMEOUT-PASS".style(self.styles.skip))?;
1716 }
1717 (
1719 _,
1720 _,
1721 ExecutionResultDescription::Leak {
1722 result: LeakTimeoutResult::Fail,
1723 },
1724 )
1725 | (
1726 _,
1727 _,
1728 ExecutionResultDescription::Timeout {
1729 result: SlowTimeoutResult::Fail,
1730 },
1731 )
1732 | (_, _, ExecutionResultDescription::Fail { .. })
1733 | (_, _, ExecutionResultDescription::ExecFail) => {
1734 unreachable!(
1735 "success description cannot have failure result: {:?}",
1736 last_status.result
1737 )
1738 }
1739 }
1740 }
1741 ExecutionDescription::Flaky { .. } => {
1742 let status = match kind {
1744 StatusLineKind::Intermediate => {
1745 format!("TRY {} PASS", last_status.retry_data.attempt)
1746 }
1747 StatusLineKind::Final => {
1748 format!(
1749 "FLAKY {}/{}",
1750 last_status.retry_data.attempt, last_status.retry_data.total_attempts
1751 )
1752 }
1753 };
1754 write!(writer, "{:>12} ", status.style(self.styles.skip))?;
1755 }
1756 ExecutionDescription::Failure { .. } => {
1757 if last_status.retry_data.attempt == 1 {
1758 write!(
1759 writer,
1760 "{:>12} ",
1761 status_str(&last_status.result).style(self.styles.fail)
1762 )?;
1763 } else {
1764 let status_str = short_status_str(&last_status.result);
1765 write!(
1766 writer,
1767 "{:>12} ",
1768 format!("TRY {} {}", last_status.retry_data.attempt, status_str)
1769 .style(self.styles.fail)
1770 )?;
1771 }
1772 }
1773 }
1774 Ok(())
1775 }
1776
1777 fn display_test_instance(
1778 &self,
1779 stress_index: Option<StressIndex>,
1780 counter: TestInstanceCounter,
1781 instance: TestInstanceId<'a>,
1782 ) -> DisplayTestInstance<'_> {
1783 let counter_index = match (counter, self.counter_width) {
1784 (TestInstanceCounter::Counter { current, total }, Some(_)) => {
1785 Some(DisplayCounterIndex::new_counter(current, total))
1786 }
1787 (TestInstanceCounter::Padded, Some(counter_width)) => Some(
1788 DisplayCounterIndex::new_padded(self.theme_characters.hbar_char(), counter_width),
1789 ),
1790 (TestInstanceCounter::None, _) | (_, None) => None,
1791 };
1792
1793 DisplayTestInstance::new(
1794 stress_index,
1795 counter_index,
1796 instance,
1797 &self.styles.list_styles,
1798 )
1799 }
1800
1801 fn write_command_line(
1802 &self,
1803 command_line: &[String],
1804 writer: &mut dyn WriteStr,
1805 ) -> io::Result<()> {
1806 writeln!(
1808 writer,
1809 "{:>20}: {}",
1810 "command".style(self.styles.count),
1811 shell_words::join(command_line),
1812 )
1813 }
1814
1815 fn display_script_instance(
1816 &self,
1817 stress_index: Option<StressIndex>,
1818 script_id: ScriptId,
1819 command: &str,
1820 args: &[String],
1821 ) -> DisplayScriptInstance {
1822 DisplayScriptInstance::new(
1823 stress_index,
1824 script_id,
1825 command,
1826 args,
1827 self.styles.script_id,
1828 self.styles.count,
1829 )
1830 }
1831
1832 fn write_info_response(
1833 &self,
1834 index: usize,
1835 total: usize,
1836 response: &InfoResponse<'_>,
1837 writer: &mut dyn WriteStr,
1838 ) -> io::Result<()> {
1839 if index > 0 {
1840 writeln!(writer, "{}", self.theme_characters.hbar(8))?;
1843 }
1844
1845 let count_width = usize_decimal_char_width(index + 1) + usize_decimal_char_width(total) + 3;
1851 let padding = 8usize.saturating_sub(count_width);
1852
1853 write!(
1854 writer,
1855 "\n* {}/{}: {:padding$}",
1856 (index + 1).style(self.styles.count),
1858 total.style(self.styles.count),
1859 "",
1860 )?;
1861
1862 let mut writer = indented(writer).with_str(" ").skip_initial();
1865
1866 match response {
1867 InfoResponse::SetupScript(SetupScriptInfoResponse {
1868 stress_index,
1869 script_id,
1870 program,
1871 args,
1872 state,
1873 output,
1874 }) => {
1875 writeln!(
1877 writer,
1878 "{}",
1879 self.display_script_instance(*stress_index, script_id.clone(), program, args)
1880 )?;
1881
1882 self.write_unit_state(
1884 UnitKind::Script,
1885 "",
1886 state,
1887 output.has_errors(),
1888 &mut writer,
1889 )?;
1890
1891 if state.has_valid_output() {
1893 self.unit_output.write_child_execution_output(
1894 &self.styles,
1895 &self.output_spec_for_info(UnitKind::Script),
1896 output,
1897 &mut writer,
1898 )?;
1899 }
1900 }
1901 InfoResponse::Test(TestInfoResponse {
1902 stress_index,
1903 test_instance,
1904 retry_data,
1905 state,
1906 output,
1907 }) => {
1908 writeln!(
1910 writer,
1911 "{}",
1912 self.display_test_instance(
1913 *stress_index,
1914 TestInstanceCounter::None,
1915 *test_instance
1916 )
1917 )?;
1918
1919 let show_attempt_str = (retry_data.attempt > 1 && retry_data.total_attempts > 1)
1923 || matches!(state, UnitState::DelayBeforeNextAttempt { .. });
1924 let attempt_str = if show_attempt_str {
1925 format!(
1926 "(attempt {}/{}) ",
1927 retry_data.attempt, retry_data.total_attempts
1928 )
1929 } else {
1930 String::new()
1931 };
1932
1933 self.write_unit_state(
1935 UnitKind::Test,
1936 &attempt_str,
1937 state,
1938 output.has_errors(),
1939 &mut writer,
1940 )?;
1941
1942 if state.has_valid_output() {
1944 self.unit_output.write_child_execution_output(
1945 &self.styles,
1946 &self.output_spec_for_info(UnitKind::Test),
1947 output,
1948 &mut writer,
1949 )?;
1950 }
1951 }
1952 }
1953
1954 writer.write_str_flush()?;
1955 let inner_writer = writer.into_inner();
1956
1957 writeln!(inner_writer)?;
1959
1960 Ok(())
1961 }
1962
1963 fn write_unit_state(
1964 &self,
1965 kind: UnitKind,
1966 attempt_str: &str,
1967 state: &UnitState,
1968 output_has_errors: bool,
1969 writer: &mut dyn WriteStr,
1970 ) -> io::Result<()> {
1971 let status_str = "status".style(self.styles.count);
1972 match state {
1973 UnitState::Running {
1974 pid,
1975 time_taken,
1976 slow_after,
1977 } => {
1978 let running_style = if output_has_errors {
1979 self.styles.fail
1980 } else if slow_after.is_some() {
1981 self.styles.skip
1982 } else {
1983 self.styles.pass
1984 };
1985 write!(
1986 writer,
1987 "{status_str}: {attempt_str}{} {} for {:.3?}s as PID {}",
1988 DisplayUnitKind::new(self.mode, kind),
1989 "running".style(running_style),
1990 time_taken.as_secs_f64(),
1991 pid.style(self.styles.count),
1992 )?;
1993 if let Some(slow_after) = slow_after {
1994 write!(
1995 writer,
1996 " (marked slow after {:.3?}s)",
1997 slow_after.as_secs_f64()
1998 )?;
1999 }
2000 writeln!(writer)?;
2001 }
2002 UnitState::Exiting {
2003 pid,
2004 time_taken,
2005 slow_after,
2006 tentative_result,
2007 waiting_duration,
2008 remaining,
2009 } => {
2010 write!(
2011 writer,
2012 "{status_str}: {attempt_str}{} ",
2013 DisplayUnitKind::new(self.mode, kind)
2014 )?;
2015
2016 let tentative_desc = tentative_result.map(ExecutionResultDescription::from);
2017 self.write_info_execution_result(
2018 tentative_desc.as_ref(),
2019 slow_after.is_some(),
2020 writer,
2021 )?;
2022 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2023 if let Some(slow_after) = slow_after {
2024 write!(
2025 writer,
2026 " (marked slow after {:.3?}s)",
2027 slow_after.as_secs_f64()
2028 )?;
2029 }
2030 writeln!(writer)?;
2031
2032 if *waiting_duration >= Duration::from_secs(1) {
2035 writeln!(
2036 writer,
2037 "{}: spent {:.3?}s waiting for {} PID {} to shut down, \
2038 will mark as leaky after another {:.3?}s",
2039 "note".style(self.styles.count),
2040 waiting_duration.as_secs_f64(),
2041 DisplayUnitKind::new(self.mode, kind),
2042 pid.style(self.styles.count),
2043 remaining.as_secs_f64(),
2044 )?;
2045 }
2046 }
2047 UnitState::Terminating(state) => {
2048 self.write_terminating_state(kind, attempt_str, state, writer)?;
2049 }
2050 UnitState::Exited {
2051 result,
2052 time_taken,
2053 slow_after,
2054 } => {
2055 write!(
2056 writer,
2057 "{status_str}: {attempt_str}{} ",
2058 DisplayUnitKind::new(self.mode, kind)
2059 )?;
2060 let result_desc = ExecutionResultDescription::from(*result);
2061 self.write_info_execution_result(Some(&result_desc), slow_after.is_some(), writer)?;
2062 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2063 if let Some(slow_after) = slow_after {
2064 write!(
2065 writer,
2066 " (marked slow after {:.3?}s)",
2067 slow_after.as_secs_f64()
2068 )?;
2069 }
2070 writeln!(writer)?;
2071 }
2072 UnitState::DelayBeforeNextAttempt {
2073 previous_result,
2074 previous_slow,
2075 waiting_duration,
2076 remaining,
2077 } => {
2078 write!(
2079 writer,
2080 "{status_str}: {attempt_str}{} ",
2081 DisplayUnitKind::new(self.mode, kind)
2082 )?;
2083 let previous_desc = ExecutionResultDescription::from(*previous_result);
2084 self.write_info_execution_result(Some(&previous_desc), *previous_slow, writer)?;
2085 writeln!(
2086 writer,
2087 ", currently {} before next attempt",
2088 "waiting".style(self.styles.count)
2089 )?;
2090 writeln!(
2091 writer,
2092 "{}: waited {:.3?}s so far, will wait another {:.3?}s before retrying {}",
2093 "note".style(self.styles.count),
2094 waiting_duration.as_secs_f64(),
2095 remaining.as_secs_f64(),
2096 DisplayUnitKind::new(self.mode, kind),
2097 )?;
2098 }
2099 }
2100
2101 Ok(())
2102 }
2103
2104 fn write_terminating_state(
2105 &self,
2106 kind: UnitKind,
2107 attempt_str: &str,
2108 state: &UnitTerminatingState,
2109 writer: &mut dyn WriteStr,
2110 ) -> io::Result<()> {
2111 let UnitTerminatingState {
2112 pid,
2113 time_taken,
2114 reason,
2115 method,
2116 waiting_duration,
2117 remaining,
2118 } = state;
2119
2120 writeln!(
2121 writer,
2122 "{}: {attempt_str}{} {} PID {} due to {} ({} ran for {:.3?}s)",
2123 "status".style(self.styles.count),
2124 "terminating".style(self.styles.fail),
2125 DisplayUnitKind::new(self.mode, kind),
2126 pid.style(self.styles.count),
2127 reason.style(self.styles.count),
2128 DisplayUnitKind::new(self.mode, kind),
2129 time_taken.as_secs_f64(),
2130 )?;
2131
2132 match method {
2133 #[cfg(unix)]
2134 UnitTerminateMethod::Signal(signal) => {
2135 writeln!(
2136 writer,
2137 "{}: sent {} to process group; spent {:.3?}s waiting for {} to exit, \
2138 will SIGKILL after another {:.3?}s",
2139 "note".style(self.styles.count),
2140 signal,
2141 waiting_duration.as_secs_f64(),
2142 DisplayUnitKind::new(self.mode, kind),
2143 remaining.as_secs_f64(),
2144 )?;
2145 }
2146 #[cfg(windows)]
2147 UnitTerminateMethod::JobObject => {
2148 writeln!(
2149 writer,
2150 "{}: instructed job object to terminate",
2154 "note".style(self.styles.count),
2155 )?;
2156 }
2157 #[cfg(windows)]
2158 UnitTerminateMethod::Wait => {
2159 writeln!(
2160 writer,
2161 "{}: waiting for {} to exit on its own; spent {:.3?}s, will terminate \
2162 job object after another {:.3?}s",
2163 "note".style(self.styles.count),
2164 DisplayUnitKind::new(self.mode, kind),
2165 waiting_duration.as_secs_f64(),
2166 remaining.as_secs_f64(),
2167 )?;
2168 }
2169 #[cfg(test)]
2170 UnitTerminateMethod::Fake => {
2171 writeln!(
2173 writer,
2174 "{}: fake termination method; spent {:.3?}s waiting for {} to exit, \
2175 will kill after another {:.3?}s",
2176 "note".style(self.styles.count),
2177 waiting_duration.as_secs_f64(),
2178 DisplayUnitKind::new(self.mode, kind),
2179 remaining.as_secs_f64(),
2180 )?;
2181 }
2182 }
2183
2184 Ok(())
2185 }
2186
2187 fn write_info_execution_result(
2191 &self,
2192 result: Option<&ExecutionResultDescription>,
2193 is_slow: bool,
2194 writer: &mut dyn WriteStr,
2195 ) -> io::Result<()> {
2196 match result {
2197 Some(ExecutionResultDescription::Pass) => {
2198 let style = if is_slow {
2199 self.styles.skip
2200 } else {
2201 self.styles.pass
2202 };
2203
2204 write!(writer, "{}", "passed".style(style))
2205 }
2206 Some(ExecutionResultDescription::Leak {
2207 result: LeakTimeoutResult::Pass,
2208 }) => write!(
2209 writer,
2210 "{}",
2211 "passed with leaked handles".style(self.styles.skip)
2212 ),
2213 Some(ExecutionResultDescription::Leak {
2214 result: LeakTimeoutResult::Fail,
2215 }) => write!(
2216 writer,
2217 "{}: exited with code 0, but leaked handles",
2218 "failed".style(self.styles.fail),
2219 ),
2220 Some(ExecutionResultDescription::Timeout {
2221 result: SlowTimeoutResult::Pass,
2222 }) => {
2223 write!(writer, "{}", "passed with timeout".style(self.styles.skip))
2224 }
2225 Some(ExecutionResultDescription::Timeout {
2226 result: SlowTimeoutResult::Fail,
2227 }) => {
2228 write!(writer, "{}", "timed out".style(self.styles.fail))
2229 }
2230 Some(ExecutionResultDescription::Fail {
2231 failure: FailureDescription::Abort { abort },
2232 leaked: _,
2235 }) => {
2236 write!(writer, "{}", "aborted".style(self.styles.fail))?;
2238 if let AbortDescription::UnixSignal { signal, name } = abort {
2242 write!(writer, " with signal {}", signal.style(self.styles.count))?;
2243 if let Some(s) = name {
2244 write!(writer, ": SIG{s}")?;
2245 }
2246 }
2247 Ok(())
2248 }
2249 Some(ExecutionResultDescription::Fail {
2250 failure: FailureDescription::ExitCode { code },
2251 leaked,
2252 }) => {
2253 if *leaked {
2254 write!(
2255 writer,
2256 "{} with exit code {}, and leaked handles",
2257 "failed".style(self.styles.fail),
2258 code.style(self.styles.count),
2259 )
2260 } else {
2261 write!(
2262 writer,
2263 "{} with exit code {}",
2264 "failed".style(self.styles.fail),
2265 code.style(self.styles.count),
2266 )
2267 }
2268 }
2269 Some(ExecutionResultDescription::ExecFail) => {
2270 write!(writer, "{}", "failed to execute".style(self.styles.fail))
2271 }
2272 None => {
2273 write!(
2274 writer,
2275 "{} with unknown status",
2276 "failed".style(self.styles.fail)
2277 )
2278 }
2279 }
2280 }
2281
2282 fn write_setup_script_execute_status(
2283 &self,
2284 run_status: &SetupScriptExecuteStatus<LiveSpec>,
2285 writer: &mut dyn WriteStr,
2286 ) -> io::Result<()> {
2287 let spec = self.output_spec_for_finished(&run_status.result, false);
2288 self.unit_output.write_child_execution_output(
2289 &self.styles,
2290 &spec,
2291 &run_status.output,
2292 writer,
2293 )?;
2294
2295 if show_finished_status_info_line(&run_status.result) {
2296 write!(
2297 writer,
2298 " (script ",
2300 )?;
2301 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2302 writeln!(writer, ")\n")?;
2303 }
2304
2305 Ok(())
2306 }
2307
2308 fn write_test_execute_status(
2309 &self,
2310 run_status: &ExecuteStatus<LiveSpec>,
2311 is_retry: bool,
2312 writer: &mut dyn WriteStr,
2313 ) -> io::Result<()> {
2314 let spec = self.output_spec_for_finished(&run_status.result, is_retry);
2315 self.unit_output.write_child_execution_output(
2316 &self.styles,
2317 &spec,
2318 &run_status.output,
2319 writer,
2320 )?;
2321
2322 if show_finished_status_info_line(&run_status.result) {
2323 write!(
2324 writer,
2325 " (test ",
2327 )?;
2328 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2329 writeln!(writer, ")\n")?;
2330 }
2331
2332 Ok(())
2333 }
2334
2335 fn output_spec_for_finished(
2336 &self,
2337 result: &ExecutionResultDescription,
2338 is_retry: bool,
2339 ) -> ChildOutputSpec {
2340 let header_style = if is_retry {
2341 self.styles.retry
2342 } else {
2343 match result {
2344 ExecutionResultDescription::Pass => self.styles.pass,
2345 ExecutionResultDescription::Leak {
2346 result: LeakTimeoutResult::Pass,
2347 } => self.styles.skip,
2348 ExecutionResultDescription::Leak {
2349 result: LeakTimeoutResult::Fail,
2350 } => self.styles.fail,
2351 ExecutionResultDescription::Timeout {
2352 result: SlowTimeoutResult::Pass,
2353 } => self.styles.skip,
2354 ExecutionResultDescription::Timeout {
2355 result: SlowTimeoutResult::Fail,
2356 } => self.styles.fail,
2357 ExecutionResultDescription::Fail { .. } => self.styles.fail,
2358 ExecutionResultDescription::ExecFail => self.styles.fail,
2359 }
2360 };
2361
2362 let (six_char_start, six_char_end, eight_char_start, eight_char_end, output_indent) =
2383 if self.no_output_indent {
2384 (
2385 self.theme_characters.hbar(2),
2386 self.theme_characters.hbar(2),
2387 self.theme_characters.hbar(1),
2388 self.theme_characters.hbar(1),
2389 "",
2390 )
2391 } else {
2392 (
2393 " ".to_owned(),
2394 self.theme_characters.hbar(3),
2395 " ".to_owned(),
2396 self.theme_characters.hbar(1),
2397 " ",
2398 )
2399 };
2400
2401 let stdout_header = format!(
2402 "{} {} {}",
2403 six_char_start.style(header_style),
2404 "stdout".style(header_style),
2405 six_char_end.style(header_style),
2406 );
2407 let stderr_header = format!(
2408 "{} {} {}",
2409 six_char_start.style(header_style),
2410 "stderr".style(header_style),
2411 six_char_end.style(header_style),
2412 );
2413 let combined_header = format!(
2414 "{} {} {}",
2415 six_char_start.style(header_style),
2416 "output".style(header_style),
2417 six_char_end.style(header_style),
2418 );
2419 let exec_fail_header = format!(
2420 "{} {} {}",
2421 eight_char_start.style(header_style),
2422 "execfail".style(header_style),
2423 eight_char_end.style(header_style),
2424 );
2425
2426 ChildOutputSpec {
2427 kind: UnitKind::Test,
2428 stdout_header,
2429 stderr_header,
2430 combined_header,
2431 exec_fail_header,
2432 output_indent,
2433 }
2434 }
2435
2436 fn output_spec_for_info(&self, kind: UnitKind) -> ChildOutputSpec {
2440 let stdout_header = format!("{}:", "stdout".style(self.styles.count));
2441 let stderr_header = format!("{}:", "stderr".style(self.styles.count));
2442 let combined_header = format!("{}:", "output".style(self.styles.count));
2443 let exec_fail_header = format!("{}:", "errors".style(self.styles.count));
2444
2445 ChildOutputSpec {
2446 kind,
2447 stdout_header,
2448 stderr_header,
2449 combined_header,
2450 exec_fail_header,
2451 output_indent: " ",
2452 }
2453 }
2454}
2455
2456#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2457enum TestInstanceCounter {
2458 Counter { current: usize, total: usize },
2459 Padded,
2460 None,
2461}
2462
2463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2466enum StatusLineKind {
2467 Intermediate,
2469 Final,
2471}
2472
2473const LIBTEST_PANIC_EXIT_CODE: i32 = 101;
2474
2475fn show_finished_status_info_line(result: &ExecutionResultDescription) -> bool {
2478 match result {
2480 ExecutionResultDescription::Pass => false,
2481 ExecutionResultDescription::Leak {
2482 result: LeakTimeoutResult::Pass,
2483 } => {
2484 true
2486 }
2487 ExecutionResultDescription::Leak {
2488 result: LeakTimeoutResult::Fail,
2489 } => {
2490 true
2492 }
2493 ExecutionResultDescription::Fail {
2494 failure: FailureDescription::ExitCode { code },
2495 leaked,
2496 } => {
2497 *code != LIBTEST_PANIC_EXIT_CODE && !leaked
2500 }
2501 ExecutionResultDescription::Fail {
2502 failure: FailureDescription::Abort { .. },
2503 leaked: _,
2504 } => {
2505 true
2507 }
2508 ExecutionResultDescription::ExecFail => {
2509 false
2512 }
2513 ExecutionResultDescription::Timeout { .. } => {
2514 true
2516 }
2517 }
2518}
2519
2520fn status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2521 match result {
2523 ExecutionResultDescription::Fail {
2524 failure:
2525 FailureDescription::Abort {
2526 abort: AbortDescription::UnixSignal { signal, name },
2527 },
2528 leaked: _,
2529 } => match name {
2530 Some(s) => format!("SIG{s}").into(),
2531 None => format!("ABORT SIG {signal}").into(),
2532 },
2533 ExecutionResultDescription::Fail {
2534 failure:
2535 FailureDescription::Abort {
2536 abort: AbortDescription::WindowsNtStatus { .. },
2537 }
2538 | FailureDescription::Abort {
2539 abort: AbortDescription::WindowsJobObject,
2540 },
2541 leaked: _,
2542 } => {
2543 "ABORT".into()
2546 }
2547 ExecutionResultDescription::Fail {
2548 failure: FailureDescription::ExitCode { .. },
2549 leaked: true,
2550 } => "FAIL + LEAK".into(),
2551 ExecutionResultDescription::Fail {
2552 failure: FailureDescription::ExitCode { .. },
2553 leaked: false,
2554 } => "FAIL".into(),
2555 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2556 ExecutionResultDescription::Pass => "PASS".into(),
2557 ExecutionResultDescription::Leak {
2558 result: LeakTimeoutResult::Pass,
2559 } => "LEAK".into(),
2560 ExecutionResultDescription::Leak {
2561 result: LeakTimeoutResult::Fail,
2562 } => "LEAK-FAIL".into(),
2563 ExecutionResultDescription::Timeout {
2564 result: SlowTimeoutResult::Pass,
2565 } => "TIMEOUT-PASS".into(),
2566 ExecutionResultDescription::Timeout {
2567 result: SlowTimeoutResult::Fail,
2568 } => "TIMEOUT".into(),
2569 }
2570}
2571
2572fn short_status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2573 match result {
2575 ExecutionResultDescription::Fail {
2576 failure:
2577 FailureDescription::Abort {
2578 abort: AbortDescription::UnixSignal { signal, name },
2579 },
2580 leaked: _,
2581 } => match name {
2582 Some(s) => s.to_string().into(),
2583 None => format!("SIG {signal}").into(),
2584 },
2585 ExecutionResultDescription::Fail {
2586 failure:
2587 FailureDescription::Abort {
2588 abort: AbortDescription::WindowsNtStatus { .. },
2589 }
2590 | FailureDescription::Abort {
2591 abort: AbortDescription::WindowsJobObject,
2592 },
2593 leaked: _,
2594 } => {
2595 "ABORT".into()
2598 }
2599 ExecutionResultDescription::Fail {
2600 failure: FailureDescription::ExitCode { .. },
2601 leaked: true,
2602 } => "FL+LK".into(),
2603 ExecutionResultDescription::Fail {
2604 failure: FailureDescription::ExitCode { .. },
2605 leaked: false,
2606 } => "FAIL".into(),
2607 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2608 ExecutionResultDescription::Pass => "PASS".into(),
2609 ExecutionResultDescription::Leak {
2610 result: LeakTimeoutResult::Pass,
2611 } => "LEAK".into(),
2612 ExecutionResultDescription::Leak {
2613 result: LeakTimeoutResult::Fail,
2614 } => "LKFAIL".into(),
2615 ExecutionResultDescription::Timeout {
2616 result: SlowTimeoutResult::Pass,
2617 } => "TMPASS".into(),
2618 ExecutionResultDescription::Timeout {
2619 result: SlowTimeoutResult::Fail,
2620 } => "TMT".into(),
2621 }
2622}
2623
2624fn write_windows_abort_line(
2628 status: &AbortDescription,
2629 styles: &Styles,
2630 writer: &mut dyn WriteStr,
2631) -> io::Result<()> {
2632 match status {
2633 AbortDescription::UnixSignal { .. } => {
2634 Ok(())
2636 }
2637 AbortDescription::WindowsNtStatus { code, message } => {
2638 const INDENT: &str = " - ";
2641 let mut indented = indented(writer).with_str(INDENT).skip_initial();
2642 let code_str = format!("{:#010x}", code.style(styles.count));
2644 let status_str = match message {
2645 Some(msg) => format!("{code_str}: {msg}"),
2646 None => code_str,
2647 };
2648 writeln!(
2649 indented,
2650 "{:>12} {} {}",
2651 "-",
2652 "with code".style(styles.fail),
2653 status_str,
2654 )?;
2655 indented.write_str_flush()
2656 }
2657 AbortDescription::WindowsJobObject => {
2658 writeln!(
2659 writer,
2660 "{:>12} {} via {}",
2661 "-",
2662 "terminated".style(styles.fail),
2663 "job object".style(styles.count),
2664 )
2665 }
2666 }
2667}
2668
2669#[cfg(test)]
2670mod tests {
2671 use super::*;
2672 use crate::{
2673 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
2674 reporter::{
2675 ShowProgress,
2676 events::{
2677 ChildExecutionOutputDescription, ExecutionResult, FailureStatus,
2678 UnitTerminateReason,
2679 },
2680 },
2681 test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
2682 };
2683 use bytes::Bytes;
2684 use chrono::Local;
2685 use nextest_metadata::{RustBinaryId, TestCaseName};
2686 use quick_junit::ReportUuid;
2687 use smol_str::SmolStr;
2688 use std::{num::NonZero, sync::Arc};
2689
2690 fn with_reporter<'a, F>(f: F, out: &'a mut String)
2694 where
2695 F: FnOnce(DisplayReporter<'a>),
2696 {
2697 with_reporter_impl(f, out, false)
2698 }
2699
2700 fn with_verbose_reporter<'a, F>(f: F, out: &'a mut String)
2702 where
2703 F: FnOnce(DisplayReporter<'a>),
2704 {
2705 with_reporter_impl(f, out, true)
2706 }
2707
2708 fn with_reporter_impl<'a, F>(f: F, out: &'a mut String, verbose: bool)
2709 where
2710 F: FnOnce(DisplayReporter<'a>),
2711 {
2712 let builder = DisplayReporterBuilder {
2713 mode: NextestRunMode::Test,
2714 default_filter: CompiledDefaultFilter::for_default_config(),
2715 display_config: DisplayConfig {
2716 show_progress: ShowProgress::Counter,
2717 no_capture: true,
2718 status_level: Some(StatusLevel::Fail),
2719 final_status_level: Some(FinalStatusLevel::Fail),
2720 profile_status_level: StatusLevel::Fail,
2721 profile_final_status_level: FinalStatusLevel::Fail,
2722 },
2723 test_count: 5000,
2724 success_output: Some(TestOutputDisplay::Immediate),
2725 failure_output: Some(TestOutputDisplay::Immediate),
2726 should_colorize: false,
2727 verbose,
2728 no_output_indent: false,
2729 max_progress_running: MaxProgressRunning::default(),
2730 show_term_progress: ShowTerminalProgress::No,
2731 displayer_kind: DisplayerKind::Live,
2732 };
2733
2734 let output = ReporterOutput::Writer {
2735 writer: out,
2736 use_unicode: true,
2737 };
2738 let reporter = builder.build(output);
2739 f(reporter);
2740 }
2741
2742 #[test]
2743 fn final_status_line() {
2744 let binary_id = RustBinaryId::new("my-binary-id");
2745 let test_name = TestCaseName::new("test1");
2746 let test_instance = TestInstanceId {
2747 binary_id: &binary_id,
2748 test_name: &test_name,
2749 };
2750
2751 let fail_result_internal = ExecutionResult::Fail {
2752 failure_status: FailureStatus::ExitCode(1),
2753 leaked: false,
2754 };
2755 let fail_result = ExecutionResultDescription::from(fail_result_internal);
2756
2757 let fail_status = ExecuteStatus {
2758 retry_data: RetryData {
2759 attempt: 1,
2760 total_attempts: 2,
2761 },
2762 output: make_split_output(Some(fail_result_internal), "", ""),
2764 result: fail_result.clone(),
2765 start_time: Local::now().into(),
2766 time_taken: Duration::from_secs(1),
2767 is_slow: false,
2768 delay_before_start: Duration::ZERO,
2769 error_summary: None,
2770 output_error_slice: None,
2771 };
2772 let fail_describe = ExecutionDescription::Failure {
2773 first_status: &fail_status,
2774 last_status: &fail_status,
2775 retries: &[],
2776 };
2777
2778 let flaky_status = ExecuteStatus {
2779 retry_data: RetryData {
2780 attempt: 2,
2781 total_attempts: 2,
2782 },
2783 output: make_split_output(Some(fail_result_internal), "", ""),
2785 result: ExecutionResultDescription::Pass,
2786 start_time: Local::now().into(),
2787 time_taken: Duration::from_secs(2),
2788 is_slow: false,
2789 delay_before_start: Duration::ZERO,
2790 error_summary: None,
2791 output_error_slice: None,
2792 };
2793
2794 let statuses = ExecutionStatuses::new(vec![fail_status.clone(), flaky_status]);
2796 let flaky_describe = statuses.describe();
2797
2798 let mut out = String::new();
2799
2800 with_reporter(
2801 |mut reporter| {
2802 reporter
2804 .inner
2805 .write_final_status_line(
2806 None,
2807 TestInstanceCounter::None,
2808 test_instance,
2809 fail_describe,
2810 reporter.output.writer_mut().unwrap(),
2811 )
2812 .unwrap();
2813
2814 reporter
2815 .inner
2816 .write_final_status_line(
2817 Some(StressIndex {
2818 current: 1,
2819 total: None,
2820 }),
2821 TestInstanceCounter::Padded,
2822 test_instance,
2823 flaky_describe,
2824 reporter.output.writer_mut().unwrap(),
2825 )
2826 .unwrap();
2827
2828 reporter
2829 .inner
2830 .write_final_status_line(
2831 Some(StressIndex {
2832 current: 2,
2833 total: Some(NonZero::new(3).unwrap()),
2834 }),
2835 TestInstanceCounter::Counter {
2836 current: 20,
2837 total: 5000,
2838 },
2839 test_instance,
2840 flaky_describe,
2841 reporter.output.writer_mut().unwrap(),
2842 )
2843 .unwrap();
2844 },
2845 &mut out,
2846 );
2847
2848 insta::assert_snapshot!("final_status_output", out,);
2849 }
2850
2851 #[test]
2852 fn status_line_all_variants() {
2853 let binary_id = RustBinaryId::new("my-binary-id");
2854 let test_name = TestCaseName::new("test_name");
2855 let test_instance = TestInstanceId {
2856 binary_id: &binary_id,
2857 test_name: &test_name,
2858 };
2859
2860 let pass_result_internal = ExecutionResult::Pass;
2862 let pass_result = ExecutionResultDescription::from(pass_result_internal);
2863
2864 let leak_pass_result_internal = ExecutionResult::Leak {
2865 result: LeakTimeoutResult::Pass,
2866 };
2867 let leak_pass_result = ExecutionResultDescription::from(leak_pass_result_internal);
2868
2869 let timeout_pass_result_internal = ExecutionResult::Timeout {
2870 result: SlowTimeoutResult::Pass,
2871 };
2872 let timeout_pass_result = ExecutionResultDescription::from(timeout_pass_result_internal);
2873
2874 let fail_result_internal = ExecutionResult::Fail {
2876 failure_status: FailureStatus::ExitCode(1),
2877 leaked: false,
2878 };
2879 let fail_result = ExecutionResultDescription::from(fail_result_internal);
2880
2881 let fail_leak_result_internal = ExecutionResult::Fail {
2882 failure_status: FailureStatus::ExitCode(1),
2883 leaked: true,
2884 };
2885 let fail_leak_result = ExecutionResultDescription::from(fail_leak_result_internal);
2886
2887 let exec_fail_result_internal = ExecutionResult::ExecFail;
2888 let exec_fail_result = ExecutionResultDescription::from(exec_fail_result_internal);
2889
2890 let leak_fail_result_internal = ExecutionResult::Leak {
2891 result: LeakTimeoutResult::Fail,
2892 };
2893 let leak_fail_result = ExecutionResultDescription::from(leak_fail_result_internal);
2894
2895 let timeout_fail_result_internal = ExecutionResult::Timeout {
2896 result: SlowTimeoutResult::Fail,
2897 };
2898 let timeout_fail_result = ExecutionResultDescription::from(timeout_fail_result_internal);
2899
2900 let abort_unix_result = ExecutionResultDescription::Fail {
2902 failure: FailureDescription::Abort {
2903 abort: AbortDescription::UnixSignal {
2904 signal: 11,
2905 name: Some("SEGV".into()),
2906 },
2907 },
2908 leaked: false,
2909 };
2910 let abort_windows_result = ExecutionResultDescription::Fail {
2911 failure: FailureDescription::Abort {
2912 abort: AbortDescription::WindowsNtStatus {
2913 code: 0xC0000005_u32 as i32,
2915 message: Some("Access violation".into()),
2916 },
2917 },
2918 leaked: false,
2919 };
2920
2921 let pass_status = ExecuteStatus {
2923 retry_data: RetryData {
2924 attempt: 1,
2925 total_attempts: 1,
2926 },
2927 output: make_split_output(Some(pass_result_internal), "", ""),
2928 result: pass_result.clone(),
2929 start_time: Local::now().into(),
2930 time_taken: Duration::from_secs(1),
2931 is_slow: false,
2932 delay_before_start: Duration::ZERO,
2933 error_summary: None,
2934 output_error_slice: None,
2935 };
2936
2937 let leak_pass_status = ExecuteStatus {
2938 retry_data: RetryData {
2939 attempt: 1,
2940 total_attempts: 1,
2941 },
2942 output: make_split_output(Some(leak_pass_result_internal), "", ""),
2943 result: leak_pass_result.clone(),
2944 start_time: Local::now().into(),
2945 time_taken: Duration::from_secs(2),
2946 is_slow: false,
2947 delay_before_start: Duration::ZERO,
2948 error_summary: None,
2949 output_error_slice: None,
2950 };
2951
2952 let timeout_pass_status = ExecuteStatus {
2953 retry_data: RetryData {
2954 attempt: 1,
2955 total_attempts: 1,
2956 },
2957 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
2958 result: timeout_pass_result.clone(),
2959 start_time: Local::now().into(),
2960 time_taken: Duration::from_secs(240),
2961 is_slow: false,
2962 delay_before_start: Duration::ZERO,
2963 error_summary: None,
2964 output_error_slice: None,
2965 };
2966
2967 let pass_slow_status = ExecuteStatus {
2969 retry_data: RetryData {
2970 attempt: 1,
2971 total_attempts: 1,
2972 },
2973 output: make_split_output(Some(pass_result_internal), "", ""),
2974 result: pass_result.clone(),
2975 start_time: Local::now().into(),
2976 time_taken: Duration::from_secs(30),
2977 is_slow: true,
2978 delay_before_start: Duration::ZERO,
2979 error_summary: None,
2980 output_error_slice: None,
2981 };
2982
2983 let leak_pass_slow_status = ExecuteStatus {
2984 retry_data: RetryData {
2985 attempt: 1,
2986 total_attempts: 1,
2987 },
2988 output: make_split_output(Some(leak_pass_result_internal), "", ""),
2989 result: leak_pass_result.clone(),
2990 start_time: Local::now().into(),
2991 time_taken: Duration::from_secs(30),
2992 is_slow: true,
2993 delay_before_start: Duration::ZERO,
2994 error_summary: None,
2995 output_error_slice: None,
2996 };
2997
2998 let timeout_pass_slow_status = ExecuteStatus {
2999 retry_data: RetryData {
3000 attempt: 1,
3001 total_attempts: 1,
3002 },
3003 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3004 result: timeout_pass_result.clone(),
3005 start_time: Local::now().into(),
3006 time_taken: Duration::from_secs(300),
3007 is_slow: true,
3008 delay_before_start: Duration::ZERO,
3009 error_summary: None,
3010 output_error_slice: None,
3011 };
3012
3013 let flaky_first_status = ExecuteStatus {
3015 retry_data: RetryData {
3016 attempt: 1,
3017 total_attempts: 2,
3018 },
3019 output: make_split_output(Some(fail_result_internal), "", ""),
3020 result: fail_result.clone(),
3021 start_time: Local::now().into(),
3022 time_taken: Duration::from_secs(1),
3023 is_slow: false,
3024 delay_before_start: Duration::ZERO,
3025 error_summary: None,
3026 output_error_slice: None,
3027 };
3028 let flaky_last_status = ExecuteStatus {
3029 retry_data: RetryData {
3030 attempt: 2,
3031 total_attempts: 2,
3032 },
3033 output: make_split_output(Some(pass_result_internal), "", ""),
3034 result: pass_result.clone(),
3035 start_time: Local::now().into(),
3036 time_taken: Duration::from_secs(1),
3037 is_slow: false,
3038 delay_before_start: Duration::ZERO,
3039 error_summary: None,
3040 output_error_slice: None,
3041 };
3042
3043 let fail_status = ExecuteStatus {
3045 retry_data: RetryData {
3046 attempt: 1,
3047 total_attempts: 1,
3048 },
3049 output: make_split_output(Some(fail_result_internal), "", ""),
3050 result: fail_result.clone(),
3051 start_time: Local::now().into(),
3052 time_taken: Duration::from_secs(1),
3053 is_slow: false,
3054 delay_before_start: Duration::ZERO,
3055 error_summary: None,
3056 output_error_slice: None,
3057 };
3058
3059 let fail_leak_status = ExecuteStatus {
3060 retry_data: RetryData {
3061 attempt: 1,
3062 total_attempts: 1,
3063 },
3064 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3065 result: fail_leak_result.clone(),
3066 start_time: Local::now().into(),
3067 time_taken: Duration::from_secs(1),
3068 is_slow: false,
3069 delay_before_start: Duration::ZERO,
3070 error_summary: None,
3071 output_error_slice: None,
3072 };
3073
3074 let exec_fail_status = ExecuteStatus {
3075 retry_data: RetryData {
3076 attempt: 1,
3077 total_attempts: 1,
3078 },
3079 output: make_split_output(Some(exec_fail_result_internal), "", ""),
3080 result: exec_fail_result.clone(),
3081 start_time: Local::now().into(),
3082 time_taken: Duration::from_secs(1),
3083 is_slow: false,
3084 delay_before_start: Duration::ZERO,
3085 error_summary: None,
3086 output_error_slice: None,
3087 };
3088
3089 let leak_fail_status = ExecuteStatus {
3090 retry_data: RetryData {
3091 attempt: 1,
3092 total_attempts: 1,
3093 },
3094 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3095 result: leak_fail_result.clone(),
3096 start_time: Local::now().into(),
3097 time_taken: Duration::from_secs(1),
3098 is_slow: false,
3099 delay_before_start: Duration::ZERO,
3100 error_summary: None,
3101 output_error_slice: None,
3102 };
3103
3104 let timeout_fail_status = ExecuteStatus {
3105 retry_data: RetryData {
3106 attempt: 1,
3107 total_attempts: 1,
3108 },
3109 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3110 result: timeout_fail_result.clone(),
3111 start_time: Local::now().into(),
3112 time_taken: Duration::from_secs(60),
3113 is_slow: false,
3114 delay_before_start: Duration::ZERO,
3115 error_summary: None,
3116 output_error_slice: None,
3117 };
3118
3119 let abort_unix_status = ExecuteStatus {
3120 retry_data: RetryData {
3121 attempt: 1,
3122 total_attempts: 1,
3123 },
3124 output: make_split_output(None, "", ""),
3125 result: abort_unix_result.clone(),
3126 start_time: Local::now().into(),
3127 time_taken: Duration::from_secs(1),
3128 is_slow: false,
3129 delay_before_start: Duration::ZERO,
3130 error_summary: None,
3131 output_error_slice: None,
3132 };
3133
3134 let abort_windows_status = ExecuteStatus {
3135 retry_data: RetryData {
3136 attempt: 1,
3137 total_attempts: 1,
3138 },
3139 output: make_split_output(None, "", ""),
3140 result: abort_windows_result.clone(),
3141 start_time: Local::now().into(),
3142 time_taken: Duration::from_secs(1),
3143 is_slow: false,
3144 delay_before_start: Duration::ZERO,
3145 error_summary: None,
3146 output_error_slice: None,
3147 };
3148
3149 let fail_retry_status = ExecuteStatus {
3151 retry_data: RetryData {
3152 attempt: 2,
3153 total_attempts: 2,
3154 },
3155 output: make_split_output(Some(fail_result_internal), "", ""),
3156 result: fail_result.clone(),
3157 start_time: Local::now().into(),
3158 time_taken: Duration::from_secs(1),
3159 is_slow: false,
3160 delay_before_start: Duration::ZERO,
3161 error_summary: None,
3162 output_error_slice: None,
3163 };
3164
3165 let fail_leak_retry_status = ExecuteStatus {
3166 retry_data: RetryData {
3167 attempt: 2,
3168 total_attempts: 2,
3169 },
3170 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3171 result: fail_leak_result.clone(),
3172 start_time: Local::now().into(),
3173 time_taken: Duration::from_secs(1),
3174 is_slow: false,
3175 delay_before_start: Duration::ZERO,
3176 error_summary: None,
3177 output_error_slice: None,
3178 };
3179
3180 let leak_fail_retry_status = ExecuteStatus {
3181 retry_data: RetryData {
3182 attempt: 2,
3183 total_attempts: 2,
3184 },
3185 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3186 result: leak_fail_result.clone(),
3187 start_time: Local::now().into(),
3188 time_taken: Duration::from_secs(1),
3189 is_slow: false,
3190 delay_before_start: Duration::ZERO,
3191 error_summary: None,
3192 output_error_slice: None,
3193 };
3194
3195 let timeout_fail_retry_status = ExecuteStatus {
3196 retry_data: RetryData {
3197 attempt: 2,
3198 total_attempts: 2,
3199 },
3200 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3201 result: timeout_fail_result.clone(),
3202 start_time: Local::now().into(),
3203 time_taken: Duration::from_secs(60),
3204 is_slow: false,
3205 delay_before_start: Duration::ZERO,
3206 error_summary: None,
3207 output_error_slice: None,
3208 };
3209
3210 let pass_describe = ExecutionDescription::Success {
3212 single_status: &pass_status,
3213 };
3214 let leak_pass_describe = ExecutionDescription::Success {
3215 single_status: &leak_pass_status,
3216 };
3217 let timeout_pass_describe = ExecutionDescription::Success {
3218 single_status: &timeout_pass_status,
3219 };
3220 let pass_slow_describe = ExecutionDescription::Success {
3221 single_status: &pass_slow_status,
3222 };
3223 let leak_pass_slow_describe = ExecutionDescription::Success {
3224 single_status: &leak_pass_slow_status,
3225 };
3226 let timeout_pass_slow_describe = ExecutionDescription::Success {
3227 single_status: &timeout_pass_slow_status,
3228 };
3229 let flaky_describe = ExecutionDescription::Flaky {
3230 last_status: &flaky_last_status,
3231 prior_statuses: std::slice::from_ref(&flaky_first_status),
3232 };
3233 let fail_describe = ExecutionDescription::Failure {
3234 first_status: &fail_status,
3235 last_status: &fail_status,
3236 retries: &[],
3237 };
3238 let fail_leak_describe = ExecutionDescription::Failure {
3239 first_status: &fail_leak_status,
3240 last_status: &fail_leak_status,
3241 retries: &[],
3242 };
3243 let exec_fail_describe = ExecutionDescription::Failure {
3244 first_status: &exec_fail_status,
3245 last_status: &exec_fail_status,
3246 retries: &[],
3247 };
3248 let leak_fail_describe = ExecutionDescription::Failure {
3249 first_status: &leak_fail_status,
3250 last_status: &leak_fail_status,
3251 retries: &[],
3252 };
3253 let timeout_fail_describe = ExecutionDescription::Failure {
3254 first_status: &timeout_fail_status,
3255 last_status: &timeout_fail_status,
3256 retries: &[],
3257 };
3258 let abort_unix_describe = ExecutionDescription::Failure {
3259 first_status: &abort_unix_status,
3260 last_status: &abort_unix_status,
3261 retries: &[],
3262 };
3263 let abort_windows_describe = ExecutionDescription::Failure {
3264 first_status: &abort_windows_status,
3265 last_status: &abort_windows_status,
3266 retries: &[],
3267 };
3268 let fail_retry_describe = ExecutionDescription::Failure {
3269 first_status: &fail_status,
3270 last_status: &fail_retry_status,
3271 retries: std::slice::from_ref(&fail_retry_status),
3272 };
3273 let fail_leak_retry_describe = ExecutionDescription::Failure {
3274 first_status: &fail_leak_status,
3275 last_status: &fail_leak_retry_status,
3276 retries: std::slice::from_ref(&fail_leak_retry_status),
3277 };
3278 let leak_fail_retry_describe = ExecutionDescription::Failure {
3279 first_status: &leak_fail_status,
3280 last_status: &leak_fail_retry_status,
3281 retries: std::slice::from_ref(&leak_fail_retry_status),
3282 };
3283 let timeout_fail_retry_describe = ExecutionDescription::Failure {
3284 first_status: &timeout_fail_status,
3285 last_status: &timeout_fail_retry_status,
3286 retries: std::slice::from_ref(&timeout_fail_retry_status),
3287 };
3288
3289 let test_cases: Vec<(&str, ExecutionDescription<'_, LiveSpec>)> = vec![
3292 ("pass", pass_describe),
3294 ("leak pass", leak_pass_describe),
3295 ("timeout pass", timeout_pass_describe),
3296 ("pass slow", pass_slow_describe),
3298 ("leak pass slow", leak_pass_slow_describe),
3299 ("timeout pass slow", timeout_pass_slow_describe),
3300 ("flaky", flaky_describe),
3302 ("fail", fail_describe),
3304 ("fail leak", fail_leak_describe),
3305 ("exec fail", exec_fail_describe),
3306 ("leak fail", leak_fail_describe),
3307 ("timeout fail", timeout_fail_describe),
3308 ("abort unix", abort_unix_describe),
3309 ("abort windows", abort_windows_describe),
3310 ("fail retry", fail_retry_describe),
3312 ("fail leak retry", fail_leak_retry_describe),
3313 ("leak fail retry", leak_fail_retry_describe),
3314 ("timeout fail retry", timeout_fail_retry_describe),
3315 ];
3316
3317 let mut out = String::new();
3318 let mut counter = 0usize;
3319
3320 with_reporter(
3321 |mut reporter| {
3322 let writer = reporter.output.writer_mut().unwrap();
3323
3324 for (kind_name, kind) in [
3326 ("intermediate", StatusLineKind::Intermediate),
3327 ("final", StatusLineKind::Final),
3328 ] {
3329 writeln!(writer, "=== {kind_name} ===").unwrap();
3330
3331 for (label, describe) in &test_cases {
3332 counter += 1;
3333 let test_counter = TestInstanceCounter::Counter {
3334 current: counter,
3335 total: 100,
3336 };
3337
3338 writeln!(writer, "# {label}: ").unwrap();
3340
3341 reporter
3342 .inner
3343 .write_status_line_impl(
3344 None,
3345 test_counter,
3346 test_instance,
3347 *describe,
3348 kind,
3349 writer,
3350 )
3351 .unwrap();
3352 }
3353 }
3354 },
3355 &mut out,
3356 );
3357
3358 insta::assert_snapshot!("status_line_all_variants", out);
3359 }
3360
3361 #[test]
3362 fn test_summary_line() {
3363 let run_id = ReportUuid::nil();
3364 let mut out = String::new();
3365
3366 with_reporter(
3367 |mut reporter| {
3368 let run_stats_success = RunStats {
3370 initial_run_count: 5,
3371 finished_count: 5,
3372 setup_scripts_initial_count: 0,
3373 setup_scripts_finished_count: 0,
3374 setup_scripts_passed: 0,
3375 setup_scripts_failed: 0,
3376 setup_scripts_exec_failed: 0,
3377 setup_scripts_timed_out: 0,
3378 passed: 5,
3379 passed_slow: 0,
3380 passed_timed_out: 0,
3381 flaky: 0,
3382 failed: 0,
3383 failed_slow: 0,
3384 failed_timed_out: 0,
3385 leaky: 0,
3386 leaky_failed: 0,
3387 exec_failed: 0,
3388 skipped: 0,
3389 cancel_reason: None,
3390 };
3391
3392 reporter
3393 .write_event(&TestEvent {
3394 timestamp: Local::now().into(),
3395 elapsed: Duration::ZERO,
3396 kind: TestEventKind::RunFinished {
3397 run_id,
3398 start_time: Local::now().into(),
3399 elapsed: Duration::from_secs(2),
3400 run_stats: RunFinishedStats::Single(run_stats_success),
3401 outstanding_not_seen: None,
3402 },
3403 })
3404 .unwrap();
3405
3406 let run_stats_mixed = RunStats {
3408 initial_run_count: 10,
3409 finished_count: 8,
3410 setup_scripts_initial_count: 1,
3411 setup_scripts_finished_count: 1,
3412 setup_scripts_passed: 1,
3413 setup_scripts_failed: 0,
3414 setup_scripts_exec_failed: 0,
3415 setup_scripts_timed_out: 0,
3416 passed: 5,
3417 passed_slow: 1,
3418 passed_timed_out: 2,
3419 flaky: 1,
3420 failed: 2,
3421 failed_slow: 0,
3422 failed_timed_out: 1,
3423 leaky: 1,
3424 leaky_failed: 0,
3425 exec_failed: 1,
3426 skipped: 2,
3427 cancel_reason: Some(CancelReason::Signal),
3428 };
3429
3430 reporter
3431 .write_event(&TestEvent {
3432 timestamp: Local::now().into(),
3433 elapsed: Duration::ZERO,
3434 kind: TestEventKind::RunFinished {
3435 run_id,
3436 start_time: Local::now().into(),
3437 elapsed: Duration::from_millis(15750),
3438 run_stats: RunFinishedStats::Single(run_stats_mixed),
3439 outstanding_not_seen: None,
3440 },
3441 })
3442 .unwrap();
3443
3444 let stress_stats_success = StressRunStats {
3446 completed: StressIndex {
3447 current: 25,
3448 total: Some(NonZero::new(50).unwrap()),
3449 },
3450 success_count: 25,
3451 failed_count: 0,
3452 last_final_stats: FinalRunStats::Success,
3453 };
3454
3455 reporter
3456 .write_event(&TestEvent {
3457 timestamp: Local::now().into(),
3458 elapsed: Duration::ZERO,
3459 kind: TestEventKind::RunFinished {
3460 run_id,
3461 start_time: Local::now().into(),
3462 elapsed: Duration::from_secs(120),
3463 run_stats: RunFinishedStats::Stress(stress_stats_success),
3464 outstanding_not_seen: None,
3465 },
3466 })
3467 .unwrap();
3468
3469 let stress_stats_failed = StressRunStats {
3471 completed: StressIndex {
3472 current: 15,
3473 total: None, },
3475 success_count: 12,
3476 failed_count: 3,
3477 last_final_stats: FinalRunStats::Cancelled {
3478 reason: Some(CancelReason::Interrupt),
3479 kind: RunStatsFailureKind::SetupScript,
3480 },
3481 };
3482
3483 reporter
3484 .write_event(&TestEvent {
3485 timestamp: Local::now().into(),
3486 elapsed: Duration::ZERO,
3487 kind: TestEventKind::RunFinished {
3488 run_id,
3489 start_time: Local::now().into(),
3490 elapsed: Duration::from_millis(45250),
3491 run_stats: RunFinishedStats::Stress(stress_stats_failed),
3492 outstanding_not_seen: None,
3493 },
3494 })
3495 .unwrap();
3496
3497 let run_stats_empty = RunStats {
3499 initial_run_count: 0,
3500 finished_count: 0,
3501 setup_scripts_initial_count: 0,
3502 setup_scripts_finished_count: 0,
3503 setup_scripts_passed: 0,
3504 setup_scripts_failed: 0,
3505 setup_scripts_exec_failed: 0,
3506 setup_scripts_timed_out: 0,
3507 passed: 0,
3508 passed_slow: 0,
3509 passed_timed_out: 0,
3510 flaky: 0,
3511 failed: 0,
3512 failed_slow: 0,
3513 failed_timed_out: 0,
3514 leaky: 0,
3515 leaky_failed: 0,
3516 exec_failed: 0,
3517 skipped: 0,
3518 cancel_reason: None,
3519 };
3520
3521 reporter
3522 .write_event(&TestEvent {
3523 timestamp: Local::now().into(),
3524 elapsed: Duration::ZERO,
3525 kind: TestEventKind::RunFinished {
3526 run_id,
3527 start_time: Local::now().into(),
3528 elapsed: Duration::from_millis(100),
3529 run_stats: RunFinishedStats::Single(run_stats_empty),
3530 outstanding_not_seen: None,
3531 },
3532 })
3533 .unwrap();
3534 },
3535 &mut out,
3536 );
3537
3538 insta::assert_snapshot!("summary_line_output", out,);
3539 }
3540
3541 #[test]
3545 fn test_info_response() {
3546 let args = vec!["arg1".to_string(), "arg2".to_string()];
3547 let binary_id = RustBinaryId::new("my-binary-id");
3548 let test_name1 = TestCaseName::new("test1");
3549 let test_name2 = TestCaseName::new("test2");
3550 let test_name3 = TestCaseName::new("test3");
3551 let test_name4 = TestCaseName::new("test4");
3552
3553 let mut out = String::new();
3554
3555 with_reporter(
3556 |mut reporter| {
3557 reporter
3559 .write_event(&TestEvent {
3560 timestamp: Local::now().into(),
3561 elapsed: Duration::ZERO,
3562 kind: TestEventKind::InfoStarted {
3563 total: 30,
3564 run_stats: RunStats {
3565 initial_run_count: 40,
3566 finished_count: 20,
3567 setup_scripts_initial_count: 1,
3568 setup_scripts_finished_count: 1,
3569 setup_scripts_passed: 1,
3570 setup_scripts_failed: 0,
3571 setup_scripts_exec_failed: 0,
3572 setup_scripts_timed_out: 0,
3573 passed: 17,
3574 passed_slow: 4,
3575 passed_timed_out: 3,
3576 flaky: 2,
3577 failed: 2,
3578 failed_slow: 1,
3579 failed_timed_out: 1,
3580 leaky: 1,
3581 leaky_failed: 2,
3582 exec_failed: 1,
3583 skipped: 5,
3584 cancel_reason: None,
3585 },
3586 },
3587 })
3588 .unwrap();
3589
3590 reporter
3592 .write_event(&TestEvent {
3593 timestamp: Local::now().into(),
3594 elapsed: Duration::ZERO,
3595 kind: TestEventKind::InfoResponse {
3596 index: 0,
3597 total: 20,
3598 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3601 stress_index: None,
3602 script_id: ScriptId::new(SmolStr::new("setup")).unwrap(),
3603 program: "setup".to_owned(),
3604 args: args.clone(),
3605 state: UnitState::Running {
3606 pid: 4567,
3607 time_taken: Duration::from_millis(1234),
3608 slow_after: None,
3609 },
3610 output: make_split_output(
3611 None,
3612 "script stdout 1",
3613 "script stderr 1",
3614 ),
3615 }),
3616 },
3617 })
3618 .unwrap();
3619
3620 reporter
3623 .write_event(&TestEvent {
3624 timestamp: Local::now().into(),
3625 elapsed: Duration::ZERO,
3626 kind: TestEventKind::InfoResponse {
3627 index: 1,
3628 total: 20,
3629 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3630 stress_index: None,
3631 script_id: ScriptId::new(SmolStr::new("setup-slow")).unwrap(),
3632 program: "setup-slow".to_owned(),
3633 args: args.clone(),
3634 state: UnitState::Running {
3635 pid: 4568,
3636 time_taken: Duration::from_millis(1234),
3637 slow_after: Some(Duration::from_millis(1000)),
3638 },
3639 output: make_combined_output_with_errors(
3640 None,
3641 "script output 2\n",
3642 vec![ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3643 std::io::Error::other("read stdout error"),
3644 )))],
3645 ),
3646 }),
3647 },
3648 })
3649 .unwrap();
3650
3651 reporter
3653 .write_event(&TestEvent {
3654 timestamp: Local::now().into(),
3655 elapsed: Duration::ZERO,
3656 kind: TestEventKind::InfoResponse {
3657 index: 2,
3658 total: 20,
3659 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3660 stress_index: None,
3661 script_id: ScriptId::new(SmolStr::new("setup-terminating"))
3662 .unwrap(),
3663 program: "setup-terminating".to_owned(),
3664 args: args.clone(),
3665 state: UnitState::Terminating(UnitTerminatingState {
3666 pid: 5094,
3667 time_taken: Duration::from_millis(1234),
3668 reason: UnitTerminateReason::Signal,
3669 method: UnitTerminateMethod::Fake,
3670 waiting_duration: Duration::from_millis(6789),
3671 remaining: Duration::from_millis(9786),
3672 }),
3673 output: make_split_output_with_errors(
3674 None,
3675 "script output 3\n",
3676 "script stderr 3\n",
3677 vec![
3678 ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3679 std::io::Error::other("read stdout error"),
3680 ))),
3681 ChildError::Fd(ChildFdError::ReadStderr(Arc::new(
3682 std::io::Error::other("read stderr error"),
3683 ))),
3684 ],
3685 ),
3686 }),
3687 },
3688 })
3689 .unwrap();
3690
3691 reporter
3695 .write_event(&TestEvent {
3696 timestamp: Local::now().into(),
3697 elapsed: Duration::ZERO,
3698 kind: TestEventKind::InfoResponse {
3699 index: 3,
3700 total: 20,
3701 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3702 stress_index: Some(StressIndex {
3703 current: 0,
3704 total: None,
3705 }),
3706 script_id: ScriptId::new(SmolStr::new("setup-exiting")).unwrap(),
3707 program: "setup-exiting".to_owned(),
3708 args: args.clone(),
3709 state: UnitState::Exiting {
3710 pid: 9987,
3711 time_taken: Duration::from_millis(1234),
3712 slow_after: Some(Duration::from_millis(1000)),
3713 tentative_result: Some(ExecutionResult::ExecFail),
3717 waiting_duration: Duration::from_millis(10467),
3718 remaining: Duration::from_millis(335),
3719 },
3720 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3721 Arc::new(std::io::Error::other("exec error")),
3722 ))
3723 .into(),
3724 }),
3725 },
3726 })
3727 .unwrap();
3728
3729 reporter
3731 .write_event(&TestEvent {
3732 timestamp: Local::now().into(),
3733 elapsed: Duration::ZERO,
3734 kind: TestEventKind::InfoResponse {
3735 index: 4,
3736 total: 20,
3737 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3738 stress_index: Some(StressIndex {
3739 current: 1,
3740 total: Some(NonZero::new(3).unwrap()),
3741 }),
3742 script_id: ScriptId::new(SmolStr::new("setup-exited")).unwrap(),
3743 program: "setup-exited".to_owned(),
3744 args: args.clone(),
3745 state: UnitState::Exited {
3746 result: ExecutionResult::Fail {
3747 failure_status: FailureStatus::ExitCode(1),
3748 leaked: true,
3749 },
3750 time_taken: Duration::from_millis(9999),
3751 slow_after: Some(Duration::from_millis(3000)),
3752 },
3753 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3754 Arc::new(std::io::Error::other("exec error")),
3755 ))
3756 .into(),
3757 }),
3758 },
3759 })
3760 .unwrap();
3761
3762 reporter
3764 .write_event(&TestEvent {
3765 timestamp: Local::now().into(),
3766 elapsed: Duration::ZERO,
3767 kind: TestEventKind::InfoResponse {
3768 index: 5,
3769 total: 20,
3770 response: InfoResponse::Test(TestInfoResponse {
3771 stress_index: None,
3772 test_instance: TestInstanceId {
3773 binary_id: &binary_id,
3774 test_name: &test_name1,
3775 },
3776 retry_data: RetryData {
3777 attempt: 1,
3778 total_attempts: 1,
3779 },
3780 state: UnitState::Running {
3781 pid: 12345,
3782 time_taken: Duration::from_millis(400),
3783 slow_after: None,
3784 },
3785 output: make_split_output(None, "abc", "def"),
3786 }),
3787 },
3788 })
3789 .unwrap();
3790
3791 reporter
3793 .write_event(&TestEvent {
3794 timestamp: Local::now().into(),
3795 elapsed: Duration::ZERO,
3796 kind: TestEventKind::InfoResponse {
3797 index: 6,
3798 total: 20,
3799 response: InfoResponse::Test(TestInfoResponse {
3800 stress_index: Some(StressIndex {
3801 current: 0,
3802 total: None,
3803 }),
3804 test_instance: TestInstanceId {
3805 binary_id: &binary_id,
3806 test_name: &test_name2,
3807 },
3808 retry_data: RetryData {
3809 attempt: 2,
3810 total_attempts: 3,
3811 },
3812 state: UnitState::Terminating(UnitTerminatingState {
3813 pid: 12346,
3814 time_taken: Duration::from_millis(99999),
3815 reason: UnitTerminateReason::Timeout,
3816 method: UnitTerminateMethod::Fake,
3817 waiting_duration: Duration::from_millis(6789),
3818 remaining: Duration::from_millis(9786),
3819 }),
3820 output: make_split_output(None, "abc", "def"),
3821 }),
3822 },
3823 })
3824 .unwrap();
3825
3826 reporter
3828 .write_event(&TestEvent {
3829 timestamp: Local::now().into(),
3830 elapsed: Duration::ZERO,
3831 kind: TestEventKind::InfoResponse {
3832 index: 7,
3833 total: 20,
3834 response: InfoResponse::Test(TestInfoResponse {
3835 stress_index: None,
3836 test_instance: TestInstanceId {
3837 binary_id: &binary_id,
3838 test_name: &test_name3,
3839 },
3840 retry_data: RetryData {
3841 attempt: 2,
3842 total_attempts: 3,
3843 },
3844 state: UnitState::Exiting {
3845 pid: 99999,
3846 time_taken: Duration::from_millis(99999),
3847 slow_after: Some(Duration::from_millis(33333)),
3848 tentative_result: None,
3849 waiting_duration: Duration::from_millis(1),
3850 remaining: Duration::from_millis(999),
3851 },
3852 output: make_split_output(None, "abc", "def"),
3853 }),
3854 },
3855 })
3856 .unwrap();
3857
3858 reporter
3860 .write_event(&TestEvent {
3861 timestamp: Local::now().into(),
3862 elapsed: Duration::ZERO,
3863 kind: TestEventKind::InfoResponse {
3864 index: 8,
3865 total: 20,
3866 response: InfoResponse::Test(TestInfoResponse {
3867 stress_index: Some(StressIndex {
3868 current: 1,
3869 total: Some(NonZero::new(3).unwrap()),
3870 }),
3871 test_instance: TestInstanceId {
3872 binary_id: &binary_id,
3873 test_name: &test_name4,
3874 },
3875 retry_data: RetryData {
3876 attempt: 1,
3877 total_attempts: 5,
3878 },
3879 state: UnitState::Exited {
3880 result: ExecutionResult::Pass,
3881 time_taken: Duration::from_millis(99999),
3882 slow_after: Some(Duration::from_millis(33333)),
3883 },
3884 output: make_combined_output_with_errors(
3885 Some(ExecutionResult::Pass),
3886 "abc\ndef\nghi\n",
3887 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
3888 std::io::Error::other("error waiting"),
3889 )))],
3890 ),
3891 }),
3892 },
3893 })
3894 .unwrap();
3895
3896 reporter
3898 .write_event(&TestEvent {
3899 timestamp: Local::now().into(),
3900 elapsed: Duration::ZERO,
3901 kind: TestEventKind::InfoResponse {
3902 index: 9,
3903 total: 20,
3904 response: InfoResponse::Test(TestInfoResponse {
3905 stress_index: None,
3906 test_instance: TestInstanceId {
3907 binary_id: &binary_id,
3908 test_name: &test_name4,
3909 },
3910 retry_data: RetryData {
3911 attempt: 1,
3915 total_attempts: 5,
3916 },
3917 state: UnitState::DelayBeforeNextAttempt {
3918 previous_result: ExecutionResult::ExecFail,
3919 previous_slow: true,
3920 waiting_duration: Duration::from_millis(1234),
3921 remaining: Duration::from_millis(5678),
3922 },
3923 output: make_combined_output_with_errors(
3926 Some(ExecutionResult::Pass),
3927 "*** THIS OUTPUT SHOULD BE IGNORED",
3928 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
3929 std::io::Error::other(
3930 "*** THIS ERROR SHOULD ALSO BE IGNORED",
3931 ),
3932 )))],
3933 ),
3934 }),
3935 },
3936 })
3937 .unwrap();
3938
3939 reporter
3940 .write_event(&TestEvent {
3941 timestamp: Local::now().into(),
3942 elapsed: Duration::ZERO,
3943 kind: TestEventKind::InfoFinished { missing: 2 },
3944 })
3945 .unwrap();
3946 },
3947 &mut out,
3948 );
3949
3950 insta::assert_snapshot!("info_response_output", out,);
3951 }
3952
3953 fn make_split_output(
3954 result: Option<ExecutionResult>,
3955 stdout: &str,
3956 stderr: &str,
3957 ) -> ChildExecutionOutputDescription<LiveSpec> {
3958 ChildExecutionOutput::Output {
3959 result,
3960 output: ChildOutput::Split(ChildSplitOutput {
3961 stdout: Some(Bytes::from(stdout.to_owned()).into()),
3962 stderr: Some(Bytes::from(stderr.to_owned()).into()),
3963 }),
3964 errors: None,
3965 }
3966 .into()
3967 }
3968
3969 fn make_split_output_with_errors(
3970 result: Option<ExecutionResult>,
3971 stdout: &str,
3972 stderr: &str,
3973 errors: Vec<ChildError>,
3974 ) -> ChildExecutionOutputDescription<LiveSpec> {
3975 ChildExecutionOutput::Output {
3976 result,
3977 output: ChildOutput::Split(ChildSplitOutput {
3978 stdout: Some(Bytes::from(stdout.to_owned()).into()),
3979 stderr: Some(Bytes::from(stderr.to_owned()).into()),
3980 }),
3981 errors: ErrorList::new("testing split output", errors),
3982 }
3983 .into()
3984 }
3985
3986 fn make_combined_output_with_errors(
3987 result: Option<ExecutionResult>,
3988 output: &str,
3989 errors: Vec<ChildError>,
3990 ) -> ChildExecutionOutputDescription<LiveSpec> {
3991 ChildExecutionOutput::Output {
3992 result,
3993 output: ChildOutput::Combined {
3994 output: Bytes::from(output.to_owned()).into(),
3995 },
3996 errors: ErrorList::new("testing split output", errors),
3997 }
3998 .into()
3999 }
4000
4001 #[test]
4002 fn verbose_command_line() {
4003 let binary_id = RustBinaryId::new("my-binary-id");
4004 let test_name = TestCaseName::new("test_name");
4005 let test_with_spaces = TestCaseName::new("test_with_spaces");
4006 let test_special_chars = TestCaseName::new("test_special_chars");
4007 let test_retry = TestCaseName::new("test_retry");
4008 let mut out = String::new();
4009
4010 with_verbose_reporter(
4011 |mut reporter| {
4012 let current_stats = RunStats {
4013 initial_run_count: 10,
4014 finished_count: 0,
4015 ..Default::default()
4016 };
4017
4018 reporter
4020 .write_event(&TestEvent {
4021 timestamp: Local::now().into(),
4022 elapsed: Duration::ZERO,
4023 kind: TestEventKind::TestStarted {
4024 stress_index: None,
4025 test_instance: TestInstanceId {
4026 binary_id: &binary_id,
4027 test_name: &test_name,
4028 },
4029 current_stats,
4030 running: 1,
4031 command_line: vec![
4032 "/path/to/binary".to_string(),
4033 "--exact".to_string(),
4034 "test_name".to_string(),
4035 ],
4036 },
4037 })
4038 .unwrap();
4039
4040 reporter
4042 .write_event(&TestEvent {
4043 timestamp: Local::now().into(),
4044 elapsed: Duration::ZERO,
4045 kind: TestEventKind::TestStarted {
4046 stress_index: None,
4047 test_instance: TestInstanceId {
4048 binary_id: &binary_id,
4049 test_name: &test_with_spaces,
4050 },
4051 current_stats,
4052 running: 2,
4053 command_line: vec![
4054 "/path/to/binary".to_string(),
4055 "--exact".to_string(),
4056 "test with spaces".to_string(),
4057 "--flag=value".to_string(),
4058 ],
4059 },
4060 })
4061 .unwrap();
4062
4063 reporter
4065 .write_event(&TestEvent {
4066 timestamp: Local::now().into(),
4067 elapsed: Duration::ZERO,
4068 kind: TestEventKind::TestStarted {
4069 stress_index: None,
4070 test_instance: TestInstanceId {
4071 binary_id: &binary_id,
4072 test_name: &test_special_chars,
4073 },
4074 current_stats,
4075 running: 3,
4076 command_line: vec![
4077 "/path/to/binary".to_string(),
4078 "test\"with\"quotes".to_string(),
4079 "test'with'single".to_string(),
4080 ],
4081 },
4082 })
4083 .unwrap();
4084
4085 reporter
4087 .write_event(&TestEvent {
4088 timestamp: Local::now().into(),
4089 elapsed: Duration::ZERO,
4090 kind: TestEventKind::TestRetryStarted {
4091 stress_index: None,
4092 test_instance: TestInstanceId {
4093 binary_id: &binary_id,
4094 test_name: &test_retry,
4095 },
4096 retry_data: RetryData {
4097 attempt: 2,
4098 total_attempts: 3,
4099 },
4100 running: 1,
4101 command_line: vec![
4102 "/path/to/binary".to_string(),
4103 "--exact".to_string(),
4104 "test_retry".to_string(),
4105 ],
4106 },
4107 })
4108 .unwrap();
4109
4110 reporter
4112 .write_event(&TestEvent {
4113 timestamp: Local::now().into(),
4114 elapsed: Duration::ZERO,
4115 kind: TestEventKind::TestRetryStarted {
4116 stress_index: None,
4117 test_instance: TestInstanceId {
4118 binary_id: &binary_id,
4119 test_name: &test_retry,
4120 },
4121 retry_data: RetryData {
4122 attempt: 3,
4123 total_attempts: 3,
4124 },
4125 running: 1,
4126 command_line: vec![
4127 "/path/to/binary".to_string(),
4128 "--exact".to_string(),
4129 "test_retry".to_string(),
4130 ],
4131 },
4132 })
4133 .unwrap();
4134 },
4135 &mut out,
4136 );
4137
4138 insta::assert_snapshot!("verbose_command_line", out);
4139 }
4140
4141 #[test]
4142 fn no_capture_settings() {
4143 let mut out = String::new();
4145
4146 with_reporter(
4147 |reporter| {
4148 assert!(reporter.inner.no_capture, "no_capture is true");
4149 let overrides = reporter.inner.unit_output.overrides();
4150 assert_eq!(
4151 overrides.force_failure_output,
4152 Some(TestOutputDisplay::Never),
4153 "failure output is never, overriding other settings"
4154 );
4155 assert_eq!(
4156 overrides.force_success_output,
4157 Some(TestOutputDisplay::Never),
4158 "success output is never, overriding other settings"
4159 );
4160 assert_eq!(
4161 reporter.inner.status_levels.status_level,
4162 StatusLevel::Pass,
4163 "status level is pass, overriding other settings"
4164 );
4165 },
4166 &mut out,
4167 );
4168 }
4169}
4170
4171#[cfg(all(windows, test))]
4172mod windows_tests {
4173 use super::*;
4174 use crate::reporter::events::AbortDescription;
4175 use windows_sys::Win32::{
4176 Foundation::{STATUS_CONTROL_C_EXIT, STATUS_CONTROL_STACK_VIOLATION},
4177 Globalization::SetThreadUILanguage,
4178 };
4179
4180 #[test]
4181 fn test_write_windows_abort_line() {
4182 unsafe {
4183 SetThreadUILanguage(0x0409);
4185 }
4186
4187 insta::assert_snapshot!(
4188 "ctrl_c_code",
4189 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_C_EXIT))
4190 );
4191 insta::assert_snapshot!(
4192 "stack_violation_code",
4193 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_STACK_VIOLATION)),
4194 );
4195 insta::assert_snapshot!("job_object", to_abort_line(AbortStatus::JobObject));
4196 }
4197
4198 #[track_caller]
4199 fn to_abort_line(status: AbortStatus) -> String {
4200 let mut buf = String::new();
4201 let description = AbortDescription::from(status);
4202 write_windows_abort_line(&description, &Styles::default(), &mut buf).unwrap();
4203 buf
4204 }
4205}