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::{FlakyResult, LeakTimeoutResult, SlowTimeoutResult},
24 overrides::CompiledDefaultFilter,
25 scripts::ScriptId,
26 },
27 errors::WriteEventError,
28 helpers::{
29 DisplayCounterIndex, DisplayScriptInstance, DisplayTestInstance, DurationRounding,
30 ThemeCharacters, decimal_char_width, plural,
31 },
32 indenter::indented,
33 list::TestInstanceId,
34 output_spec::{LiveSpec, RecordingSpec},
35 record::{LoadOutput, OutputEventKind, ReplayHeader, ShortestRunIdPrefix},
36 redact::Redactor,
37 reporter::{
38 displayer::progress::{ShowTerminalProgress, TerminalProgress},
39 events::*,
40 helpers::Styles,
41 imp::ReporterOutput,
42 },
43 run_mode::NextestRunMode,
44 runner::StressCount,
45 write_str::WriteStr,
46};
47use debug_ignore::DebugIgnore;
48use nextest_metadata::MismatchReason;
49use owo_colors::OwoColorize;
50use std::{
51 borrow::Cow,
52 cmp::{Ordering, Reverse},
53 io::{self, BufWriter, IsTerminal, Write},
54 time::Duration,
55};
56
57#[derive(Copy, Clone, Debug, Eq, PartialEq)]
59pub(crate) enum DisplayerKind {
60 Live,
62
63 Replay,
68}
69
70pub(crate) struct DisplayReporterBuilder {
71 pub(crate) mode: NextestRunMode,
72 pub(crate) default_filter: CompiledDefaultFilter,
73 pub(crate) display_config: DisplayConfig,
74 pub(crate) run_count: usize,
75 pub(crate) success_output: Option<TestOutputDisplay>,
76 pub(crate) failure_output: Option<TestOutputDisplay>,
77 pub(crate) should_colorize: bool,
78 pub(crate) verbose: bool,
79 pub(crate) no_output_indent: bool,
80 pub(crate) max_progress_running: MaxProgressRunning,
81 pub(crate) show_term_progress: ShowTerminalProgress,
82 pub(crate) displayer_kind: DisplayerKind,
83 pub(crate) redactor: Redactor,
84}
85
86impl DisplayReporterBuilder {
87 pub(crate) fn build<'a>(self, output: ReporterOutput<'a>) -> DisplayReporter<'a> {
88 let mut styles: Box<Styles> = Box::default();
89 if self.should_colorize {
90 styles.colorize();
91 }
92
93 let theme_characters = match &output {
94 ReporterOutput::Terminal => ThemeCharacters::detect(supports_unicode::Stream::Stderr),
95 ReporterOutput::Writer { use_unicode, .. } => {
96 let mut tc = ThemeCharacters::default();
97 if *use_unicode {
98 tc.use_unicode();
99 }
100 tc
101 }
102 };
103
104 let is_terminal = matches!(&output, ReporterOutput::Terminal) && io::stderr().is_terminal();
105 let is_ci = is_ci::uncached();
106
107 let resolved = self.display_config.resolve(is_terminal, is_ci);
108
109 let output = match output {
110 ReporterOutput::Terminal => {
111 let progress_bar = if resolved.progress_display == ProgressDisplay::Bar {
112 Some(ProgressBarState::new(
113 self.mode,
114 self.run_count,
115 theme_characters.progress_chars(),
116 self.max_progress_running,
117 ))
118 } else {
119 None
120 };
121 let term_progress = TerminalProgress::new(self.show_term_progress);
122 ReporterOutputImpl::Terminal {
123 progress_bar: progress_bar.map(Box::new),
124 term_progress,
125 }
126 }
127 ReporterOutput::Writer { writer, .. } => ReporterOutputImpl::Writer(writer),
128 };
129
130 let no_capture = self.display_config.no_capture;
131
132 let overrides = OutputDisplayOverrides {
136 force_success_output: match no_capture {
137 true => Some(TestOutputDisplay::Never),
138 false => self.success_output,
139 },
140 force_failure_output: match no_capture {
141 true => Some(TestOutputDisplay::Never),
142 false => self.failure_output,
143 },
144 force_exec_fail_output: match no_capture {
145 true => Some(TestOutputDisplay::Immediate),
146 false => self.failure_output,
147 },
148 };
149
150 let counter_width = matches!(resolved.progress_display, ProgressDisplay::Counter)
151 .then_some(decimal_char_width(self.run_count));
152
153 DisplayReporter {
154 inner: DisplayReporterImpl {
155 mode: self.mode,
156 default_filter: self.default_filter,
157 status_levels: resolved.status_levels,
158 no_capture,
159 verbose: self.verbose,
160 no_output_indent: self.no_output_indent,
161 counter_width,
162 styles,
163 theme_characters,
164 cancel_status: None,
165 unit_output: UnitOutputReporter::new(overrides, self.displayer_kind),
166 final_outputs: DebugIgnore(Vec::new()),
167 run_id_unique_prefix: None,
168 redactor: self.redactor,
169 },
170 output,
171 }
172 }
173}
174
175pub(crate) struct DisplayReporter<'a> {
178 inner: DisplayReporterImpl<'a>,
179 output: ReporterOutputImpl<'a>,
180}
181
182impl<'a> DisplayReporter<'a> {
183 pub(crate) fn tick(&mut self) {
184 self.output.tick(&self.inner.styles);
185 }
186
187 pub(crate) fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
188 match &mut self.output {
189 ReporterOutputImpl::Terminal {
190 progress_bar,
191 term_progress,
192 } => {
193 if let Some(term_progress) = term_progress {
194 term_progress.update_progress(event);
195 }
196
197 if let Some(state) = progress_bar {
198 let mut buf = String::new();
200 self.inner
201 .write_event_impl(event, &mut buf)
202 .map_err(WriteEventError::Io)?;
203
204 state.update_progress_bar(event, &self.inner.styles);
205 state.write_buf(&buf);
206 Ok(())
207 } else {
208 let mut writer = BufWriter::new(std::io::stderr());
210 self.inner
211 .write_event_impl(event, &mut writer)
212 .map_err(WriteEventError::Io)?;
213 writer.flush().map_err(WriteEventError::Io)
214 }
215 }
216 ReporterOutputImpl::Writer(writer) => {
217 self.inner
218 .write_event_impl(event, *writer)
219 .map_err(WriteEventError::Io)?;
220 writer.write_str_flush().map_err(WriteEventError::Io)
221 }
222 }
223 }
224
225 pub(crate) fn finish(&mut self) {
226 self.output.finish_and_clear_bar();
227 }
228
229 pub(crate) fn set_run_id_unique_prefix(&mut self, prefix: ShortestRunIdPrefix) {
234 self.inner.run_id_unique_prefix = Some(prefix);
235 }
236
237 pub(crate) fn write_replay_header(
242 &mut self,
243 header: &ReplayHeader,
244 ) -> Result<(), WriteEventError> {
245 self.write_impl(|writer, styles, _theme_chars| {
246 write!(writer, "{:>12} ", "Replaying".style(styles.pass))?;
248 let run_id_display = if let Some(prefix_info) = &header.unique_prefix {
249 format!(
251 "{}{}",
252 prefix_info.prefix.style(styles.run_id_prefix),
253 prefix_info.rest.style(styles.run_id_rest),
254 )
255 } else {
256 header.run_id.to_string().style(styles.count).to_string()
258 };
259 writeln!(writer, "recorded run {}", run_id_display)?;
260
261 let status_str = header.status.short_status_str();
263 write!(writer, "{:>12} ", "Started".style(styles.pass))?;
264 writeln!(
265 writer,
266 "{} status: {}",
267 header.started_at.format("%Y-%m-%d %H:%M:%S"),
268 status_str.style(styles.count)
269 )?;
270
271 Ok(())
272 })
273 }
274
275 pub(crate) fn output_load_decider(&self) -> OutputLoadDecider {
281 OutputLoadDecider {
282 status_level: self.inner.status_levels.status_level,
283 overrides: self.inner.unit_output.overrides(),
284 }
285 }
286
287 fn write_impl<F>(&mut self, f: F) -> Result<(), WriteEventError>
289 where
290 F: FnOnce(&mut dyn WriteStr, &Styles, &ThemeCharacters) -> io::Result<()>,
291 {
292 match &mut self.output {
293 ReporterOutputImpl::Terminal { progress_bar, .. } => {
294 if let Some(state) = progress_bar {
295 let mut buf = String::new();
297 f(&mut buf, &self.inner.styles, &self.inner.theme_characters)
298 .map_err(WriteEventError::Io)?;
299 state.write_buf(&buf);
300 Ok(())
301 } else {
302 let mut writer = BufWriter::new(std::io::stderr());
304 f(
305 &mut writer,
306 &self.inner.styles,
307 &self.inner.theme_characters,
308 )
309 .map_err(WriteEventError::Io)?;
310 writer.flush().map_err(WriteEventError::Io)
311 }
312 }
313 ReporterOutputImpl::Writer(writer) => {
314 f(*writer, &self.inner.styles, &self.inner.theme_characters)
315 .map_err(WriteEventError::Io)?;
316 writer.write_str_flush().map_err(WriteEventError::Io)
317 }
318 }
319 }
320}
321
322#[derive(Debug)]
334pub struct OutputLoadDecider {
335 pub(super) status_level: StatusLevel,
336 pub(super) overrides: OutputDisplayOverrides,
337}
338
339impl OutputLoadDecider {
340 pub fn should_load_output(&self, kind: &OutputEventKind<RecordingSpec>) -> LoadOutput {
342 match kind {
343 OutputEventKind::SetupScriptFinished { run_status, .. } => {
344 Self::should_load_for_setup_script(&run_status.result)
345 }
346 OutputEventKind::TestAttemptFailedWillRetry { failure_output, .. } => {
347 let display = self.overrides.failure_output(*failure_output);
348 Self::should_load_for_retry(display, self.status_level)
349 }
350 OutputEventKind::TestFinished {
351 success_output,
352 failure_output,
353 run_statuses,
354 ..
355 } => self.should_load_for_test_finished(*success_output, *failure_output, run_statuses),
356 }
357 }
358
359 pub(super) fn should_load_for_test_finished(
360 &self,
361 success_output: TestOutputDisplay,
362 failure_output: TestOutputDisplay,
363 run_statuses: &ExecutionStatuses<RecordingSpec>,
364 ) -> LoadOutput {
365 let describe = run_statuses.describe();
366
367 let display =
368 self.overrides
369 .resolve_for_describe(success_output, failure_output, &describe);
370
371 Self::should_load_for_display(display)
372 }
373
374 pub(super) fn should_load_for_setup_script(result: &ExecutionResultDescription) -> LoadOutput {
381 if result.is_success() {
383 LoadOutput::Skip
384 } else {
385 LoadOutput::Load
386 }
387 }
388
389 pub(super) fn should_load_for_retry(
396 display: TestOutputDisplay,
397 status_level: StatusLevel,
398 ) -> LoadOutput {
399 if display.is_immediate() && status_level >= StatusLevel::Retry {
400 LoadOutput::Load
401 } else {
402 LoadOutput::Skip
403 }
404 }
405
406 pub(super) fn should_load_for_display(display: TestOutputDisplay) -> LoadOutput {
410 let is_immediate = display.is_immediate();
411 let is_final = display.is_final();
412
413 if is_immediate || is_final {
417 LoadOutput::Load
418 } else {
419 LoadOutput::Skip
420 }
421 }
422}
423
424enum ReporterOutputImpl<'a> {
425 Terminal {
426 progress_bar: Option<Box<ProgressBarState>>,
429 term_progress: Option<TerminalProgress>,
431 },
432 Writer(&'a mut (dyn WriteStr + Send)),
433}
434
435impl ReporterOutputImpl<'_> {
436 fn tick(&mut self, styles: &Styles) {
437 match self {
438 ReporterOutputImpl::Terminal {
439 progress_bar,
440 term_progress,
441 } => {
442 if let Some(state) = progress_bar {
443 state.tick(styles);
444 }
445 if let Some(term_progress) = term_progress {
446 eprint!("{}", term_progress.last_value())
453 }
454 }
455 ReporterOutputImpl::Writer(_) => {
456 }
458 }
459 }
460
461 fn finish_and_clear_bar(&self) {
462 match self {
463 ReporterOutputImpl::Terminal {
464 progress_bar,
465 term_progress,
466 } => {
467 if let Some(state) = progress_bar {
468 state.finish_and_clear();
469 }
470 if let Some(term_progress) = term_progress {
471 eprint!("{}", term_progress.last_value())
473 }
474 }
475 ReporterOutputImpl::Writer(_) => {
476 }
478 }
479 }
480
481 #[cfg(test)]
482 fn writer_mut(&mut self) -> Option<&mut (dyn WriteStr + Send)> {
483 match self {
484 Self::Writer(writer) => Some(*writer),
485 _ => None,
486 }
487 }
488}
489
490#[derive(Debug)]
491enum FinalOutput {
492 Skipped(#[expect(dead_code)] MismatchReason),
493 Executed {
494 run_statuses: ExecutionStatuses<LiveSpec>,
495 display_output: bool,
496 },
497}
498
499impl FinalOutput {
500 fn final_status_level(&self) -> FinalStatusLevel {
501 match self {
502 Self::Skipped(_) => FinalStatusLevel::Skip,
503 Self::Executed { run_statuses, .. } => run_statuses.describe().final_status_level(),
504 }
505 }
506}
507
508struct FinalOutputEntry<'a> {
509 stress_index: Option<StressIndex>,
510 counter: TestInstanceCounter,
511 instance: TestInstanceId<'a>,
512 output: FinalOutput,
513}
514
515fn sort_final_outputs(entries: &mut [FinalOutputEntry<'_>], include_counter: bool) {
531 entries.sort_unstable_by(|a, b| {
532 Reverse(a.output.final_status_level())
533 .cmp(&Reverse(b.output.final_status_level()))
534 .then_with(|| a.stress_index.cmp(&b.stress_index))
535 .then_with(|| {
536 if include_counter {
537 a.counter.cmp(&b.counter)
538 } else {
539 Ordering::Equal
540 }
541 })
542 .then_with(|| a.instance.cmp(&b.instance))
543 });
544}
545
546struct DisplayReporterImpl<'a> {
547 mode: NextestRunMode,
548 default_filter: CompiledDefaultFilter,
549 status_levels: StatusLevels,
550 no_capture: bool,
551 verbose: bool,
552 no_output_indent: bool,
553 counter_width: Option<usize>,
556 styles: Box<Styles>,
557 theme_characters: ThemeCharacters,
558 cancel_status: Option<CancelReason>,
559 unit_output: UnitOutputReporter,
560 final_outputs: DebugIgnore<Vec<FinalOutputEntry<'a>>>,
561 run_id_unique_prefix: Option<ShortestRunIdPrefix>,
564 redactor: Redactor,
565}
566
567impl<'a> DisplayReporterImpl<'a> {
568 fn write_event_impl(
569 &mut self,
570 event: &TestEvent<'a>,
571 writer: &mut dyn WriteStr,
572 ) -> io::Result<()> {
573 match &event.kind {
574 TestEventKind::RunStarted {
575 test_list,
576 run_id,
577 profile_name,
578 cli_args: _,
579 stress_condition: _,
580 } => {
581 writeln!(writer, "{}", self.theme_characters.hbar(12))?;
582 write!(writer, "{:>12} ", "Nextest run".style(self.styles.pass))?;
583
584 let run_id_display = if let Some(prefix_info) = &self.run_id_unique_prefix {
587 format!(
588 "{}{}",
589 prefix_info.prefix.style(self.styles.run_id_prefix),
590 prefix_info.rest.style(self.styles.run_id_rest),
591 )
592 } else {
593 run_id.style(self.styles.count).to_string()
594 };
595
596 writeln!(
597 writer,
598 "ID {} with nextest profile: {}",
599 run_id_display,
600 profile_name.style(self.styles.count),
601 )?;
602
603 write!(writer, "{:>12} ", "Starting".style(self.styles.pass))?;
604
605 let count_style = self.styles.count;
606
607 let tests_str = plural::tests_str(self.mode, test_list.run_count());
608 let binaries_str = plural::binaries_str(test_list.listed_binary_count());
609
610 write!(
611 writer,
612 "{} {tests_str} across {} {binaries_str}",
613 test_list.run_count().style(count_style),
614 test_list.listed_binary_count().style(count_style),
615 )?;
616
617 write_skip_counts(
618 self.mode,
619 test_list.skip_counts(),
620 &self.default_filter,
621 &self.styles,
622 writer,
623 )?;
624
625 writeln!(writer)?;
626 }
627 TestEventKind::StressSubRunStarted { progress } => {
628 write!(
629 writer,
630 "{}\n{:>12} ",
631 self.theme_characters.hbar(12),
632 "Stress test".style(self.styles.pass)
633 )?;
634
635 match progress {
636 StressProgress::Count {
637 total: StressCount::Count { count },
638 elapsed,
639 completed,
640 } => {
641 write!(
642 writer,
643 "iteration {}/{} ({} elapsed so far",
644 (completed + 1).style(self.styles.count),
645 count.style(self.styles.count),
646 self.redactor
647 .redact_hhmmss_duration(*elapsed, DurationRounding::Floor)
648 .style(self.styles.count),
649 )?;
650 }
651 StressProgress::Count {
652 total: StressCount::Infinite,
653 elapsed,
654 completed,
655 } => {
656 write!(
657 writer,
658 "iteration {} ({} elapsed so far",
659 (completed + 1).style(self.styles.count),
660 self.redactor
661 .redact_hhmmss_duration(*elapsed, DurationRounding::Floor)
662 .style(self.styles.count),
663 )?;
664 }
665 StressProgress::Time {
666 total,
667 elapsed,
668 completed,
669 } => {
670 write!(
671 writer,
672 "iteration {} ({}/{} elapsed so far",
673 (completed + 1).style(self.styles.count),
674 self.redactor
675 .redact_hhmmss_duration(*elapsed, DurationRounding::Floor)
676 .style(self.styles.count),
677 self.redactor
678 .redact_hhmmss_duration(*total, DurationRounding::Floor)
679 .style(self.styles.count),
680 )?;
681 }
682 }
683
684 if let Some(remaining) = progress.remaining() {
685 match remaining {
686 StressRemaining::Count(n) => {
687 write!(
688 writer,
689 ", {} {} remaining",
690 n.style(self.styles.count),
691 plural::iterations_str(n.get()),
692 )?;
693 }
694 StressRemaining::Infinite => {
695 }
697 StressRemaining::Time(t) => {
698 write!(
707 writer,
708 ", {} remaining",
709 self.redactor
710 .redact_hhmmss_duration(t, DurationRounding::Ceiling)
711 .style(self.styles.count)
712 )?;
713 }
714 }
715 }
716
717 writeln!(writer, ")")?;
718 }
719 TestEventKind::SetupScriptStarted {
720 stress_index,
721 index,
722 total,
723 script_id,
724 program,
725 args,
726 ..
727 } => {
728 writeln!(
729 writer,
730 "{:>12} [{:>9}] {}",
731 "SETUP".style(self.styles.pass),
732 format!("{}/{}", index + 1, total),
734 self.display_script_instance(*stress_index, script_id.clone(), program, args)
735 )?;
736 }
737 TestEventKind::SetupScriptSlow {
738 stress_index,
739 script_id,
740 program,
741 args,
742 elapsed,
743 will_terminate,
744 } => {
745 if !*will_terminate && self.status_levels.status_level >= StatusLevel::Slow {
746 write!(writer, "{:>12} ", "SETUP SLOW".style(self.styles.skip))?;
747 writeln!(
748 writer,
749 "{}{}",
750 DisplaySlowDuration(*elapsed),
751 self.display_script_instance(
752 *stress_index,
753 script_id.clone(),
754 program,
755 args
756 )
757 )?;
758 } else if *will_terminate && self.status_levels.status_level >= StatusLevel::Fail {
759 write!(writer, "{:>12} ", "TERMINATING".style(self.styles.fail))?;
760 writeln!(
761 writer,
762 "{}{}",
763 DisplaySlowDuration(*elapsed),
764 self.display_script_instance(
765 *stress_index,
766 script_id.clone(),
767 program,
768 args
769 )
770 )?;
771 }
772 }
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.attempt > 1 {
843 write!(
844 writer,
845 "{:>12} ",
846 format!("TRY {} SLOW", retry_data.attempt).style(self.styles.skip)
847 )?;
848 } else {
849 write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
850 }
851 writeln!(
852 writer,
853 "{}{}",
854 DisplaySlowDuration(*elapsed),
855 self.display_test_instance(
856 *stress_index,
857 TestInstanceCounter::Padded,
858 *test_instance
859 )
860 )?;
861 } else if *will_terminate {
862 let (required_status_level, style) = if retry_data.is_last_attempt() {
863 (StatusLevel::Fail, self.styles.fail)
864 } else {
865 (StatusLevel::Retry, self.styles.retry)
866 };
867 if self.status_levels.status_level >= required_status_level {
868 if retry_data.total_attempts > 1
871 && self.status_levels.status_level > required_status_level
872 {
873 write!(
874 writer,
875 "{:>12} ",
876 format!("TRY {} TRMNTG", retry_data.attempt).style(style)
877 )?;
878 } else {
879 write!(writer, "{:>12} ", "TERMINATING".style(style))?;
880 };
881 writeln!(
882 writer,
883 "{}{}",
884 DisplaySlowDuration(*elapsed),
885 self.display_test_instance(
886 *stress_index,
887 TestInstanceCounter::Padded,
888 *test_instance
889 )
890 )?;
891 }
892 }
893 }
897
898 TestEventKind::TestAttemptFailedWillRetry {
899 stress_index,
900 test_instance,
901 run_status,
902 delay_before_next_attempt,
903 failure_output,
904 running: _,
905 } => {
906 if self.status_levels.status_level >= StatusLevel::Retry {
907 let try_status_string = format!(
908 "TRY {} {}",
909 run_status.retry_data.attempt,
910 short_status_str(&run_status.result),
911 );
912
913 write!(
915 writer,
916 "{:>12} {}",
917 try_status_string.style(self.styles.retry),
918 DisplayBracketedDuration(run_status.time_taken),
919 )?;
920
921 writeln!(
923 writer,
924 "{}",
925 self.display_test_instance(
926 *stress_index,
927 TestInstanceCounter::Padded,
928 *test_instance
929 )
930 )?;
931
932 assert!(
934 !run_status.result.is_success(),
935 "only failing tests are retried"
936 );
937 if self
938 .unit_output
939 .overrides()
940 .failure_output(*failure_output)
941 .is_immediate()
942 {
943 self.write_test_execute_status(run_status, true, writer)?;
944 }
945
946 if !delay_before_next_attempt.is_zero() {
950 let delay_string = format!(
952 "DELAY {}/{}",
953 run_status.retry_data.attempt + 1,
954 run_status.retry_data.total_attempts,
955 );
956 write!(
957 writer,
958 "{:>12} {}",
959 delay_string.style(self.styles.retry),
960 DisplayDurationBy(*delay_before_next_attempt)
961 )?;
962
963 writeln!(
965 writer,
966 "{}",
967 self.display_test_instance(
968 *stress_index,
969 TestInstanceCounter::Padded,
970 *test_instance
971 )
972 )?;
973 }
974 }
975 }
976 TestEventKind::TestRetryStarted {
977 stress_index,
978 test_instance,
979 slot_assignment: _,
980 retry_data: RetryData { attempt, .. },
981 running: _,
982 command_line,
983 } => {
984 if self.no_capture || self.verbose {
986 let retry_string = format!("TRY {attempt} START");
987 writeln!(
988 writer,
989 "{:>12} [ ] {}",
990 retry_string.style(self.styles.retry),
991 self.display_test_instance(
992 *stress_index,
993 TestInstanceCounter::Padded,
994 *test_instance
995 )
996 )?;
997 }
998
999 if self.verbose {
1000 self.write_command_line(command_line, writer)?;
1001 }
1002 }
1003 TestEventKind::TestFinished {
1004 stress_index,
1005 test_instance,
1006 success_output,
1007 failure_output,
1008 run_statuses,
1009 current_stats,
1010 ..
1011 } => {
1012 let describe = run_statuses.describe();
1013 let last_status = run_statuses.last_status();
1014 let test_output_display = self.unit_output.overrides().resolve_for_describe(
1015 *success_output,
1016 *failure_output,
1017 &describe,
1018 );
1019
1020 let output_on_test_finished = self.status_levels.compute_output_on_test_finished(
1021 test_output_display,
1022 self.cancel_status,
1023 describe.status_level(),
1024 describe.final_status_level(),
1025 &last_status.result,
1026 );
1027
1028 let counter = TestInstanceCounter::Counter {
1029 current: current_stats.finished_count,
1030 total: current_stats.initial_run_count,
1031 };
1032
1033 if output_on_test_finished.write_status_line {
1034 self.write_status_line(
1035 *stress_index,
1036 counter,
1037 *test_instance,
1038 describe,
1039 writer,
1040 )?;
1041 }
1042 if output_on_test_finished.show_immediate {
1043 self.write_test_execute_status(last_status, false, writer)?;
1044 }
1045 if let OutputStoreFinal::Yes { display_output } =
1046 output_on_test_finished.store_final
1047 {
1048 self.final_outputs.push(FinalOutputEntry {
1049 stress_index: *stress_index,
1050 counter,
1051 instance: *test_instance,
1052 output: FinalOutput::Executed {
1053 run_statuses: run_statuses.clone(),
1054 display_output,
1055 },
1056 });
1057 }
1058 }
1059 TestEventKind::TestSkipped {
1060 stress_index,
1061 test_instance,
1062 reason,
1063 } => {
1064 if self.status_levels.status_level >= StatusLevel::Skip {
1065 self.write_skip_line(*stress_index, *test_instance, writer)?;
1066 }
1067 if self.status_levels.final_status_level >= FinalStatusLevel::Skip {
1068 self.final_outputs.push(FinalOutputEntry {
1069 stress_index: *stress_index,
1070 counter: TestInstanceCounter::Padded,
1071 instance: *test_instance,
1072 output: FinalOutput::Skipped(*reason),
1073 });
1074 }
1075 }
1076 TestEventKind::RunBeginCancel {
1077 setup_scripts_running,
1078 current_stats,
1079 running,
1080 } => {
1081 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1082
1083 write!(writer, "{:>12} ", "Cancelling".style(self.styles.fail))?;
1084 if let Some(reason) = current_stats.cancel_reason {
1085 write!(
1086 writer,
1087 "due to {}: ",
1088 reason.to_static_str().style(self.styles.fail)
1089 )?;
1090 }
1091
1092 let immediately_terminating_text =
1093 if current_stats.cancel_reason == Some(CancelReason::TestFailureImmediate) {
1094 format!("immediately {} ", "terminating".style(self.styles.fail))
1095 } else {
1096 String::new()
1097 };
1098
1099 if *setup_scripts_running > 0 {
1101 let s = plural::setup_scripts_str(*setup_scripts_running);
1102 write!(
1103 writer,
1104 "{immediately_terminating_text}{} {s} still running",
1105 setup_scripts_running.style(self.styles.count),
1106 )?;
1107 } else if *running > 0 {
1108 let tests_str = plural::tests_str(self.mode, *running);
1109 write!(
1110 writer,
1111 "{immediately_terminating_text}{} {tests_str} still running",
1112 running.style(self.styles.count),
1113 )?;
1114 }
1115 writeln!(writer)?;
1116 }
1117 TestEventKind::RunBeginKill {
1118 setup_scripts_running,
1119 current_stats,
1120 running,
1121 } => {
1122 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1123
1124 write!(writer, "{:>12} ", "Killing".style(self.styles.fail),)?;
1125 if let Some(reason) = current_stats.cancel_reason {
1126 write!(
1127 writer,
1128 "due to {}: ",
1129 reason.to_static_str().style(self.styles.fail)
1130 )?;
1131 }
1132
1133 if *setup_scripts_running > 0 {
1135 let s = plural::setup_scripts_str(*setup_scripts_running);
1136 write!(
1137 writer,
1138 ": {} {s} still running",
1139 setup_scripts_running.style(self.styles.count),
1140 )?;
1141 } else if *running > 0 {
1142 let tests_str = plural::tests_str(self.mode, *running);
1143 write!(
1144 writer,
1145 ": {} {tests_str} still running",
1146 running.style(self.styles.count),
1147 )?;
1148 }
1149 writeln!(writer)?;
1150 }
1151 TestEventKind::RunPaused {
1152 setup_scripts_running,
1153 running,
1154 } => {
1155 write!(
1156 writer,
1157 "{:>12} due to {}",
1158 "Pausing".style(self.styles.pass),
1159 "signal".style(self.styles.count)
1160 )?;
1161
1162 if *setup_scripts_running > 0 {
1164 let s = plural::setup_scripts_str(*setup_scripts_running);
1165 write!(
1166 writer,
1167 ": {} {s} running",
1168 setup_scripts_running.style(self.styles.count),
1169 )?;
1170 } else if *running > 0 {
1171 let tests_str = plural::tests_str(self.mode, *running);
1172 write!(
1173 writer,
1174 ": {} {tests_str} running",
1175 running.style(self.styles.count),
1176 )?;
1177 }
1178 writeln!(writer)?;
1179 }
1180 TestEventKind::RunContinued {
1181 setup_scripts_running,
1182 running,
1183 } => {
1184 write!(
1185 writer,
1186 "{:>12} due to {}",
1187 "Continuing".style(self.styles.pass),
1188 "signal".style(self.styles.count)
1189 )?;
1190
1191 if *setup_scripts_running > 0 {
1193 let s = plural::setup_scripts_str(*setup_scripts_running);
1194 write!(
1195 writer,
1196 ": {} {s} running",
1197 setup_scripts_running.style(self.styles.count),
1198 )?;
1199 } else if *running > 0 {
1200 let tests_str = plural::tests_str(self.mode, *running);
1201 write!(
1202 writer,
1203 ": {} {tests_str} running",
1204 running.style(self.styles.count),
1205 )?;
1206 }
1207 writeln!(writer)?;
1208 }
1209 TestEventKind::InfoStarted { total, run_stats } => {
1210 let info_style = if run_stats.has_failures() {
1211 self.styles.fail
1212 } else {
1213 self.styles.pass
1214 };
1215
1216 let hbar = self.theme_characters.hbar(12);
1217
1218 write!(writer, "{hbar}\n{}: ", "info".style(info_style))?;
1219
1220 writeln!(
1222 writer,
1223 "{} in {:.3?}s",
1224 progress_bar_msg(run_stats, *total, &self.styles),
1230 event.elapsed.as_secs_f64(),
1231 )?;
1232 }
1233 TestEventKind::InfoResponse {
1234 index,
1235 total,
1236 response,
1237 } => {
1238 self.write_info_response(*index, *total, response, writer)?;
1239 }
1240 TestEventKind::InfoFinished { missing } => {
1241 let hbar = self.theme_characters.hbar(12);
1242
1243 if *missing > 0 {
1244 writeln!(
1247 writer,
1248 "{}: missing {} responses",
1249 "info".style(self.styles.skip),
1250 missing.style(self.styles.count)
1251 )?;
1252 }
1253
1254 writeln!(writer, "{hbar}")?;
1255 }
1256 TestEventKind::InputEnter {
1257 current_stats,
1258 running,
1259 } => {
1260 writeln!(
1263 writer,
1264 "{}",
1265 progress_str(event.elapsed, current_stats, *running, &self.styles)
1266 )?;
1267 }
1268 TestEventKind::StressSubRunFinished {
1269 progress,
1270 sub_elapsed,
1271 sub_stats,
1272 } => {
1273 let stats_summary = sub_stats.summarize_final();
1274 let summary_style = match stats_summary {
1275 FinalRunStats::Success => self.styles.pass,
1276 FinalRunStats::NoTestsRun => self.styles.skip,
1277 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1278 self.styles.fail
1279 }
1280 };
1281
1282 write!(
1283 writer,
1284 "{:>12} {}",
1285 "Stress test".style(summary_style),
1286 DisplayBracketedDuration(*sub_elapsed),
1287 )?;
1288 match progress {
1289 StressProgress::Count {
1290 total: StressCount::Count { count },
1291 elapsed: _,
1292 completed,
1293 } => {
1294 write!(
1295 writer,
1296 "iteration {}/{}: ",
1297 completed.style(self.styles.count),
1301 count.style(self.styles.count),
1302 )?;
1303 }
1304 StressProgress::Count {
1305 total: StressCount::Infinite,
1306 elapsed: _,
1307 completed,
1308 } => {
1309 write!(
1310 writer,
1311 "iteration {}: ",
1312 completed.style(self.styles.count),
1316 )?;
1317 }
1318 StressProgress::Time {
1319 total: _,
1320 elapsed: _,
1321 completed,
1322 } => {
1323 write!(
1324 writer,
1325 "iteration {}: ",
1326 completed.style(self.styles.count),
1330 )?;
1331 }
1332 }
1333
1334 write!(
1335 writer,
1336 "{}",
1337 sub_stats.finished_count.style(self.styles.count)
1338 )?;
1339 if sub_stats.finished_count != sub_stats.initial_run_count {
1340 write!(
1341 writer,
1342 "/{}",
1343 sub_stats.initial_run_count.style(self.styles.count)
1344 )?;
1345 }
1346
1347 let tests_str = plural::tests_plural_if(
1349 self.mode,
1350 sub_stats.initial_run_count != 1 || sub_stats.finished_count != 1,
1351 );
1352
1353 let mut summary_str = String::new();
1354 write_summary_str(sub_stats, &self.styles, &mut summary_str);
1355 writeln!(writer, " {tests_str} run: {summary_str}")?;
1356 }
1357 TestEventKind::RunFinished {
1358 start_time: _start_time,
1359 elapsed,
1360 run_stats,
1361 outstanding_not_seen: tests_not_seen,
1362 ..
1363 } => {
1364 match run_stats {
1365 RunFinishedStats::Single(run_stats) => {
1366 let stats_summary = run_stats.summarize_final();
1367 let summary_style = match stats_summary {
1368 FinalRunStats::Success => self.styles.pass,
1369 FinalRunStats::NoTestsRun => self.styles.skip,
1370 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1371 self.styles.fail
1372 }
1373 };
1374 write!(
1375 writer,
1376 "{}\n{:>12} ",
1377 self.theme_characters.hbar(12),
1378 "Summary".style(summary_style)
1379 )?;
1380
1381 write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1386
1387 write!(
1388 writer,
1389 "{}",
1390 run_stats.finished_count.style(self.styles.count)
1391 )?;
1392 if run_stats.finished_count != run_stats.initial_run_count {
1393 write!(
1394 writer,
1395 "/{}",
1396 run_stats.initial_run_count.style(self.styles.count)
1397 )?;
1398 }
1399
1400 let tests_str = plural::tests_plural_if(
1402 self.mode,
1403 run_stats.initial_run_count != 1 || run_stats.finished_count != 1,
1404 );
1405
1406 let mut summary_str = String::new();
1407 write_summary_str(run_stats, &self.styles, &mut summary_str);
1408 writeln!(writer, " {tests_str} run: {summary_str}")?;
1409 }
1410 RunFinishedStats::Stress(stats) => {
1411 let stats_summary = stats.summarize_final();
1412 let summary_style = match stats_summary {
1413 StressFinalRunStats::Success => self.styles.pass,
1414 StressFinalRunStats::NoTestsRun => self.styles.skip,
1415 StressFinalRunStats::Cancelled | StressFinalRunStats::Failed => {
1416 self.styles.fail
1417 }
1418 };
1419
1420 write!(
1421 writer,
1422 "{}\n{:>12} ",
1423 self.theme_characters.hbar(12),
1424 "Summary".style(summary_style),
1425 )?;
1426
1427 write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1432
1433 write!(
1434 writer,
1435 "{}",
1436 stats.completed.current.style(self.styles.count),
1437 )?;
1438 let iterations_str = if let Some(total) = stats.completed.total {
1439 write!(writer, "/{}", total.style(self.styles.count))?;
1440 plural::iterations_str(total.get())
1441 } else {
1442 plural::iterations_str(stats.completed.current)
1443 };
1444 write!(
1445 writer,
1446 " stress run {iterations_str}: {} {}",
1447 stats.success_count.style(self.styles.count),
1448 "passed".style(self.styles.pass),
1449 )?;
1450 if stats.failed_count > 0 {
1451 write!(
1452 writer,
1453 ", {} {}",
1454 stats.failed_count.style(self.styles.count),
1455 "failed".style(self.styles.fail),
1456 )?;
1457 }
1458
1459 match stats.last_final_stats {
1460 FinalRunStats::Cancelled { reason, kind: _ } => {
1461 if let Some(reason) = reason {
1462 write!(
1463 writer,
1464 "; cancelled due to {}",
1465 reason.to_static_str().style(self.styles.fail),
1466 )?;
1467 }
1468 }
1469 FinalRunStats::Failed { .. }
1470 | FinalRunStats::Success
1471 | FinalRunStats::NoTestsRun => {}
1472 }
1473
1474 writeln!(writer)?;
1475 }
1476 }
1477
1478 if self.cancel_status < Some(CancelReason::Interrupt) {
1481 sort_final_outputs(&mut self.final_outputs, self.counter_width.is_some());
1483
1484 for entry in &*self.final_outputs {
1485 match &entry.output {
1486 FinalOutput::Skipped(_) => {
1487 self.write_skip_line(entry.stress_index, entry.instance, writer)?;
1488 }
1489 FinalOutput::Executed {
1490 run_statuses,
1491 display_output,
1492 } => {
1493 let last_status = run_statuses.last_status();
1494
1495 self.write_final_status_line(
1496 entry.stress_index,
1497 entry.counter,
1498 entry.instance,
1499 run_statuses.describe(),
1500 writer,
1501 )?;
1502 if *display_output {
1503 self.write_test_execute_status(last_status, false, writer)?;
1504 }
1505 }
1506 }
1507 }
1508 }
1509
1510 if let Some(not_seen) = tests_not_seen
1511 && not_seen.total_not_seen > 0
1512 {
1513 writeln!(
1514 writer,
1515 "{:>12} {} outstanding {} not seen during this rerun:",
1516 "Note".style(self.styles.skip),
1517 not_seen.total_not_seen.style(self.styles.count),
1518 plural::tests_str(self.mode, not_seen.total_not_seen),
1519 )?;
1520
1521 for t in ¬_seen.not_seen {
1522 let display = DisplayTestInstance::new(
1523 None,
1524 None,
1525 t.as_ref(),
1526 &self.styles.list_styles,
1527 );
1528 writeln!(writer, " {}", display)?;
1529 }
1530
1531 let remaining = not_seen
1532 .total_not_seen
1533 .saturating_sub(not_seen.not_seen.len());
1534 if remaining > 0 {
1535 writeln!(
1536 writer,
1537 " ... and {} more {}",
1538 remaining.style(self.styles.count),
1539 plural::tests_str(self.mode, remaining),
1540 )?;
1541 }
1542 }
1543
1544 write_final_warnings(self.mode, run_stats.final_stats(), &self.styles, writer)?;
1546 }
1547 }
1548
1549 Ok(())
1550 }
1551
1552 fn write_skip_line(
1553 &self,
1554 stress_index: Option<StressIndex>,
1555 test_instance: TestInstanceId<'a>,
1556 writer: &mut dyn WriteStr,
1557 ) -> io::Result<()> {
1558 write!(writer, "{:>12} ", "SKIP".style(self.styles.skip))?;
1559 writeln!(
1561 writer,
1562 "[ ] {}",
1563 self.display_test_instance(stress_index, TestInstanceCounter::Padded, test_instance)
1564 )?;
1565
1566 Ok(())
1567 }
1568
1569 fn write_setup_script_status_line(
1570 &self,
1571 stress_index: Option<StressIndex>,
1572 script_id: &ScriptId,
1573 command: &str,
1574 args: &[String],
1575 status: &SetupScriptExecuteStatus<LiveSpec>,
1576 writer: &mut dyn WriteStr,
1577 ) -> io::Result<()> {
1578 match status.result {
1579 ExecutionResultDescription::Pass => {
1580 write!(writer, "{:>12} ", "SETUP PASS".style(self.styles.pass))?;
1581 }
1582 ExecutionResultDescription::Leak { result } => match result {
1583 LeakTimeoutResult::Pass => {
1584 write!(writer, "{:>12} ", "SETUP LEAK".style(self.styles.skip))?;
1585 }
1586 LeakTimeoutResult::Fail => {
1587 write!(writer, "{:>12} ", "SETUP LKFAIL".style(self.styles.fail))?;
1588 }
1589 },
1590 ref other => {
1591 let status_str = short_status_str(other);
1592 write!(
1593 writer,
1594 "{:>12} ",
1595 format!("SETUP {status_str}").style(self.styles.fail),
1596 )?;
1597 }
1598 }
1599
1600 writeln!(
1601 writer,
1602 "{}{}",
1603 DisplayBracketedDuration(status.time_taken),
1604 self.display_script_instance(stress_index, script_id.clone(), command, args)
1605 )?;
1606
1607 Ok(())
1608 }
1609
1610 fn write_status_line(
1611 &self,
1612 stress_index: Option<StressIndex>,
1613 counter: TestInstanceCounter,
1614 test_instance: TestInstanceId<'a>,
1615 describe: ExecutionDescription<'_, LiveSpec>,
1616 writer: &mut dyn WriteStr,
1617 ) -> io::Result<()> {
1618 self.write_status_line_impl(
1619 stress_index,
1620 counter,
1621 test_instance,
1622 describe,
1623 StatusLineKind::Intermediate,
1624 writer,
1625 )
1626 }
1627
1628 fn write_final_status_line(
1629 &self,
1630 stress_index: Option<StressIndex>,
1631 counter: TestInstanceCounter,
1632 test_instance: TestInstanceId<'a>,
1633 describe: ExecutionDescription<'_, LiveSpec>,
1634 writer: &mut dyn WriteStr,
1635 ) -> io::Result<()> {
1636 self.write_status_line_impl(
1637 stress_index,
1638 counter,
1639 test_instance,
1640 describe,
1641 StatusLineKind::Final,
1642 writer,
1643 )
1644 }
1645
1646 fn write_status_line_impl(
1647 &self,
1648 stress_index: Option<StressIndex>,
1649 counter: TestInstanceCounter,
1650 test_instance: TestInstanceId<'a>,
1651 describe: ExecutionDescription<'_, LiveSpec>,
1652 kind: StatusLineKind,
1653 writer: &mut dyn WriteStr,
1654 ) -> io::Result<()> {
1655 let last_status = describe.last_status();
1656
1657 self.write_status_line_prefix(describe, kind, writer)?;
1659
1660 writeln!(
1662 writer,
1663 "{}{}",
1664 DisplayBracketedDuration(last_status.time_taken),
1665 self.display_test_instance(stress_index, counter, test_instance),
1666 )?;
1667
1668 if let ExecutionResultDescription::Fail {
1670 failure: FailureDescription::Abort { ref abort },
1671 leaked: _,
1672 } = last_status.result
1673 {
1674 write_windows_abort_line(abort, &self.styles, writer)?;
1675 }
1676
1677 if kind == StatusLineKind::Intermediate
1681 && let ExecutionDescription::Flaky {
1682 result: FlakyResult::Fail,
1683 ..
1684 } = describe
1685 {
1686 writeln!(
1687 writer,
1688 "{:>12} test configured to {} if flaky",
1689 "-",
1690 "fail".style(self.styles.fail),
1691 )?;
1692 }
1693
1694 Ok(())
1695 }
1696
1697 fn write_status_line_prefix(
1698 &self,
1699 describe: ExecutionDescription<'_, LiveSpec>,
1700 kind: StatusLineKind,
1701 writer: &mut dyn WriteStr,
1702 ) -> io::Result<()> {
1703 let last_status = describe.last_status();
1704 match describe {
1705 ExecutionDescription::Success { .. } => {
1706 match (kind, last_status.is_slow, &last_status.result) {
1710 (StatusLineKind::Final, true, ExecutionResultDescription::Pass) => {
1712 write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
1713 }
1714 (
1715 StatusLineKind::Final,
1716 true,
1717 ExecutionResultDescription::Leak {
1718 result: LeakTimeoutResult::Pass,
1719 },
1720 ) => {
1721 write!(writer, "{:>12} ", "SLOW + LEAK".style(self.styles.skip))?;
1722 }
1723 (
1724 StatusLineKind::Final,
1725 true,
1726 ExecutionResultDescription::Timeout {
1727 result: SlowTimeoutResult::Pass,
1728 },
1729 ) => {
1730 write!(writer, "{:>12} ", "SLOW+TMPASS".style(self.styles.skip))?;
1731 }
1732 (_, _, ExecutionResultDescription::Pass) => {
1734 write!(writer, "{:>12} ", "PASS".style(self.styles.pass))?;
1735 }
1736 (
1737 _,
1738 _,
1739 ExecutionResultDescription::Leak {
1740 result: LeakTimeoutResult::Pass,
1741 },
1742 ) => {
1743 write!(writer, "{:>12} ", "LEAK".style(self.styles.skip))?;
1744 }
1745 (
1746 _,
1747 _,
1748 ExecutionResultDescription::Timeout {
1749 result: SlowTimeoutResult::Pass,
1750 },
1751 ) => {
1752 write!(writer, "{:>12} ", "TIMEOUT-PASS".style(self.styles.skip))?;
1753 }
1754 (
1756 _,
1757 _,
1758 ExecutionResultDescription::Leak {
1759 result: LeakTimeoutResult::Fail,
1760 },
1761 )
1762 | (
1763 _,
1764 _,
1765 ExecutionResultDescription::Timeout {
1766 result: SlowTimeoutResult::Fail,
1767 },
1768 )
1769 | (_, _, ExecutionResultDescription::Fail { .. })
1770 | (_, _, ExecutionResultDescription::ExecFail) => {
1771 unreachable!(
1772 "success description cannot have failure result: {:?}",
1773 last_status.result
1774 )
1775 }
1776 }
1777 }
1778 ExecutionDescription::Flaky {
1779 result: FlakyResult::Pass,
1780 ..
1781 } => {
1782 let status = match kind {
1784 StatusLineKind::Intermediate => {
1785 format!("TRY {} PASS", last_status.retry_data.attempt)
1786 }
1787 StatusLineKind::Final => {
1788 format!(
1789 "FLAKY {}/{}",
1790 last_status.retry_data.attempt, last_status.retry_data.total_attempts
1791 )
1792 }
1793 };
1794 write!(writer, "{:>12} ", status.style(self.styles.skip))?;
1795 }
1796 ExecutionDescription::Flaky {
1797 result: FlakyResult::Fail,
1798 ..
1799 } => {
1800 let status = match kind {
1802 StatusLineKind::Intermediate => {
1803 format!("TRY {} PASS", last_status.retry_data.attempt)
1804 }
1805 StatusLineKind::Final => {
1806 format!(
1807 "FLKY-FL {}/{}",
1808 last_status.retry_data.attempt, last_status.retry_data.total_attempts
1809 )
1810 }
1811 };
1812 write!(writer, "{:>12} ", status.style(self.styles.fail))?;
1813 }
1814 ExecutionDescription::Failure { .. } => {
1815 if last_status.retry_data.attempt == 1 {
1816 write!(
1817 writer,
1818 "{:>12} ",
1819 status_str(&last_status.result).style(self.styles.fail)
1820 )?;
1821 } else {
1822 let status_str = short_status_str(&last_status.result);
1823 write!(
1824 writer,
1825 "{:>12} ",
1826 format!("TRY {} {}", last_status.retry_data.attempt, status_str)
1827 .style(self.styles.fail)
1828 )?;
1829 }
1830 }
1831 }
1832 Ok(())
1833 }
1834
1835 fn display_test_instance(
1836 &self,
1837 stress_index: Option<StressIndex>,
1838 counter: TestInstanceCounter,
1839 instance: TestInstanceId<'a>,
1840 ) -> DisplayTestInstance<'_> {
1841 let counter_index = match (counter, self.counter_width) {
1842 (TestInstanceCounter::Counter { current, total }, Some(_)) => {
1843 Some(DisplayCounterIndex::new_counter(current, total))
1844 }
1845 (TestInstanceCounter::Padded, Some(counter_width)) => Some(
1846 DisplayCounterIndex::new_padded(self.theme_characters.hbar_char(), counter_width),
1847 ),
1848 (TestInstanceCounter::None, _) | (_, None) => None,
1849 };
1850
1851 DisplayTestInstance::new(
1852 stress_index,
1853 counter_index,
1854 instance,
1855 &self.styles.list_styles,
1856 )
1857 }
1858
1859 fn write_command_line(
1860 &self,
1861 command_line: &[String],
1862 writer: &mut dyn WriteStr,
1863 ) -> io::Result<()> {
1864 writeln!(
1866 writer,
1867 "{:>20}: {}",
1868 "command".style(self.styles.count),
1869 shell_words::join(command_line),
1870 )
1871 }
1872
1873 fn display_script_instance(
1874 &self,
1875 stress_index: Option<StressIndex>,
1876 script_id: ScriptId,
1877 command: &str,
1878 args: &[String],
1879 ) -> DisplayScriptInstance {
1880 DisplayScriptInstance::new(
1881 stress_index,
1882 script_id,
1883 command,
1884 args,
1885 self.styles.script_id,
1886 self.styles.count,
1887 )
1888 }
1889
1890 fn write_info_response(
1891 &self,
1892 index: usize,
1893 total: usize,
1894 response: &InfoResponse<'_>,
1895 writer: &mut dyn WriteStr,
1896 ) -> io::Result<()> {
1897 if index > 0 {
1898 writeln!(writer, "{}", self.theme_characters.hbar(8))?;
1901 }
1902
1903 let count_width = decimal_char_width(index + 1) + decimal_char_width(total) + 3;
1909 let padding = 8usize.saturating_sub(count_width);
1910
1911 write!(
1912 writer,
1913 "\n* {}/{}: {:padding$}",
1914 (index + 1).style(self.styles.count),
1916 total.style(self.styles.count),
1917 "",
1918 )?;
1919
1920 let mut writer = indented(writer).with_str(" ").skip_initial();
1923
1924 match response {
1925 InfoResponse::SetupScript(SetupScriptInfoResponse {
1926 stress_index,
1927 script_id,
1928 program,
1929 args,
1930 state,
1931 output,
1932 }) => {
1933 writeln!(
1935 writer,
1936 "{}",
1937 self.display_script_instance(*stress_index, script_id.clone(), program, args)
1938 )?;
1939
1940 self.write_unit_state(
1942 UnitKind::Script,
1943 "",
1944 state,
1945 output.has_errors(),
1946 &mut writer,
1947 )?;
1948
1949 if state.has_valid_output() {
1951 self.unit_output.write_child_execution_output(
1952 &self.styles,
1953 &self.output_spec_for_info(UnitKind::Script),
1954 output,
1955 &mut writer,
1956 )?;
1957 }
1958 }
1959 InfoResponse::Test(TestInfoResponse {
1960 stress_index,
1961 test_instance,
1962 retry_data,
1963 state,
1964 output,
1965 }) => {
1966 writeln!(
1968 writer,
1969 "{}",
1970 self.display_test_instance(
1971 *stress_index,
1972 TestInstanceCounter::None,
1973 *test_instance
1974 )
1975 )?;
1976
1977 let show_attempt_str = (retry_data.attempt > 1 && retry_data.total_attempts > 1)
1981 || matches!(state, UnitState::DelayBeforeNextAttempt { .. });
1982 let attempt_str = if show_attempt_str {
1983 format!(
1984 "(attempt {}/{}) ",
1985 retry_data.attempt, retry_data.total_attempts
1986 )
1987 } else {
1988 String::new()
1989 };
1990
1991 self.write_unit_state(
1993 UnitKind::Test,
1994 &attempt_str,
1995 state,
1996 output.has_errors(),
1997 &mut writer,
1998 )?;
1999
2000 if state.has_valid_output() {
2002 self.unit_output.write_child_execution_output(
2003 &self.styles,
2004 &self.output_spec_for_info(UnitKind::Test),
2005 output,
2006 &mut writer,
2007 )?;
2008 }
2009 }
2010 }
2011
2012 writer.write_str_flush()?;
2013 let inner_writer = writer.into_inner();
2014
2015 writeln!(inner_writer)?;
2017
2018 Ok(())
2019 }
2020
2021 fn write_unit_state(
2022 &self,
2023 kind: UnitKind,
2024 attempt_str: &str,
2025 state: &UnitState,
2026 output_has_errors: bool,
2027 writer: &mut dyn WriteStr,
2028 ) -> io::Result<()> {
2029 let status_str = "status".style(self.styles.count);
2030 match state {
2031 UnitState::Running {
2032 pid,
2033 time_taken,
2034 slow_after,
2035 } => {
2036 let running_style = if output_has_errors {
2037 self.styles.fail
2038 } else if slow_after.is_some() {
2039 self.styles.skip
2040 } else {
2041 self.styles.pass
2042 };
2043 write!(
2044 writer,
2045 "{status_str}: {attempt_str}{} {} for {:.3?}s as PID {}",
2046 DisplayUnitKind::new(self.mode, kind),
2047 "running".style(running_style),
2048 time_taken.as_secs_f64(),
2049 pid.style(self.styles.count),
2050 )?;
2051 if let Some(slow_after) = slow_after {
2052 write!(
2053 writer,
2054 " (marked slow after {:.3?}s)",
2055 slow_after.as_secs_f64()
2056 )?;
2057 }
2058 writeln!(writer)?;
2059 }
2060 UnitState::Exiting {
2061 pid,
2062 time_taken,
2063 slow_after,
2064 tentative_result,
2065 waiting_duration,
2066 remaining,
2067 } => {
2068 write!(
2069 writer,
2070 "{status_str}: {attempt_str}{} ",
2071 DisplayUnitKind::new(self.mode, kind)
2072 )?;
2073
2074 self.write_info_execution_result(
2075 tentative_result.as_ref(),
2076 slow_after.is_some(),
2077 writer,
2078 )?;
2079 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2080 if let Some(slow_after) = slow_after {
2081 write!(
2082 writer,
2083 " (marked slow after {:.3?}s)",
2084 slow_after.as_secs_f64()
2085 )?;
2086 }
2087 writeln!(writer)?;
2088
2089 if *waiting_duration >= Duration::from_secs(1) {
2092 writeln!(
2093 writer,
2094 "{}: spent {:.3?}s waiting for {} PID {} to shut down, \
2095 will mark as leaky after another {:.3?}s",
2096 "note".style(self.styles.count),
2097 waiting_duration.as_secs_f64(),
2098 DisplayUnitKind::new(self.mode, kind),
2099 pid.style(self.styles.count),
2100 remaining.as_secs_f64(),
2101 )?;
2102 }
2103 }
2104 UnitState::Terminating(state) => {
2105 self.write_terminating_state(kind, attempt_str, state, writer)?;
2106 }
2107 UnitState::Exited {
2108 result,
2109 time_taken,
2110 slow_after,
2111 } => {
2112 write!(
2113 writer,
2114 "{status_str}: {attempt_str}{} ",
2115 DisplayUnitKind::new(self.mode, kind)
2116 )?;
2117 self.write_info_execution_result(Some(result), slow_after.is_some(), writer)?;
2118 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2119 if let Some(slow_after) = slow_after {
2120 write!(
2121 writer,
2122 " (marked slow after {:.3?}s)",
2123 slow_after.as_secs_f64()
2124 )?;
2125 }
2126 writeln!(writer)?;
2127 }
2128 UnitState::DelayBeforeNextAttempt {
2129 previous_result,
2130 previous_slow,
2131 waiting_duration,
2132 remaining,
2133 } => {
2134 write!(
2135 writer,
2136 "{status_str}: {attempt_str}{} ",
2137 DisplayUnitKind::new(self.mode, kind)
2138 )?;
2139 self.write_info_execution_result(Some(previous_result), *previous_slow, writer)?;
2140 writeln!(
2141 writer,
2142 ", currently {} before next attempt",
2143 "waiting".style(self.styles.count)
2144 )?;
2145 writeln!(
2146 writer,
2147 "{}: waited {:.3?}s so far, will wait another {:.3?}s before retrying {}",
2148 "note".style(self.styles.count),
2149 waiting_duration.as_secs_f64(),
2150 remaining.as_secs_f64(),
2151 DisplayUnitKind::new(self.mode, kind),
2152 )?;
2153 }
2154 }
2155
2156 Ok(())
2157 }
2158
2159 fn write_terminating_state(
2160 &self,
2161 kind: UnitKind,
2162 attempt_str: &str,
2163 state: &UnitTerminatingState,
2164 writer: &mut dyn WriteStr,
2165 ) -> io::Result<()> {
2166 let UnitTerminatingState {
2167 pid,
2168 time_taken,
2169 reason,
2170 method,
2171 waiting_duration,
2172 remaining,
2173 } = state;
2174
2175 writeln!(
2176 writer,
2177 "{}: {attempt_str}{} {} PID {} due to {} ({} ran for {:.3?}s)",
2178 "status".style(self.styles.count),
2179 "terminating".style(self.styles.fail),
2180 DisplayUnitKind::new(self.mode, kind),
2181 pid.style(self.styles.count),
2182 reason.style(self.styles.count),
2183 DisplayUnitKind::new(self.mode, kind),
2184 time_taken.as_secs_f64(),
2185 )?;
2186
2187 match method {
2188 #[cfg(unix)]
2189 UnitTerminateMethod::Signal(signal) => {
2190 writeln!(
2191 writer,
2192 "{}: sent {} to process group; spent {:.3?}s waiting for {} to exit, \
2193 will SIGKILL after another {:.3?}s",
2194 "note".style(self.styles.count),
2195 signal,
2196 waiting_duration.as_secs_f64(),
2197 DisplayUnitKind::new(self.mode, kind),
2198 remaining.as_secs_f64(),
2199 )?;
2200 }
2201 #[cfg(windows)]
2202 UnitTerminateMethod::JobObject => {
2203 writeln!(
2204 writer,
2205 "{}: instructed job object to terminate",
2209 "note".style(self.styles.count),
2210 )?;
2211 }
2212 #[cfg(windows)]
2213 UnitTerminateMethod::Wait => {
2214 writeln!(
2215 writer,
2216 "{}: waiting for {} to exit on its own; spent {:.3?}s, will terminate \
2217 job object after another {:.3?}s",
2218 "note".style(self.styles.count),
2219 DisplayUnitKind::new(self.mode, kind),
2220 waiting_duration.as_secs_f64(),
2221 remaining.as_secs_f64(),
2222 )?;
2223 }
2224 #[cfg(test)]
2225 UnitTerminateMethod::Fake => {
2226 writeln!(
2228 writer,
2229 "{}: fake termination method; spent {:.3?}s waiting for {} to exit, \
2230 will kill after another {:.3?}s",
2231 "note".style(self.styles.count),
2232 waiting_duration.as_secs_f64(),
2233 DisplayUnitKind::new(self.mode, kind),
2234 remaining.as_secs_f64(),
2235 )?;
2236 }
2237 }
2238
2239 Ok(())
2240 }
2241
2242 fn write_info_execution_result(
2246 &self,
2247 result: Option<&ExecutionResultDescription>,
2248 is_slow: bool,
2249 writer: &mut dyn WriteStr,
2250 ) -> io::Result<()> {
2251 match result {
2252 Some(ExecutionResultDescription::Pass) => {
2253 let style = if is_slow {
2254 self.styles.skip
2255 } else {
2256 self.styles.pass
2257 };
2258
2259 write!(writer, "{}", "passed".style(style))
2260 }
2261 Some(ExecutionResultDescription::Leak {
2262 result: LeakTimeoutResult::Pass,
2263 }) => write!(
2264 writer,
2265 "{}",
2266 "passed with leaked handles".style(self.styles.skip)
2267 ),
2268 Some(ExecutionResultDescription::Leak {
2269 result: LeakTimeoutResult::Fail,
2270 }) => write!(
2271 writer,
2272 "{}: exited with code 0, but leaked handles",
2273 "failed".style(self.styles.fail),
2274 ),
2275 Some(ExecutionResultDescription::Timeout {
2276 result: SlowTimeoutResult::Pass,
2277 }) => {
2278 write!(writer, "{}", "passed with timeout".style(self.styles.skip))
2279 }
2280 Some(ExecutionResultDescription::Timeout {
2281 result: SlowTimeoutResult::Fail,
2282 }) => {
2283 write!(writer, "{}", "timed out".style(self.styles.fail))
2284 }
2285 Some(ExecutionResultDescription::Fail {
2286 failure: FailureDescription::Abort { abort },
2287 leaked,
2288 }) => {
2289 write!(writer, "{}", "aborted".style(self.styles.fail))?;
2291 if let AbortDescription::UnixSignal { signal, name } = abort {
2295 write!(writer, " with signal {}", signal.style(self.styles.count))?;
2296 if let Some(s) = name {
2297 write!(writer, ": SIG{s}")?;
2298 }
2299 }
2300 if *leaked {
2301 write!(writer, " (leaked handles)")?;
2302 }
2303 Ok(())
2304 }
2305 Some(ExecutionResultDescription::Fail {
2306 failure: FailureDescription::ExitCode { code },
2307 leaked,
2308 }) => {
2309 write!(
2310 writer,
2311 "{} with exit code {}",
2312 "failed".style(self.styles.fail),
2313 code.style(self.styles.count),
2314 )?;
2315 if *leaked {
2316 write!(writer, " (leaked handles)")?;
2317 }
2318 Ok(())
2319 }
2320 Some(ExecutionResultDescription::ExecFail) => {
2321 write!(writer, "{}", "failed to execute".style(self.styles.fail))
2322 }
2323 None => {
2324 write!(
2325 writer,
2326 "{} with unknown status",
2327 "failed".style(self.styles.fail)
2328 )
2329 }
2330 }
2331 }
2332
2333 fn write_setup_script_execute_status(
2334 &self,
2335 run_status: &SetupScriptExecuteStatus<LiveSpec>,
2336 writer: &mut dyn WriteStr,
2337 ) -> io::Result<()> {
2338 let spec = self.output_spec_for_finished(&run_status.result, false);
2339 self.unit_output.write_child_execution_output(
2340 &self.styles,
2341 &spec,
2342 &run_status.output,
2343 writer,
2344 )?;
2345
2346 if show_finished_status_info_line(&run_status.result) {
2347 write!(
2348 writer,
2349 " (script ",
2351 )?;
2352 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2353 writeln!(writer, ")\n")?;
2354 }
2355
2356 Ok(())
2357 }
2358
2359 fn write_test_execute_status(
2360 &self,
2361 run_status: &ExecuteStatus<LiveSpec>,
2362 is_retry: bool,
2363 writer: &mut dyn WriteStr,
2364 ) -> io::Result<()> {
2365 let spec = self.output_spec_for_finished(&run_status.result, is_retry);
2370 self.unit_output.write_child_execution_output(
2371 &self.styles,
2372 &spec,
2373 &run_status.output,
2374 writer,
2375 )?;
2376
2377 if show_finished_status_info_line(&run_status.result) {
2378 write!(
2379 writer,
2380 " (test ",
2382 )?;
2383 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2384 writeln!(writer, ")\n")?;
2385 }
2386
2387 Ok(())
2388 }
2389
2390 fn output_spec_for_finished(
2391 &self,
2392 result: &ExecutionResultDescription,
2393 is_retry: bool,
2394 ) -> ChildOutputSpec {
2395 let header_style = if is_retry {
2396 self.styles.retry
2397 } else {
2398 match result {
2399 ExecutionResultDescription::Pass => self.styles.pass,
2400 ExecutionResultDescription::Leak {
2401 result: LeakTimeoutResult::Pass,
2402 } => self.styles.skip,
2403 ExecutionResultDescription::Leak {
2404 result: LeakTimeoutResult::Fail,
2405 } => self.styles.fail,
2406 ExecutionResultDescription::Timeout {
2407 result: SlowTimeoutResult::Pass,
2408 } => self.styles.skip,
2409 ExecutionResultDescription::Timeout {
2410 result: SlowTimeoutResult::Fail,
2411 } => self.styles.fail,
2412 ExecutionResultDescription::Fail { .. } => self.styles.fail,
2413 ExecutionResultDescription::ExecFail => self.styles.fail,
2414 }
2415 };
2416
2417 let (six_char_start, six_char_end, eight_char_start, eight_char_end, output_indent) =
2438 if self.no_output_indent {
2439 (
2440 self.theme_characters.hbar(2),
2441 self.theme_characters.hbar(2),
2442 self.theme_characters.hbar(1),
2443 self.theme_characters.hbar(1),
2444 "",
2445 )
2446 } else {
2447 (
2448 " ".to_owned(),
2449 self.theme_characters.hbar(3),
2450 " ".to_owned(),
2451 self.theme_characters.hbar(1),
2452 " ",
2453 )
2454 };
2455
2456 let stdout_header = format!(
2457 "{} {} {}",
2458 six_char_start.style(header_style),
2459 "stdout".style(header_style),
2460 six_char_end.style(header_style),
2461 );
2462 let stderr_header = format!(
2463 "{} {} {}",
2464 six_char_start.style(header_style),
2465 "stderr".style(header_style),
2466 six_char_end.style(header_style),
2467 );
2468 let combined_header = format!(
2469 "{} {} {}",
2470 six_char_start.style(header_style),
2471 "output".style(header_style),
2472 six_char_end.style(header_style),
2473 );
2474 let exec_fail_header = format!(
2475 "{} {} {}",
2476 eight_char_start.style(header_style),
2477 "execfail".style(header_style),
2478 eight_char_end.style(header_style),
2479 );
2480
2481 ChildOutputSpec {
2482 kind: UnitKind::Test,
2483 stdout_header,
2484 stderr_header,
2485 combined_header,
2486 exec_fail_header,
2487 output_indent,
2488 }
2489 }
2490
2491 fn output_spec_for_info(&self, kind: UnitKind) -> ChildOutputSpec {
2495 let stdout_header = format!("{}:", "stdout".style(self.styles.count));
2496 let stderr_header = format!("{}:", "stderr".style(self.styles.count));
2497 let combined_header = format!("{}:", "output".style(self.styles.count));
2498 let exec_fail_header = format!("{}:", "errors".style(self.styles.count));
2499
2500 ChildOutputSpec {
2501 kind,
2502 stdout_header,
2503 stderr_header,
2504 combined_header,
2505 exec_fail_header,
2506 output_indent: " ",
2507 }
2508 }
2509}
2510
2511#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2512enum TestInstanceCounter {
2513 Counter { current: usize, total: usize },
2514 Padded,
2515 None,
2516}
2517
2518#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2521enum StatusLineKind {
2522 Intermediate,
2524 Final,
2526}
2527
2528const LIBTEST_PANIC_EXIT_CODE: i32 = 101;
2529
2530fn show_finished_status_info_line(result: &ExecutionResultDescription) -> bool {
2533 match result {
2535 ExecutionResultDescription::Pass => false,
2536 ExecutionResultDescription::Leak {
2537 result: LeakTimeoutResult::Pass,
2538 } => {
2539 true
2541 }
2542 ExecutionResultDescription::Leak {
2543 result: LeakTimeoutResult::Fail,
2544 } => {
2545 true
2547 }
2548 ExecutionResultDescription::Fail {
2549 failure: FailureDescription::ExitCode { code },
2550 leaked,
2551 } => {
2552 *code != LIBTEST_PANIC_EXIT_CODE && !leaked
2555 }
2556 ExecutionResultDescription::Fail {
2557 failure: FailureDescription::Abort { .. },
2558 leaked: _,
2559 } => {
2560 true
2562 }
2563 ExecutionResultDescription::ExecFail => {
2564 false
2567 }
2568 ExecutionResultDescription::Timeout { .. } => {
2569 true
2571 }
2572 }
2573}
2574
2575fn status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2576 match result {
2578 ExecutionResultDescription::Fail {
2579 failure:
2580 FailureDescription::Abort {
2581 abort: AbortDescription::UnixSignal { signal, name },
2582 },
2583 leaked: _,
2584 } => match name {
2585 Some(s) => format!("SIG{s}").into(),
2586 None => format!("ABORT SIG {signal}").into(),
2587 },
2588 ExecutionResultDescription::Fail {
2589 failure:
2590 FailureDescription::Abort {
2591 abort: AbortDescription::WindowsNtStatus { .. },
2592 }
2593 | FailureDescription::Abort {
2594 abort: AbortDescription::WindowsJobObject,
2595 },
2596 leaked: _,
2597 } => {
2598 "ABORT".into()
2601 }
2602 ExecutionResultDescription::Fail {
2603 failure: FailureDescription::ExitCode { .. },
2604 leaked: true,
2605 } => "FAIL + LEAK".into(),
2606 ExecutionResultDescription::Fail {
2607 failure: FailureDescription::ExitCode { .. },
2608 leaked: false,
2609 } => "FAIL".into(),
2610 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2611 ExecutionResultDescription::Pass => "PASS".into(),
2612 ExecutionResultDescription::Leak {
2613 result: LeakTimeoutResult::Pass,
2614 } => "LEAK".into(),
2615 ExecutionResultDescription::Leak {
2616 result: LeakTimeoutResult::Fail,
2617 } => "LEAK-FAIL".into(),
2618 ExecutionResultDescription::Timeout {
2619 result: SlowTimeoutResult::Pass,
2620 } => "TIMEOUT-PASS".into(),
2621 ExecutionResultDescription::Timeout {
2622 result: SlowTimeoutResult::Fail,
2623 } => "TIMEOUT".into(),
2624 }
2625}
2626
2627fn short_status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2628 match result {
2630 ExecutionResultDescription::Fail {
2631 failure:
2632 FailureDescription::Abort {
2633 abort: AbortDescription::UnixSignal { signal, name },
2634 },
2635 leaked: _,
2636 } => match name {
2637 Some(s) => s.to_string().into(),
2638 None => format!("SIG {signal}").into(),
2639 },
2640 ExecutionResultDescription::Fail {
2641 failure:
2642 FailureDescription::Abort {
2643 abort: AbortDescription::WindowsNtStatus { .. },
2644 }
2645 | FailureDescription::Abort {
2646 abort: AbortDescription::WindowsJobObject,
2647 },
2648 leaked: _,
2649 } => {
2650 "ABORT".into()
2653 }
2654 ExecutionResultDescription::Fail {
2655 failure: FailureDescription::ExitCode { .. },
2656 leaked: true,
2657 } => "FL+LK".into(),
2658 ExecutionResultDescription::Fail {
2659 failure: FailureDescription::ExitCode { .. },
2660 leaked: false,
2661 } => "FAIL".into(),
2662 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2663 ExecutionResultDescription::Pass => "PASS".into(),
2664 ExecutionResultDescription::Leak {
2665 result: LeakTimeoutResult::Pass,
2666 } => "LEAK".into(),
2667 ExecutionResultDescription::Leak {
2668 result: LeakTimeoutResult::Fail,
2669 } => "LKFAIL".into(),
2670 ExecutionResultDescription::Timeout {
2671 result: SlowTimeoutResult::Pass,
2672 } => "TMPASS".into(),
2673 ExecutionResultDescription::Timeout {
2674 result: SlowTimeoutResult::Fail,
2675 } => "TMT".into(),
2676 }
2677}
2678
2679fn write_windows_abort_line(
2683 status: &AbortDescription,
2684 styles: &Styles,
2685 writer: &mut dyn WriteStr,
2686) -> io::Result<()> {
2687 match status {
2688 AbortDescription::UnixSignal { .. } => {
2689 Ok(())
2691 }
2692 AbortDescription::WindowsNtStatus { code, message } => {
2693 const INDENT: &str = " - ";
2696 let mut indented = indented(writer).with_str(INDENT).skip_initial();
2697 let code_str = format!("{:#010x}", code.style(styles.count));
2699 let status_str = match message {
2700 Some(msg) => format!("{code_str}: {msg}"),
2701 None => code_str,
2702 };
2703 writeln!(
2704 indented,
2705 "{:>12} {} {}",
2706 "-",
2707 "with code".style(styles.fail),
2708 status_str,
2709 )?;
2710 indented.write_str_flush()
2711 }
2712 AbortDescription::WindowsJobObject => {
2713 writeln!(
2714 writer,
2715 "{:>12} {} via {}",
2716 "-",
2717 "terminated".style(styles.fail),
2718 "job object".style(styles.count),
2719 )
2720 }
2721 }
2722}
2723
2724#[cfg(test)]
2725mod tests {
2726 use super::*;
2727 use crate::{
2728 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
2729 reporter::{
2730 ShowProgress,
2731 events::{
2732 ChildExecutionOutputDescription, ExecutionResult, FailureStatus,
2733 UnitTerminateReason,
2734 },
2735 test_helpers::global_slot_assignment,
2736 },
2737 test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
2738 };
2739 use bytes::Bytes;
2740 use chrono::Local;
2741 use nextest_metadata::{RustBinaryId, TestCaseName};
2742 use quick_junit::ReportUuid;
2743 use smol_str::SmolStr;
2744 use std::{num::NonZero, sync::Arc};
2745 use test_case::test_case;
2746
2747 fn with_reporter<'a, F>(f: F, out: &'a mut String)
2751 where
2752 F: FnOnce(DisplayReporter<'a>),
2753 {
2754 with_reporter_impl(f, out, false)
2755 }
2756
2757 fn with_verbose_reporter<'a, F>(f: F, out: &'a mut String)
2759 where
2760 F: FnOnce(DisplayReporter<'a>),
2761 {
2762 with_reporter_impl(f, out, true)
2763 }
2764
2765 fn with_reporter_impl<'a, F>(f: F, out: &'a mut String, verbose: bool)
2766 where
2767 F: FnOnce(DisplayReporter<'a>),
2768 {
2769 let builder = DisplayReporterBuilder {
2770 mode: NextestRunMode::Test,
2771 default_filter: CompiledDefaultFilter::for_default_config(),
2772 display_config: DisplayConfig {
2773 show_progress: ShowProgress::Counter,
2774 no_capture: true,
2775 status_level: Some(StatusLevel::Fail),
2776 final_status_level: Some(FinalStatusLevel::Fail),
2777 profile_status_level: StatusLevel::Fail,
2778 profile_final_status_level: FinalStatusLevel::Fail,
2779 },
2780 run_count: 5000,
2781 success_output: Some(TestOutputDisplay::Immediate),
2782 failure_output: Some(TestOutputDisplay::Immediate),
2783 should_colorize: false,
2784 verbose,
2785 no_output_indent: false,
2786 max_progress_running: MaxProgressRunning::default(),
2787 show_term_progress: ShowTerminalProgress::No,
2788 displayer_kind: DisplayerKind::Live,
2789 redactor: Redactor::noop(),
2790 };
2791
2792 let output = ReporterOutput::Writer {
2793 writer: out,
2794 use_unicode: true,
2795 };
2796 let reporter = builder.build(output);
2797 f(reporter);
2798 }
2799
2800 fn with_reporter_at_status_level<'a, F>(f: F, out: &'a mut String, status_level: StatusLevel)
2803 where
2804 F: FnOnce(DisplayReporter<'a>),
2805 {
2806 let builder = DisplayReporterBuilder {
2807 mode: NextestRunMode::Test,
2808 default_filter: CompiledDefaultFilter::for_default_config(),
2809 display_config: DisplayConfig {
2810 show_progress: ShowProgress::Counter,
2811 no_capture: false,
2812 status_level: Some(status_level),
2813 final_status_level: Some(FinalStatusLevel::Fail),
2814 profile_status_level: StatusLevel::Fail,
2815 profile_final_status_level: FinalStatusLevel::Fail,
2816 },
2817 run_count: 5000,
2818 success_output: Some(TestOutputDisplay::Immediate),
2819 failure_output: Some(TestOutputDisplay::Immediate),
2820 should_colorize: false,
2821 verbose: false,
2822 no_output_indent: false,
2823 max_progress_running: MaxProgressRunning::default(),
2824 show_term_progress: ShowTerminalProgress::No,
2825 displayer_kind: DisplayerKind::Live,
2826 redactor: Redactor::noop(),
2827 };
2828
2829 let output = ReporterOutput::Writer {
2830 writer: out,
2831 use_unicode: true,
2832 };
2833 let reporter = builder.build(output);
2834 f(reporter);
2835 }
2836
2837 fn make_split_output(
2838 result: Option<ExecutionResult>,
2839 stdout: &str,
2840 stderr: &str,
2841 ) -> ChildExecutionOutputDescription<LiveSpec> {
2842 ChildExecutionOutput::Output {
2843 result,
2844 output: ChildOutput::Split(ChildSplitOutput {
2845 stdout: Some(Bytes::from(stdout.to_owned()).into()),
2846 stderr: Some(Bytes::from(stderr.to_owned()).into()),
2847 }),
2848 errors: None,
2849 }
2850 .into()
2851 }
2852
2853 fn make_split_output_with_errors(
2854 result: Option<ExecutionResult>,
2855 stdout: &str,
2856 stderr: &str,
2857 errors: Vec<ChildError>,
2858 ) -> ChildExecutionOutputDescription<LiveSpec> {
2859 ChildExecutionOutput::Output {
2860 result,
2861 output: ChildOutput::Split(ChildSplitOutput {
2862 stdout: Some(Bytes::from(stdout.to_owned()).into()),
2863 stderr: Some(Bytes::from(stderr.to_owned()).into()),
2864 }),
2865 errors: ErrorList::new("testing split output", errors),
2866 }
2867 .into()
2868 }
2869
2870 fn make_combined_output_with_errors(
2871 result: Option<ExecutionResult>,
2872 output: &str,
2873 errors: Vec<ChildError>,
2874 ) -> ChildExecutionOutputDescription<LiveSpec> {
2875 ChildExecutionOutput::Output {
2876 result,
2877 output: ChildOutput::Combined {
2878 output: Bytes::from(output.to_owned()).into(),
2879 },
2880 errors: ErrorList::new("testing split output", errors),
2881 }
2882 .into()
2883 }
2884
2885 fn make_pass_output() -> FinalOutput {
2887 let status = ExecuteStatus {
2888 retry_data: RetryData {
2889 attempt: 1,
2890 total_attempts: 1,
2891 },
2892 output: make_split_output(Some(ExecutionResult::Pass), "", ""),
2893 result: ExecutionResultDescription::Pass,
2894 start_time: Local::now().into(),
2895 time_taken: Duration::from_secs(1),
2896 is_slow: false,
2897 delay_before_start: Duration::ZERO,
2898 error_summary: None,
2899 output_error_slice: None,
2900 };
2901 FinalOutput::Executed {
2902 run_statuses: ExecutionStatuses::new(vec![status], FlakyResult::default()),
2903 display_output: false,
2904 }
2905 }
2906
2907 fn make_fail_output() -> FinalOutput {
2909 let result = ExecutionResult::Fail {
2910 failure_status: FailureStatus::ExitCode(1),
2911 leaked: false,
2912 };
2913 let status = ExecuteStatus {
2914 retry_data: RetryData {
2915 attempt: 1,
2916 total_attempts: 1,
2917 },
2918 output: make_split_output(Some(result), "", ""),
2919 result: ExecutionResultDescription::from(result),
2920 start_time: Local::now().into(),
2921 time_taken: Duration::from_secs(1),
2922 is_slow: false,
2923 delay_before_start: Duration::ZERO,
2924 error_summary: None,
2925 output_error_slice: None,
2926 };
2927 FinalOutput::Executed {
2928 run_statuses: ExecutionStatuses::new(vec![status], FlakyResult::default()),
2929 display_output: false,
2930 }
2931 }
2932
2933 fn make_skip_output() -> FinalOutput {
2935 FinalOutput::Skipped(MismatchReason::Ignored)
2936 }
2937
2938 fn extract_ids<'a>(entries: &[FinalOutputEntry<'a>]) -> Vec<(&'a str, &'a str)> {
2940 entries
2941 .iter()
2942 .map(|e| (e.instance.binary_id.as_str(), e.instance.test_name.as_str()))
2943 .collect()
2944 }
2945
2946 #[test]
2947 fn final_status_line() {
2948 let binary_id = RustBinaryId::new("my-binary-id");
2949 let test_name = TestCaseName::new("test1");
2950 let test_instance = TestInstanceId {
2951 binary_id: &binary_id,
2952 test_name: &test_name,
2953 };
2954
2955 let fail_result_internal = ExecutionResult::Fail {
2956 failure_status: FailureStatus::ExitCode(1),
2957 leaked: false,
2958 };
2959 let fail_result = ExecutionResultDescription::from(fail_result_internal);
2960
2961 let fail_status = ExecuteStatus {
2962 retry_data: RetryData {
2963 attempt: 1,
2964 total_attempts: 2,
2965 },
2966 output: make_split_output(Some(fail_result_internal), "", ""),
2968 result: fail_result.clone(),
2969 start_time: Local::now().into(),
2970 time_taken: Duration::from_secs(1),
2971 is_slow: false,
2972 delay_before_start: Duration::ZERO,
2973 error_summary: None,
2974 output_error_slice: None,
2975 };
2976 let fail_describe = ExecutionDescription::Failure {
2977 first_status: &fail_status,
2978 last_status: &fail_status,
2979 retries: &[],
2980 };
2981
2982 let flaky_status = ExecuteStatus {
2983 retry_data: RetryData {
2984 attempt: 2,
2985 total_attempts: 2,
2986 },
2987 output: make_split_output(Some(fail_result_internal), "", ""),
2989 result: ExecutionResultDescription::Pass,
2990 start_time: Local::now().into(),
2991 time_taken: Duration::from_secs(2),
2992 is_slow: false,
2993 delay_before_start: Duration::ZERO,
2994 error_summary: None,
2995 output_error_slice: None,
2996 };
2997
2998 let statuses =
3000 ExecutionStatuses::new(vec![fail_status.clone(), flaky_status], FlakyResult::Pass);
3001 let flaky_describe = statuses.describe();
3002
3003 let mut out = String::new();
3004
3005 with_reporter(
3006 |mut reporter| {
3007 reporter
3009 .inner
3010 .write_final_status_line(
3011 None,
3012 TestInstanceCounter::None,
3013 test_instance,
3014 fail_describe,
3015 reporter.output.writer_mut().unwrap(),
3016 )
3017 .unwrap();
3018
3019 reporter
3020 .inner
3021 .write_final_status_line(
3022 Some(StressIndex {
3023 current: 1,
3024 total: None,
3025 }),
3026 TestInstanceCounter::Padded,
3027 test_instance,
3028 flaky_describe,
3029 reporter.output.writer_mut().unwrap(),
3030 )
3031 .unwrap();
3032
3033 reporter
3034 .inner
3035 .write_final_status_line(
3036 Some(StressIndex {
3037 current: 2,
3038 total: Some(NonZero::new(3).unwrap()),
3039 }),
3040 TestInstanceCounter::Counter {
3041 current: 20,
3042 total: 5000,
3043 },
3044 test_instance,
3045 flaky_describe,
3046 reporter.output.writer_mut().unwrap(),
3047 )
3048 .unwrap();
3049 },
3050 &mut out,
3051 );
3052
3053 insta::assert_snapshot!("final_status_output", out,);
3054 }
3055
3056 #[test]
3057 fn status_line_all_variants() {
3058 let binary_id = RustBinaryId::new("my-binary-id");
3059 let test_name = TestCaseName::new("test_name");
3060 let test_instance = TestInstanceId {
3061 binary_id: &binary_id,
3062 test_name: &test_name,
3063 };
3064
3065 let pass_result_internal = ExecutionResult::Pass;
3067 let pass_result = ExecutionResultDescription::from(pass_result_internal);
3068
3069 let leak_pass_result_internal = ExecutionResult::Leak {
3070 result: LeakTimeoutResult::Pass,
3071 };
3072 let leak_pass_result = ExecutionResultDescription::from(leak_pass_result_internal);
3073
3074 let timeout_pass_result_internal = ExecutionResult::Timeout {
3075 result: SlowTimeoutResult::Pass,
3076 };
3077 let timeout_pass_result = ExecutionResultDescription::from(timeout_pass_result_internal);
3078
3079 let fail_result_internal = ExecutionResult::Fail {
3081 failure_status: FailureStatus::ExitCode(1),
3082 leaked: false,
3083 };
3084 let fail_result = ExecutionResultDescription::from(fail_result_internal);
3085
3086 let fail_leak_result_internal = ExecutionResult::Fail {
3087 failure_status: FailureStatus::ExitCode(1),
3088 leaked: true,
3089 };
3090 let fail_leak_result = ExecutionResultDescription::from(fail_leak_result_internal);
3091
3092 let exec_fail_result_internal = ExecutionResult::ExecFail;
3093 let exec_fail_result = ExecutionResultDescription::from(exec_fail_result_internal);
3094
3095 let leak_fail_result_internal = ExecutionResult::Leak {
3096 result: LeakTimeoutResult::Fail,
3097 };
3098 let leak_fail_result = ExecutionResultDescription::from(leak_fail_result_internal);
3099
3100 let timeout_fail_result_internal = ExecutionResult::Timeout {
3101 result: SlowTimeoutResult::Fail,
3102 };
3103 let timeout_fail_result = ExecutionResultDescription::from(timeout_fail_result_internal);
3104
3105 let abort_unix_result = ExecutionResultDescription::Fail {
3107 failure: FailureDescription::Abort {
3108 abort: AbortDescription::UnixSignal {
3109 signal: 11,
3110 name: Some("SEGV".into()),
3111 },
3112 },
3113 leaked: false,
3114 };
3115 let abort_windows_result = ExecutionResultDescription::Fail {
3116 failure: FailureDescription::Abort {
3117 abort: AbortDescription::WindowsNtStatus {
3118 code: 0xC0000005_u32 as i32,
3120 message: Some("Access violation".into()),
3121 },
3122 },
3123 leaked: false,
3124 };
3125
3126 let pass_status = ExecuteStatus {
3128 retry_data: RetryData {
3129 attempt: 1,
3130 total_attempts: 1,
3131 },
3132 output: make_split_output(Some(pass_result_internal), "", ""),
3133 result: pass_result.clone(),
3134 start_time: Local::now().into(),
3135 time_taken: Duration::from_secs(1),
3136 is_slow: false,
3137 delay_before_start: Duration::ZERO,
3138 error_summary: None,
3139 output_error_slice: None,
3140 };
3141
3142 let leak_pass_status = ExecuteStatus {
3143 retry_data: RetryData {
3144 attempt: 1,
3145 total_attempts: 1,
3146 },
3147 output: make_split_output(Some(leak_pass_result_internal), "", ""),
3148 result: leak_pass_result.clone(),
3149 start_time: Local::now().into(),
3150 time_taken: Duration::from_secs(2),
3151 is_slow: false,
3152 delay_before_start: Duration::ZERO,
3153 error_summary: None,
3154 output_error_slice: None,
3155 };
3156
3157 let timeout_pass_status = ExecuteStatus {
3158 retry_data: RetryData {
3159 attempt: 1,
3160 total_attempts: 1,
3161 },
3162 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3163 result: timeout_pass_result.clone(),
3164 start_time: Local::now().into(),
3165 time_taken: Duration::from_secs(240),
3166 is_slow: false,
3167 delay_before_start: Duration::ZERO,
3168 error_summary: None,
3169 output_error_slice: None,
3170 };
3171
3172 let pass_slow_status = ExecuteStatus {
3174 retry_data: RetryData {
3175 attempt: 1,
3176 total_attempts: 1,
3177 },
3178 output: make_split_output(Some(pass_result_internal), "", ""),
3179 result: pass_result.clone(),
3180 start_time: Local::now().into(),
3181 time_taken: Duration::from_secs(30),
3182 is_slow: true,
3183 delay_before_start: Duration::ZERO,
3184 error_summary: None,
3185 output_error_slice: None,
3186 };
3187
3188 let leak_pass_slow_status = ExecuteStatus {
3189 retry_data: RetryData {
3190 attempt: 1,
3191 total_attempts: 1,
3192 },
3193 output: make_split_output(Some(leak_pass_result_internal), "", ""),
3194 result: leak_pass_result.clone(),
3195 start_time: Local::now().into(),
3196 time_taken: Duration::from_secs(30),
3197 is_slow: true,
3198 delay_before_start: Duration::ZERO,
3199 error_summary: None,
3200 output_error_slice: None,
3201 };
3202
3203 let timeout_pass_slow_status = ExecuteStatus {
3204 retry_data: RetryData {
3205 attempt: 1,
3206 total_attempts: 1,
3207 },
3208 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3209 result: timeout_pass_result.clone(),
3210 start_time: Local::now().into(),
3211 time_taken: Duration::from_secs(300),
3212 is_slow: true,
3213 delay_before_start: Duration::ZERO,
3214 error_summary: None,
3215 output_error_slice: None,
3216 };
3217
3218 let flaky_first_status = ExecuteStatus {
3220 retry_data: RetryData {
3221 attempt: 1,
3222 total_attempts: 2,
3223 },
3224 output: make_split_output(Some(fail_result_internal), "", ""),
3225 result: fail_result.clone(),
3226 start_time: Local::now().into(),
3227 time_taken: Duration::from_secs(1),
3228 is_slow: false,
3229 delay_before_start: Duration::ZERO,
3230 error_summary: None,
3231 output_error_slice: None,
3232 };
3233 let flaky_last_status = ExecuteStatus {
3234 retry_data: RetryData {
3235 attempt: 2,
3236 total_attempts: 2,
3237 },
3238 output: make_split_output(Some(pass_result_internal), "", ""),
3239 result: pass_result.clone(),
3240 start_time: Local::now().into(),
3241 time_taken: Duration::from_secs(1),
3242 is_slow: false,
3243 delay_before_start: Duration::ZERO,
3244 error_summary: None,
3245 output_error_slice: None,
3246 };
3247
3248 let fail_status = ExecuteStatus {
3250 retry_data: RetryData {
3251 attempt: 1,
3252 total_attempts: 1,
3253 },
3254 output: make_split_output(Some(fail_result_internal), "", ""),
3255 result: fail_result.clone(),
3256 start_time: Local::now().into(),
3257 time_taken: Duration::from_secs(1),
3258 is_slow: false,
3259 delay_before_start: Duration::ZERO,
3260 error_summary: None,
3261 output_error_slice: None,
3262 };
3263
3264 let fail_leak_status = ExecuteStatus {
3265 retry_data: RetryData {
3266 attempt: 1,
3267 total_attempts: 1,
3268 },
3269 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3270 result: fail_leak_result.clone(),
3271 start_time: Local::now().into(),
3272 time_taken: Duration::from_secs(1),
3273 is_slow: false,
3274 delay_before_start: Duration::ZERO,
3275 error_summary: None,
3276 output_error_slice: None,
3277 };
3278
3279 let exec_fail_status = ExecuteStatus {
3280 retry_data: RetryData {
3281 attempt: 1,
3282 total_attempts: 1,
3283 },
3284 output: make_split_output(Some(exec_fail_result_internal), "", ""),
3285 result: exec_fail_result.clone(),
3286 start_time: Local::now().into(),
3287 time_taken: Duration::from_secs(1),
3288 is_slow: false,
3289 delay_before_start: Duration::ZERO,
3290 error_summary: None,
3291 output_error_slice: None,
3292 };
3293
3294 let leak_fail_status = ExecuteStatus {
3295 retry_data: RetryData {
3296 attempt: 1,
3297 total_attempts: 1,
3298 },
3299 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3300 result: leak_fail_result.clone(),
3301 start_time: Local::now().into(),
3302 time_taken: Duration::from_secs(1),
3303 is_slow: false,
3304 delay_before_start: Duration::ZERO,
3305 error_summary: None,
3306 output_error_slice: None,
3307 };
3308
3309 let timeout_fail_status = ExecuteStatus {
3310 retry_data: RetryData {
3311 attempt: 1,
3312 total_attempts: 1,
3313 },
3314 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3315 result: timeout_fail_result.clone(),
3316 start_time: Local::now().into(),
3317 time_taken: Duration::from_secs(60),
3318 is_slow: false,
3319 delay_before_start: Duration::ZERO,
3320 error_summary: None,
3321 output_error_slice: None,
3322 };
3323
3324 let abort_unix_status = ExecuteStatus {
3325 retry_data: RetryData {
3326 attempt: 1,
3327 total_attempts: 1,
3328 },
3329 output: make_split_output(None, "", ""),
3330 result: abort_unix_result.clone(),
3331 start_time: Local::now().into(),
3332 time_taken: Duration::from_secs(1),
3333 is_slow: false,
3334 delay_before_start: Duration::ZERO,
3335 error_summary: None,
3336 output_error_slice: None,
3337 };
3338
3339 let abort_windows_status = ExecuteStatus {
3340 retry_data: RetryData {
3341 attempt: 1,
3342 total_attempts: 1,
3343 },
3344 output: make_split_output(None, "", ""),
3345 result: abort_windows_result.clone(),
3346 start_time: Local::now().into(),
3347 time_taken: Duration::from_secs(1),
3348 is_slow: false,
3349 delay_before_start: Duration::ZERO,
3350 error_summary: None,
3351 output_error_slice: None,
3352 };
3353
3354 let fail_retry_status = ExecuteStatus {
3356 retry_data: RetryData {
3357 attempt: 2,
3358 total_attempts: 2,
3359 },
3360 output: make_split_output(Some(fail_result_internal), "", ""),
3361 result: fail_result.clone(),
3362 start_time: Local::now().into(),
3363 time_taken: Duration::from_secs(1),
3364 is_slow: false,
3365 delay_before_start: Duration::ZERO,
3366 error_summary: None,
3367 output_error_slice: None,
3368 };
3369
3370 let fail_leak_retry_status = ExecuteStatus {
3371 retry_data: RetryData {
3372 attempt: 2,
3373 total_attempts: 2,
3374 },
3375 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3376 result: fail_leak_result.clone(),
3377 start_time: Local::now().into(),
3378 time_taken: Duration::from_secs(1),
3379 is_slow: false,
3380 delay_before_start: Duration::ZERO,
3381 error_summary: None,
3382 output_error_slice: None,
3383 };
3384
3385 let leak_fail_retry_status = ExecuteStatus {
3386 retry_data: RetryData {
3387 attempt: 2,
3388 total_attempts: 2,
3389 },
3390 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3391 result: leak_fail_result.clone(),
3392 start_time: Local::now().into(),
3393 time_taken: Duration::from_secs(1),
3394 is_slow: false,
3395 delay_before_start: Duration::ZERO,
3396 error_summary: None,
3397 output_error_slice: None,
3398 };
3399
3400 let timeout_fail_retry_status = ExecuteStatus {
3401 retry_data: RetryData {
3402 attempt: 2,
3403 total_attempts: 2,
3404 },
3405 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3406 result: timeout_fail_result.clone(),
3407 start_time: Local::now().into(),
3408 time_taken: Duration::from_secs(60),
3409 is_slow: false,
3410 delay_before_start: Duration::ZERO,
3411 error_summary: None,
3412 output_error_slice: None,
3413 };
3414
3415 let pass_describe = ExecutionDescription::Success {
3417 single_status: &pass_status,
3418 };
3419 let leak_pass_describe = ExecutionDescription::Success {
3420 single_status: &leak_pass_status,
3421 };
3422 let timeout_pass_describe = ExecutionDescription::Success {
3423 single_status: &timeout_pass_status,
3424 };
3425 let pass_slow_describe = ExecutionDescription::Success {
3426 single_status: &pass_slow_status,
3427 };
3428 let leak_pass_slow_describe = ExecutionDescription::Success {
3429 single_status: &leak_pass_slow_status,
3430 };
3431 let timeout_pass_slow_describe = ExecutionDescription::Success {
3432 single_status: &timeout_pass_slow_status,
3433 };
3434 let flaky_describe = ExecutionDescription::Flaky {
3435 last_status: &flaky_last_status,
3436 prior_statuses: std::slice::from_ref(&flaky_first_status),
3437 result: FlakyResult::Pass,
3438 };
3439 let flaky_fail_describe = ExecutionDescription::Flaky {
3440 last_status: &flaky_last_status,
3441 prior_statuses: std::slice::from_ref(&flaky_first_status),
3442 result: FlakyResult::Fail,
3443 };
3444 let fail_describe = ExecutionDescription::Failure {
3445 first_status: &fail_status,
3446 last_status: &fail_status,
3447 retries: &[],
3448 };
3449 let fail_leak_describe = ExecutionDescription::Failure {
3450 first_status: &fail_leak_status,
3451 last_status: &fail_leak_status,
3452 retries: &[],
3453 };
3454 let exec_fail_describe = ExecutionDescription::Failure {
3455 first_status: &exec_fail_status,
3456 last_status: &exec_fail_status,
3457 retries: &[],
3458 };
3459 let leak_fail_describe = ExecutionDescription::Failure {
3460 first_status: &leak_fail_status,
3461 last_status: &leak_fail_status,
3462 retries: &[],
3463 };
3464 let timeout_fail_describe = ExecutionDescription::Failure {
3465 first_status: &timeout_fail_status,
3466 last_status: &timeout_fail_status,
3467 retries: &[],
3468 };
3469 let abort_unix_describe = ExecutionDescription::Failure {
3470 first_status: &abort_unix_status,
3471 last_status: &abort_unix_status,
3472 retries: &[],
3473 };
3474 let abort_windows_describe = ExecutionDescription::Failure {
3475 first_status: &abort_windows_status,
3476 last_status: &abort_windows_status,
3477 retries: &[],
3478 };
3479 let fail_retry_describe = ExecutionDescription::Failure {
3480 first_status: &fail_status,
3481 last_status: &fail_retry_status,
3482 retries: std::slice::from_ref(&fail_retry_status),
3483 };
3484 let fail_leak_retry_describe = ExecutionDescription::Failure {
3485 first_status: &fail_leak_status,
3486 last_status: &fail_leak_retry_status,
3487 retries: std::slice::from_ref(&fail_leak_retry_status),
3488 };
3489 let leak_fail_retry_describe = ExecutionDescription::Failure {
3490 first_status: &leak_fail_status,
3491 last_status: &leak_fail_retry_status,
3492 retries: std::slice::from_ref(&leak_fail_retry_status),
3493 };
3494 let timeout_fail_retry_describe = ExecutionDescription::Failure {
3495 first_status: &timeout_fail_status,
3496 last_status: &timeout_fail_retry_status,
3497 retries: std::slice::from_ref(&timeout_fail_retry_status),
3498 };
3499
3500 let test_cases: Vec<(&str, ExecutionDescription<'_, LiveSpec>)> = vec![
3503 ("pass", pass_describe),
3505 ("leak pass", leak_pass_describe),
3506 ("timeout pass", timeout_pass_describe),
3507 ("pass slow", pass_slow_describe),
3509 ("leak pass slow", leak_pass_slow_describe),
3510 ("timeout pass slow", timeout_pass_slow_describe),
3511 ("flaky", flaky_describe),
3513 ("flaky fail", flaky_fail_describe),
3514 ("fail", fail_describe),
3516 ("fail leak", fail_leak_describe),
3517 ("exec fail", exec_fail_describe),
3518 ("leak fail", leak_fail_describe),
3519 ("timeout fail", timeout_fail_describe),
3520 ("abort unix", abort_unix_describe),
3521 ("abort windows", abort_windows_describe),
3522 ("fail retry", fail_retry_describe),
3524 ("fail leak retry", fail_leak_retry_describe),
3525 ("leak fail retry", leak_fail_retry_describe),
3526 ("timeout fail retry", timeout_fail_retry_describe),
3527 ];
3528
3529 let mut out = String::new();
3530 let mut counter = 0usize;
3531
3532 with_reporter(
3533 |mut reporter| {
3534 let writer = reporter.output.writer_mut().unwrap();
3535
3536 for (kind_name, kind) in [
3538 ("intermediate", StatusLineKind::Intermediate),
3539 ("final", StatusLineKind::Final),
3540 ] {
3541 writeln!(writer, "=== {kind_name} ===").unwrap();
3542
3543 for (label, describe) in &test_cases {
3544 counter += 1;
3545 let test_counter = TestInstanceCounter::Counter {
3546 current: counter,
3547 total: 100,
3548 };
3549
3550 writeln!(writer, "# {label}: ").unwrap();
3552
3553 reporter
3554 .inner
3555 .write_status_line_impl(
3556 None,
3557 test_counter,
3558 test_instance,
3559 *describe,
3560 kind,
3561 writer,
3562 )
3563 .unwrap();
3564 }
3565 }
3566 },
3567 &mut out,
3568 );
3569
3570 insta::assert_snapshot!("status_line_all_variants", out);
3571 }
3572
3573 #[test]
3574 fn test_summary_line() {
3575 let run_id = ReportUuid::nil();
3576 let mut out = String::new();
3577
3578 with_reporter(
3579 |mut reporter| {
3580 let run_stats_success = RunStats {
3582 initial_run_count: 5,
3583 finished_count: 5,
3584 setup_scripts_initial_count: 0,
3585 setup_scripts_finished_count: 0,
3586 setup_scripts_passed: 0,
3587 setup_scripts_failed: 0,
3588 setup_scripts_exec_failed: 0,
3589 setup_scripts_timed_out: 0,
3590 passed: 5,
3591 passed_slow: 0,
3592 passed_timed_out: 0,
3593 flaky: 0,
3594 failed: 0,
3595 failed_slow: 0,
3596 failed_timed_out: 0,
3597 leaky: 0,
3598 leaky_failed: 0,
3599 exec_failed: 0,
3600 skipped: 0,
3601 cancel_reason: None,
3602 };
3603
3604 reporter
3605 .write_event(&TestEvent {
3606 timestamp: Local::now().into(),
3607 elapsed: Duration::ZERO,
3608 kind: TestEventKind::RunFinished {
3609 run_id,
3610 start_time: Local::now().into(),
3611 elapsed: Duration::from_secs(2),
3612 run_stats: RunFinishedStats::Single(run_stats_success),
3613 outstanding_not_seen: None,
3614 },
3615 })
3616 .unwrap();
3617
3618 let run_stats_mixed = RunStats {
3620 initial_run_count: 10,
3621 finished_count: 8,
3622 setup_scripts_initial_count: 1,
3623 setup_scripts_finished_count: 1,
3624 setup_scripts_passed: 1,
3625 setup_scripts_failed: 0,
3626 setup_scripts_exec_failed: 0,
3627 setup_scripts_timed_out: 0,
3628 passed: 5,
3629 passed_slow: 1,
3630 passed_timed_out: 2,
3631 flaky: 1,
3632 failed: 2,
3633 failed_slow: 0,
3634 failed_timed_out: 1,
3635 leaky: 1,
3636 leaky_failed: 0,
3637 exec_failed: 1,
3638 skipped: 2,
3639 cancel_reason: Some(CancelReason::Signal),
3640 };
3641
3642 reporter
3643 .write_event(&TestEvent {
3644 timestamp: Local::now().into(),
3645 elapsed: Duration::ZERO,
3646 kind: TestEventKind::RunFinished {
3647 run_id,
3648 start_time: Local::now().into(),
3649 elapsed: Duration::from_millis(15750),
3650 run_stats: RunFinishedStats::Single(run_stats_mixed),
3651 outstanding_not_seen: None,
3652 },
3653 })
3654 .unwrap();
3655
3656 let stress_stats_success = StressRunStats {
3658 completed: StressIndex {
3659 current: 25,
3660 total: Some(NonZero::new(50).unwrap()),
3661 },
3662 success_count: 25,
3663 failed_count: 0,
3664 last_final_stats: FinalRunStats::Success,
3665 };
3666
3667 reporter
3668 .write_event(&TestEvent {
3669 timestamp: Local::now().into(),
3670 elapsed: Duration::ZERO,
3671 kind: TestEventKind::RunFinished {
3672 run_id,
3673 start_time: Local::now().into(),
3674 elapsed: Duration::from_secs(120),
3675 run_stats: RunFinishedStats::Stress(stress_stats_success),
3676 outstanding_not_seen: None,
3677 },
3678 })
3679 .unwrap();
3680
3681 let stress_stats_failed = StressRunStats {
3683 completed: StressIndex {
3684 current: 15,
3685 total: None, },
3687 success_count: 12,
3688 failed_count: 3,
3689 last_final_stats: FinalRunStats::Cancelled {
3690 reason: Some(CancelReason::Interrupt),
3691 kind: RunStatsFailureKind::SetupScript,
3692 },
3693 };
3694
3695 reporter
3696 .write_event(&TestEvent {
3697 timestamp: Local::now().into(),
3698 elapsed: Duration::ZERO,
3699 kind: TestEventKind::RunFinished {
3700 run_id,
3701 start_time: Local::now().into(),
3702 elapsed: Duration::from_millis(45250),
3703 run_stats: RunFinishedStats::Stress(stress_stats_failed),
3704 outstanding_not_seen: None,
3705 },
3706 })
3707 .unwrap();
3708
3709 let run_stats_empty = RunStats {
3711 initial_run_count: 0,
3712 finished_count: 0,
3713 setup_scripts_initial_count: 0,
3714 setup_scripts_finished_count: 0,
3715 setup_scripts_passed: 0,
3716 setup_scripts_failed: 0,
3717 setup_scripts_exec_failed: 0,
3718 setup_scripts_timed_out: 0,
3719 passed: 0,
3720 passed_slow: 0,
3721 passed_timed_out: 0,
3722 flaky: 0,
3723 failed: 0,
3724 failed_slow: 0,
3725 failed_timed_out: 0,
3726 leaky: 0,
3727 leaky_failed: 0,
3728 exec_failed: 0,
3729 skipped: 0,
3730 cancel_reason: None,
3731 };
3732
3733 reporter
3734 .write_event(&TestEvent {
3735 timestamp: Local::now().into(),
3736 elapsed: Duration::ZERO,
3737 kind: TestEventKind::RunFinished {
3738 run_id,
3739 start_time: Local::now().into(),
3740 elapsed: Duration::from_millis(100),
3741 run_stats: RunFinishedStats::Single(run_stats_empty),
3742 outstanding_not_seen: None,
3743 },
3744 })
3745 .unwrap();
3746 },
3747 &mut out,
3748 );
3749
3750 insta::assert_snapshot!("summary_line_output", out,);
3751 }
3752
3753 #[test]
3757 fn test_info_response() {
3758 let args = vec!["arg1".to_string(), "arg2".to_string()];
3759 let binary_id = RustBinaryId::new("my-binary-id");
3760 let test_name1 = TestCaseName::new("test1");
3761 let test_name2 = TestCaseName::new("test2");
3762 let test_name3 = TestCaseName::new("test3");
3763 let test_name4 = TestCaseName::new("test4");
3764 let test_name5 = TestCaseName::new("test5");
3765
3766 let mut out = String::new();
3767
3768 with_reporter(
3769 |mut reporter| {
3770 reporter
3772 .write_event(&TestEvent {
3773 timestamp: Local::now().into(),
3774 elapsed: Duration::ZERO,
3775 kind: TestEventKind::InfoStarted {
3776 total: 30,
3777 run_stats: RunStats {
3778 initial_run_count: 40,
3779 finished_count: 20,
3780 setup_scripts_initial_count: 1,
3781 setup_scripts_finished_count: 1,
3782 setup_scripts_passed: 1,
3783 setup_scripts_failed: 0,
3784 setup_scripts_exec_failed: 0,
3785 setup_scripts_timed_out: 0,
3786 passed: 17,
3787 passed_slow: 4,
3788 passed_timed_out: 3,
3789 flaky: 2,
3790 failed: 2,
3791 failed_slow: 1,
3792 failed_timed_out: 1,
3793 leaky: 1,
3794 leaky_failed: 2,
3795 exec_failed: 1,
3796 skipped: 5,
3797 cancel_reason: None,
3798 },
3799 },
3800 })
3801 .unwrap();
3802
3803 reporter
3805 .write_event(&TestEvent {
3806 timestamp: Local::now().into(),
3807 elapsed: Duration::ZERO,
3808 kind: TestEventKind::InfoResponse {
3809 index: 0,
3810 total: 21,
3811 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3814 stress_index: None,
3815 script_id: ScriptId::new(SmolStr::new("setup")).unwrap(),
3816 program: "setup".to_owned(),
3817 args: args.clone(),
3818 state: UnitState::Running {
3819 pid: 4567,
3820 time_taken: Duration::from_millis(1234),
3821 slow_after: None,
3822 },
3823 output: make_split_output(
3824 None,
3825 "script stdout 1",
3826 "script stderr 1",
3827 ),
3828 }),
3829 },
3830 })
3831 .unwrap();
3832
3833 reporter
3836 .write_event(&TestEvent {
3837 timestamp: Local::now().into(),
3838 elapsed: Duration::ZERO,
3839 kind: TestEventKind::InfoResponse {
3840 index: 1,
3841 total: 21,
3842 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3843 stress_index: None,
3844 script_id: ScriptId::new(SmolStr::new("setup-slow")).unwrap(),
3845 program: "setup-slow".to_owned(),
3846 args: args.clone(),
3847 state: UnitState::Running {
3848 pid: 4568,
3849 time_taken: Duration::from_millis(1234),
3850 slow_after: Some(Duration::from_millis(1000)),
3851 },
3852 output: make_combined_output_with_errors(
3853 None,
3854 "script output 2\n",
3855 vec![ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3856 std::io::Error::other("read stdout error"),
3857 )))],
3858 ),
3859 }),
3860 },
3861 })
3862 .unwrap();
3863
3864 reporter
3866 .write_event(&TestEvent {
3867 timestamp: Local::now().into(),
3868 elapsed: Duration::ZERO,
3869 kind: TestEventKind::InfoResponse {
3870 index: 2,
3871 total: 21,
3872 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3873 stress_index: None,
3874 script_id: ScriptId::new(SmolStr::new("setup-terminating"))
3875 .unwrap(),
3876 program: "setup-terminating".to_owned(),
3877 args: args.clone(),
3878 state: UnitState::Terminating(UnitTerminatingState {
3879 pid: 5094,
3880 time_taken: Duration::from_millis(1234),
3881 reason: UnitTerminateReason::Signal,
3882 method: UnitTerminateMethod::Fake,
3883 waiting_duration: Duration::from_millis(6789),
3884 remaining: Duration::from_millis(9786),
3885 }),
3886 output: make_split_output_with_errors(
3887 None,
3888 "script output 3\n",
3889 "script stderr 3\n",
3890 vec![
3891 ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3892 std::io::Error::other("read stdout error"),
3893 ))),
3894 ChildError::Fd(ChildFdError::ReadStderr(Arc::new(
3895 std::io::Error::other("read stderr error"),
3896 ))),
3897 ],
3898 ),
3899 }),
3900 },
3901 })
3902 .unwrap();
3903
3904 reporter
3908 .write_event(&TestEvent {
3909 timestamp: Local::now().into(),
3910 elapsed: Duration::ZERO,
3911 kind: TestEventKind::InfoResponse {
3912 index: 3,
3913 total: 21,
3914 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3915 stress_index: Some(StressIndex {
3916 current: 0,
3917 total: None,
3918 }),
3919 script_id: ScriptId::new(SmolStr::new("setup-exiting")).unwrap(),
3920 program: "setup-exiting".to_owned(),
3921 args: args.clone(),
3922 state: UnitState::Exiting {
3923 pid: 9987,
3924 time_taken: Duration::from_millis(1234),
3925 slow_after: Some(Duration::from_millis(1000)),
3926 tentative_result: Some(ExecutionResultDescription::ExecFail),
3930 waiting_duration: Duration::from_millis(10467),
3931 remaining: Duration::from_millis(335),
3932 },
3933 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3934 Arc::new(std::io::Error::other("exec error")),
3935 ))
3936 .into(),
3937 }),
3938 },
3939 })
3940 .unwrap();
3941
3942 reporter
3944 .write_event(&TestEvent {
3945 timestamp: Local::now().into(),
3946 elapsed: Duration::ZERO,
3947 kind: TestEventKind::InfoResponse {
3948 index: 4,
3949 total: 21,
3950 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3951 stress_index: Some(StressIndex {
3952 current: 1,
3953 total: Some(NonZero::new(3).unwrap()),
3954 }),
3955 script_id: ScriptId::new(SmolStr::new("setup-exited")).unwrap(),
3956 program: "setup-exited".to_owned(),
3957 args: args.clone(),
3958 state: UnitState::Exited {
3959 result: ExecutionResultDescription::Fail {
3960 failure: FailureDescription::ExitCode { code: 1 },
3961 leaked: true,
3962 },
3963 time_taken: Duration::from_millis(9999),
3964 slow_after: Some(Duration::from_millis(3000)),
3965 },
3966 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3967 Arc::new(std::io::Error::other("exec error")),
3968 ))
3969 .into(),
3970 }),
3971 },
3972 })
3973 .unwrap();
3974
3975 reporter
3977 .write_event(&TestEvent {
3978 timestamp: Local::now().into(),
3979 elapsed: Duration::ZERO,
3980 kind: TestEventKind::InfoResponse {
3981 index: 5,
3982 total: 21,
3983 response: InfoResponse::Test(TestInfoResponse {
3984 stress_index: None,
3985 test_instance: TestInstanceId {
3986 binary_id: &binary_id,
3987 test_name: &test_name1,
3988 },
3989 retry_data: RetryData {
3990 attempt: 1,
3991 total_attempts: 1,
3992 },
3993 state: UnitState::Running {
3994 pid: 12345,
3995 time_taken: Duration::from_millis(400),
3996 slow_after: None,
3997 },
3998 output: make_split_output(None, "abc", "def"),
3999 }),
4000 },
4001 })
4002 .unwrap();
4003
4004 reporter
4006 .write_event(&TestEvent {
4007 timestamp: Local::now().into(),
4008 elapsed: Duration::ZERO,
4009 kind: TestEventKind::InfoResponse {
4010 index: 6,
4011 total: 21,
4012 response: InfoResponse::Test(TestInfoResponse {
4013 stress_index: Some(StressIndex {
4014 current: 0,
4015 total: None,
4016 }),
4017 test_instance: TestInstanceId {
4018 binary_id: &binary_id,
4019 test_name: &test_name2,
4020 },
4021 retry_data: RetryData {
4022 attempt: 2,
4023 total_attempts: 3,
4024 },
4025 state: UnitState::Terminating(UnitTerminatingState {
4026 pid: 12346,
4027 time_taken: Duration::from_millis(99999),
4028 reason: UnitTerminateReason::Timeout,
4029 method: UnitTerminateMethod::Fake,
4030 waiting_duration: Duration::from_millis(6789),
4031 remaining: Duration::from_millis(9786),
4032 }),
4033 output: make_split_output(None, "abc", "def"),
4034 }),
4035 },
4036 })
4037 .unwrap();
4038
4039 reporter
4041 .write_event(&TestEvent {
4042 timestamp: Local::now().into(),
4043 elapsed: Duration::ZERO,
4044 kind: TestEventKind::InfoResponse {
4045 index: 7,
4046 total: 21,
4047 response: InfoResponse::Test(TestInfoResponse {
4048 stress_index: None,
4049 test_instance: TestInstanceId {
4050 binary_id: &binary_id,
4051 test_name: &test_name3,
4052 },
4053 retry_data: RetryData {
4054 attempt: 2,
4055 total_attempts: 3,
4056 },
4057 state: UnitState::Exiting {
4058 pid: 99999,
4059 time_taken: Duration::from_millis(99999),
4060 slow_after: Some(Duration::from_millis(33333)),
4061 tentative_result: None,
4062 waiting_duration: Duration::from_millis(1),
4063 remaining: Duration::from_millis(999),
4064 },
4065 output: make_split_output(None, "abc", "def"),
4066 }),
4067 },
4068 })
4069 .unwrap();
4070
4071 reporter
4073 .write_event(&TestEvent {
4074 timestamp: Local::now().into(),
4075 elapsed: Duration::ZERO,
4076 kind: TestEventKind::InfoResponse {
4077 index: 8,
4078 total: 21,
4079 response: InfoResponse::Test(TestInfoResponse {
4080 stress_index: Some(StressIndex {
4081 current: 1,
4082 total: Some(NonZero::new(3).unwrap()),
4083 }),
4084 test_instance: TestInstanceId {
4085 binary_id: &binary_id,
4086 test_name: &test_name4,
4087 },
4088 retry_data: RetryData {
4089 attempt: 1,
4090 total_attempts: 5,
4091 },
4092 state: UnitState::Exited {
4093 result: ExecutionResultDescription::Pass,
4094 time_taken: Duration::from_millis(99999),
4095 slow_after: Some(Duration::from_millis(33333)),
4096 },
4097 output: make_combined_output_with_errors(
4098 Some(ExecutionResult::Pass),
4099 "abc\ndef\nghi\n",
4100 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
4101 std::io::Error::other("error waiting"),
4102 )))],
4103 ),
4104 }),
4105 },
4106 })
4107 .unwrap();
4108
4109 reporter
4111 .write_event(&TestEvent {
4112 timestamp: Local::now().into(),
4113 elapsed: Duration::ZERO,
4114 kind: TestEventKind::InfoResponse {
4115 index: 9,
4116 total: 21,
4117 response: InfoResponse::Test(TestInfoResponse {
4118 stress_index: None,
4119 test_instance: TestInstanceId {
4120 binary_id: &binary_id,
4121 test_name: &test_name4,
4122 },
4123 retry_data: RetryData {
4124 attempt: 1,
4128 total_attempts: 5,
4129 },
4130 state: UnitState::DelayBeforeNextAttempt {
4131 previous_result: ExecutionResultDescription::ExecFail,
4132 previous_slow: true,
4133 waiting_duration: Duration::from_millis(1234),
4134 remaining: Duration::from_millis(5678),
4135 },
4136 output: make_combined_output_with_errors(
4139 Some(ExecutionResult::Pass),
4140 "*** THIS OUTPUT SHOULD BE IGNORED",
4141 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
4142 std::io::Error::other(
4143 "*** THIS ERROR SHOULD ALSO BE IGNORED",
4144 ),
4145 )))],
4146 ),
4147 }),
4148 },
4149 })
4150 .unwrap();
4151
4152 reporter
4154 .write_event(&TestEvent {
4155 timestamp: Local::now().into(),
4156 elapsed: Duration::ZERO,
4157 kind: TestEventKind::InfoResponse {
4158 index: 10,
4159 total: 21,
4160 response: InfoResponse::Test(TestInfoResponse {
4161 stress_index: None,
4162 test_instance: TestInstanceId {
4163 binary_id: &binary_id,
4164 test_name: &test_name5,
4165 },
4166 retry_data: RetryData {
4167 attempt: 1,
4168 total_attempts: 1,
4169 },
4170 state: UnitState::Exited {
4171 result: ExecutionResultDescription::Fail {
4172 failure: FailureDescription::Abort {
4173 abort: AbortDescription::UnixSignal {
4174 signal: 11,
4175 name: Some("SEGV".into()),
4176 },
4177 },
4178 leaked: true,
4179 },
4180 time_taken: Duration::from_millis(5678),
4181 slow_after: None,
4182 },
4183 output: make_split_output(None, "segfault output", ""),
4184 }),
4185 },
4186 })
4187 .unwrap();
4188
4189 reporter
4190 .write_event(&TestEvent {
4191 timestamp: Local::now().into(),
4192 elapsed: Duration::ZERO,
4193 kind: TestEventKind::InfoFinished { missing: 2 },
4194 })
4195 .unwrap();
4196 },
4197 &mut out,
4198 );
4199
4200 insta::assert_snapshot!("info_response_output", out,);
4201 }
4202
4203 #[test]
4204 fn verbose_command_line() {
4205 let binary_id = RustBinaryId::new("my-binary-id");
4206 let test_name = TestCaseName::new("test_name");
4207 let test_with_spaces = TestCaseName::new("test_with_spaces");
4208 let test_special_chars = TestCaseName::new("test_special_chars");
4209 let test_retry = TestCaseName::new("test_retry");
4210 let mut out = String::new();
4211
4212 with_verbose_reporter(
4213 |mut reporter| {
4214 let current_stats = RunStats {
4215 initial_run_count: 10,
4216 finished_count: 0,
4217 ..Default::default()
4218 };
4219
4220 reporter
4222 .write_event(&TestEvent {
4223 timestamp: Local::now().into(),
4224 elapsed: Duration::ZERO,
4225 kind: TestEventKind::TestStarted {
4226 stress_index: None,
4227 test_instance: TestInstanceId {
4228 binary_id: &binary_id,
4229 test_name: &test_name,
4230 },
4231 slot_assignment: global_slot_assignment(0),
4232 current_stats,
4233 running: 1,
4234 command_line: vec![
4235 "/path/to/binary".to_string(),
4236 "--exact".to_string(),
4237 "test_name".to_string(),
4238 ],
4239 },
4240 })
4241 .unwrap();
4242
4243 reporter
4245 .write_event(&TestEvent {
4246 timestamp: Local::now().into(),
4247 elapsed: Duration::ZERO,
4248 kind: TestEventKind::TestStarted {
4249 stress_index: None,
4250 test_instance: TestInstanceId {
4251 binary_id: &binary_id,
4252 test_name: &test_with_spaces,
4253 },
4254 slot_assignment: global_slot_assignment(1),
4255 current_stats,
4256 running: 2,
4257 command_line: vec![
4258 "/path/to/binary".to_string(),
4259 "--exact".to_string(),
4260 "test with spaces".to_string(),
4261 "--flag=value".to_string(),
4262 ],
4263 },
4264 })
4265 .unwrap();
4266
4267 reporter
4269 .write_event(&TestEvent {
4270 timestamp: Local::now().into(),
4271 elapsed: Duration::ZERO,
4272 kind: TestEventKind::TestStarted {
4273 stress_index: None,
4274 test_instance: TestInstanceId {
4275 binary_id: &binary_id,
4276 test_name: &test_special_chars,
4277 },
4278 slot_assignment: global_slot_assignment(2),
4279 current_stats,
4280 running: 3,
4281 command_line: vec![
4282 "/path/to/binary".to_string(),
4283 "test\"with\"quotes".to_string(),
4284 "test'with'single".to_string(),
4285 ],
4286 },
4287 })
4288 .unwrap();
4289
4290 reporter
4292 .write_event(&TestEvent {
4293 timestamp: Local::now().into(),
4294 elapsed: Duration::ZERO,
4295 kind: TestEventKind::TestRetryStarted {
4296 stress_index: None,
4297 test_instance: TestInstanceId {
4298 binary_id: &binary_id,
4299 test_name: &test_retry,
4300 },
4301 slot_assignment: global_slot_assignment(0),
4302 retry_data: RetryData {
4303 attempt: 2,
4304 total_attempts: 3,
4305 },
4306 running: 1,
4307 command_line: vec![
4308 "/path/to/binary".to_string(),
4309 "--exact".to_string(),
4310 "test_retry".to_string(),
4311 ],
4312 },
4313 })
4314 .unwrap();
4315
4316 reporter
4318 .write_event(&TestEvent {
4319 timestamp: Local::now().into(),
4320 elapsed: Duration::ZERO,
4321 kind: TestEventKind::TestRetryStarted {
4322 stress_index: None,
4323 test_instance: TestInstanceId {
4324 binary_id: &binary_id,
4325 test_name: &test_retry,
4326 },
4327 slot_assignment: global_slot_assignment(0),
4328 retry_data: RetryData {
4329 attempt: 3,
4330 total_attempts: 3,
4331 },
4332 running: 1,
4333 command_line: vec![
4334 "/path/to/binary".to_string(),
4335 "--exact".to_string(),
4336 "test_retry".to_string(),
4337 ],
4338 },
4339 })
4340 .unwrap();
4341 },
4342 &mut out,
4343 );
4344
4345 insta::assert_snapshot!("verbose_command_line", out);
4346 }
4347
4348 #[test]
4349 fn no_capture_settings() {
4350 let mut out = String::new();
4352
4353 with_reporter(
4354 |reporter| {
4355 assert!(reporter.inner.no_capture, "no_capture is true");
4356 let overrides = reporter.inner.unit_output.overrides();
4357 assert_eq!(
4358 overrides.force_failure_output,
4359 Some(TestOutputDisplay::Never),
4360 "failure output is never, overriding other settings"
4361 );
4362 assert_eq!(
4363 overrides.force_success_output,
4364 Some(TestOutputDisplay::Never),
4365 "success output is never, overriding other settings"
4366 );
4367 assert_eq!(
4368 reporter.inner.status_levels.status_level,
4369 StatusLevel::Pass,
4370 "status level is pass, overriding other settings"
4371 );
4372 },
4373 &mut out,
4374 );
4375 }
4376
4377 fn write_test_slow_events<'a>(
4385 reporter: &mut DisplayReporter<'a>,
4386 binary_id: &'a RustBinaryId,
4387 test_name: &'a TestCaseName,
4388 ) {
4389 reporter
4391 .write_event(&TestEvent {
4392 timestamp: Local::now().into(),
4393 elapsed: Duration::ZERO,
4394 kind: TestEventKind::TestSlow {
4395 stress_index: None,
4396 test_instance: TestInstanceId {
4397 binary_id,
4398 test_name,
4399 },
4400 retry_data: RetryData {
4401 attempt: 1,
4402 total_attempts: 1,
4403 },
4404 elapsed: Duration::from_secs(60),
4405 will_terminate: false,
4406 },
4407 })
4408 .unwrap();
4409
4410 reporter
4412 .write_event(&TestEvent {
4413 timestamp: Local::now().into(),
4414 elapsed: Duration::ZERO,
4415 kind: TestEventKind::TestSlow {
4416 stress_index: None,
4417 test_instance: TestInstanceId {
4418 binary_id,
4419 test_name,
4420 },
4421 retry_data: RetryData {
4422 attempt: 1,
4423 total_attempts: 3,
4424 },
4425 elapsed: Duration::from_secs(60),
4426 will_terminate: false,
4427 },
4428 })
4429 .unwrap();
4430
4431 reporter
4433 .write_event(&TestEvent {
4434 timestamp: Local::now().into(),
4435 elapsed: Duration::ZERO,
4436 kind: TestEventKind::TestSlow {
4437 stress_index: None,
4438 test_instance: TestInstanceId {
4439 binary_id,
4440 test_name,
4441 },
4442 retry_data: RetryData {
4443 attempt: 2,
4444 total_attempts: 3,
4445 },
4446 elapsed: Duration::from_secs(60),
4447 will_terminate: false,
4448 },
4449 })
4450 .unwrap();
4451
4452 reporter
4454 .write_event(&TestEvent {
4455 timestamp: Local::now().into(),
4456 elapsed: Duration::ZERO,
4457 kind: TestEventKind::TestSlow {
4458 stress_index: None,
4459 test_instance: TestInstanceId {
4460 binary_id,
4461 test_name,
4462 },
4463 retry_data: RetryData {
4464 attempt: 3,
4465 total_attempts: 3,
4466 },
4467 elapsed: Duration::from_secs(60),
4468 will_terminate: false,
4469 },
4470 })
4471 .unwrap();
4472
4473 reporter
4479 .write_event(&TestEvent {
4480 timestamp: Local::now().into(),
4481 elapsed: Duration::ZERO,
4482 kind: TestEventKind::TestSlow {
4483 stress_index: None,
4484 test_instance: TestInstanceId {
4485 binary_id,
4486 test_name,
4487 },
4488 retry_data: RetryData {
4489 attempt: 1,
4490 total_attempts: 1,
4491 },
4492 elapsed: Duration::from_secs(120),
4493 will_terminate: true,
4494 },
4495 })
4496 .unwrap();
4497
4498 reporter
4501 .write_event(&TestEvent {
4502 timestamp: Local::now().into(),
4503 elapsed: Duration::ZERO,
4504 kind: TestEventKind::TestSlow {
4505 stress_index: None,
4506 test_instance: TestInstanceId {
4507 binary_id,
4508 test_name,
4509 },
4510 retry_data: RetryData {
4511 attempt: 1,
4512 total_attempts: 3,
4513 },
4514 elapsed: Duration::from_secs(120),
4515 will_terminate: true,
4516 },
4517 })
4518 .unwrap();
4519
4520 reporter
4522 .write_event(&TestEvent {
4523 timestamp: Local::now().into(),
4524 elapsed: Duration::ZERO,
4525 kind: TestEventKind::TestSlow {
4526 stress_index: None,
4527 test_instance: TestInstanceId {
4528 binary_id,
4529 test_name,
4530 },
4531 retry_data: RetryData {
4532 attempt: 2,
4533 total_attempts: 3,
4534 },
4535 elapsed: Duration::from_secs(120),
4536 will_terminate: true,
4537 },
4538 })
4539 .unwrap();
4540
4541 reporter
4543 .write_event(&TestEvent {
4544 timestamp: Local::now().into(),
4545 elapsed: Duration::ZERO,
4546 kind: TestEventKind::TestSlow {
4547 stress_index: None,
4548 test_instance: TestInstanceId {
4549 binary_id,
4550 test_name,
4551 },
4552 retry_data: RetryData {
4553 attempt: 3,
4554 total_attempts: 3,
4555 },
4556 elapsed: Duration::from_secs(120),
4557 will_terminate: true,
4558 },
4559 })
4560 .unwrap();
4561 }
4562
4563 fn write_setup_script_slow_events(reporter: &mut DisplayReporter<'_>) {
4569 reporter
4571 .write_event(&TestEvent {
4572 timestamp: Local::now().into(),
4573 elapsed: Duration::ZERO,
4574 kind: TestEventKind::SetupScriptSlow {
4575 stress_index: None,
4576 script_id: ScriptId::new(SmolStr::new("my-script")).unwrap(),
4577 program: "my-program".to_owned(),
4578 args: vec!["--arg1".to_owned()],
4579 elapsed: Duration::from_secs(60),
4580 will_terminate: false,
4581 },
4582 })
4583 .unwrap();
4584
4585 reporter
4587 .write_event(&TestEvent {
4588 timestamp: Local::now().into(),
4589 elapsed: Duration::ZERO,
4590 kind: TestEventKind::SetupScriptSlow {
4591 stress_index: None,
4592 script_id: ScriptId::new(SmolStr::new("my-script")).unwrap(),
4593 program: "my-program".to_owned(),
4594 args: vec!["--arg1".to_owned()],
4595 elapsed: Duration::from_secs(120),
4596 will_terminate: true,
4597 },
4598 })
4599 .unwrap();
4600 }
4601
4602 #[test_case(StatusLevel::None; "none")]
4613 #[test_case(StatusLevel::Fail; "fail")]
4614 #[test_case(StatusLevel::Retry; "retry")]
4615 #[test_case(StatusLevel::Slow; "slow")]
4616 #[test_case(StatusLevel::Pass; "pass")]
4617 fn test_slow_status_levels(status_level: StatusLevel) {
4618 let binary_id = RustBinaryId::new("my-binary-id");
4619 let test_name = TestCaseName::new("test_name");
4620 let mut out = String::new();
4621
4622 with_reporter_at_status_level(
4623 |mut reporter| {
4624 write_test_slow_events(&mut reporter, &binary_id, &test_name);
4625 write_setup_script_slow_events(&mut reporter);
4626 },
4627 &mut out,
4628 status_level,
4629 );
4630
4631 let label = match status_level {
4634 StatusLevel::None => "none",
4635 StatusLevel::Fail => "fail",
4636 StatusLevel::Retry => "retry",
4637 StatusLevel::Slow => "slow",
4638 StatusLevel::Pass => "pass",
4639 _ => unreachable!("test only covers these levels"),
4640 };
4641 insta::assert_snapshot!(format!("test_slow_status_level_{label}"), out);
4642 }
4643
4644 #[test]
4645 fn sort_final_outputs_counter_flag() {
4646 let binary_a = RustBinaryId::new("aaa");
4651 let binary_b = RustBinaryId::new("bbb");
4652 let test_x = TestCaseName::new("test_x");
4653 let test_y = TestCaseName::new("test_y");
4654
4655 let mut entries = vec![
4656 FinalOutputEntry {
4657 stress_index: None,
4658 counter: TestInstanceCounter::Counter {
4659 current: 99,
4660 total: 100,
4661 },
4662 instance: TestInstanceId {
4663 binary_id: &binary_b,
4664 test_name: &test_y,
4665 },
4666 output: make_pass_output(),
4667 },
4668 FinalOutputEntry {
4669 stress_index: None,
4670 counter: TestInstanceCounter::Counter {
4671 current: 1,
4672 total: 100,
4673 },
4674 instance: TestInstanceId {
4675 binary_id: &binary_a,
4676 test_name: &test_y,
4677 },
4678 output: make_pass_output(),
4679 },
4680 FinalOutputEntry {
4681 stress_index: None,
4682 counter: TestInstanceCounter::Counter {
4683 current: 50,
4684 total: 100,
4685 },
4686 instance: TestInstanceId {
4687 binary_id: &binary_a,
4688 test_name: &test_x,
4689 },
4690 output: make_pass_output(),
4691 },
4692 FinalOutputEntry {
4693 stress_index: None,
4694 counter: TestInstanceCounter::Counter {
4695 current: 50,
4696 total: 100,
4697 },
4698 instance: TestInstanceId {
4699 binary_id: &binary_b,
4700 test_name: &test_x,
4701 },
4702 output: make_pass_output(),
4703 },
4704 ];
4705
4706 sort_final_outputs(&mut entries, false);
4708 assert_eq!(
4709 extract_ids(&entries),
4710 vec![
4711 ("aaa", "test_x"),
4712 ("aaa", "test_y"),
4713 ("bbb", "test_x"),
4714 ("bbb", "test_y"),
4715 ],
4716 "without counter, sort is purely by instance"
4717 );
4718
4719 sort_final_outputs(&mut entries, true);
4722 assert_eq!(
4723 extract_ids(&entries),
4724 vec![
4725 ("aaa", "test_y"),
4726 ("aaa", "test_x"),
4727 ("bbb", "test_x"),
4728 ("bbb", "test_y"),
4729 ],
4730 "with counter, sort by counter first, then by instance as tiebreaker"
4731 );
4732 }
4733
4734 #[test]
4735 fn sort_final_outputs_mixed_status_levels() {
4736 let binary_a = RustBinaryId::new("aaa");
4740 let binary_b = RustBinaryId::new("bbb");
4741 let binary_c = RustBinaryId::new("ccc");
4742 let test_1 = TestCaseName::new("test_1");
4743
4744 let mut entries = vec![
4745 FinalOutputEntry {
4746 stress_index: None,
4747 counter: TestInstanceCounter::Counter {
4748 current: 1,
4749 total: 100,
4750 },
4751 instance: TestInstanceId {
4752 binary_id: &binary_a,
4753 test_name: &test_1,
4754 },
4755 output: make_fail_output(),
4756 },
4757 FinalOutputEntry {
4758 stress_index: None,
4759 counter: TestInstanceCounter::Counter {
4760 current: 2,
4761 total: 100,
4762 },
4763 instance: TestInstanceId {
4764 binary_id: &binary_b,
4765 test_name: &test_1,
4766 },
4767 output: make_skip_output(),
4768 },
4769 FinalOutputEntry {
4770 stress_index: None,
4771 counter: TestInstanceCounter::Counter {
4772 current: 3,
4773 total: 100,
4774 },
4775 instance: TestInstanceId {
4776 binary_id: &binary_c,
4777 test_name: &test_1,
4778 },
4779 output: make_pass_output(),
4780 },
4781 ];
4782
4783 sort_final_outputs(&mut entries, false);
4785 assert_eq!(
4786 extract_ids(&entries),
4787 vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4788 "pass first, then skip, then fail (reversed status level)"
4789 );
4790
4791 entries.swap(0, 2);
4794 sort_final_outputs(&mut entries, true);
4795 assert_eq!(
4796 extract_ids(&entries),
4797 vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4798 "with counter, status level still dominates"
4799 );
4800 }
4801
4802 #[test]
4803 fn sort_final_outputs_stress_indexes() {
4804 let binary_a = RustBinaryId::new("aaa");
4806 let test_1 = TestCaseName::new("test_1");
4807 let test_2 = TestCaseName::new("test_2");
4808
4809 let mut entries = vec![
4810 FinalOutputEntry {
4811 stress_index: Some(StressIndex {
4812 current: 2,
4813 total: None,
4814 }),
4815 counter: TestInstanceCounter::Counter {
4816 current: 1,
4817 total: 100,
4818 },
4819 instance: TestInstanceId {
4820 binary_id: &binary_a,
4821 test_name: &test_1,
4822 },
4823 output: make_pass_output(),
4824 },
4825 FinalOutputEntry {
4826 stress_index: Some(StressIndex {
4827 current: 0,
4828 total: None,
4829 }),
4830 counter: TestInstanceCounter::Counter {
4831 current: 3,
4832 total: 100,
4833 },
4834 instance: TestInstanceId {
4835 binary_id: &binary_a,
4836 test_name: &test_2,
4837 },
4838 output: make_pass_output(),
4839 },
4840 FinalOutputEntry {
4841 stress_index: Some(StressIndex {
4842 current: 0,
4843 total: None,
4844 }),
4845 counter: TestInstanceCounter::Counter {
4846 current: 2,
4847 total: 100,
4848 },
4849 instance: TestInstanceId {
4850 binary_id: &binary_a,
4851 test_name: &test_1,
4852 },
4853 output: make_pass_output(),
4854 },
4855 ];
4856
4857 sort_final_outputs(&mut entries, false);
4858 assert_eq!(
4859 extract_ids(&entries),
4860 vec![("aaa", "test_1"), ("aaa", "test_2"), ("aaa", "test_1")],
4861 "stress index 0 entries come before stress index 2"
4862 );
4863 let stress_indexes: Vec<_> = entries
4865 .iter()
4866 .map(|e| e.stress_index.unwrap().current)
4867 .collect();
4868 assert_eq!(
4869 stress_indexes,
4870 vec![0, 0, 2],
4871 "stress indexes are sorted correctly"
4872 );
4873 }
4874}
4875
4876#[cfg(all(windows, test))]
4877mod windows_tests {
4878 use super::*;
4879 use crate::reporter::events::AbortDescription;
4880 use windows_sys::Win32::{
4881 Foundation::{STATUS_CONTROL_C_EXIT, STATUS_CONTROL_STACK_VIOLATION},
4882 Globalization::SetThreadUILanguage,
4883 };
4884
4885 #[test]
4886 fn test_write_windows_abort_line() {
4887 unsafe {
4888 SetThreadUILanguage(0x0409);
4890 }
4891
4892 insta::assert_snapshot!(
4893 "ctrl_c_code",
4894 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_C_EXIT))
4895 );
4896 insta::assert_snapshot!(
4897 "stack_violation_code",
4898 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_STACK_VIOLATION)),
4899 );
4900 insta::assert_snapshot!("job_object", to_abort_line(AbortStatus::JobObject));
4901 }
4902
4903 #[track_caller]
4904 fn to_abort_line(status: AbortStatus) -> String {
4905 let mut buf = String::new();
4906 let description = AbortDescription::from(status);
4907 write_windows_abort_line(&description, &Styles::default(), &mut buf).unwrap();
4908 buf
4909 }
4910}