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