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.get(), 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 } => {
1062 if self.status_levels.status_level >= StatusLevel::Skip {
1063 self.write_skip_line(*stress_index, *test_instance, writer)?;
1064 }
1065 if self.status_levels.final_status_level >= FinalStatusLevel::Skip {
1066 self.final_outputs.push(FinalOutputEntry {
1067 stress_index: *stress_index,
1068 counter: TestInstanceCounter::Padded,
1069 instance: *test_instance,
1070 output: FinalOutput::Skipped(*reason),
1071 });
1072 }
1073 }
1074 TestEventKind::RunBeginCancel {
1075 setup_scripts_running,
1076 current_stats,
1077 running,
1078 } => {
1079 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1080 self.write_run_stop_line(
1081 RunStopKind::Cancel,
1082 current_stats.cancel_reason,
1083 *setup_scripts_running,
1084 *running,
1085 writer,
1086 )?;
1087 }
1088 TestEventKind::RunBeginKill {
1089 setup_scripts_running,
1090 current_stats,
1091 running,
1092 } => {
1093 self.cancel_status = self.cancel_status.max(current_stats.cancel_reason);
1094 self.write_run_stop_line(
1095 RunStopKind::Kill,
1096 current_stats.cancel_reason,
1097 *setup_scripts_running,
1098 *running,
1099 writer,
1100 )?;
1101 }
1102 TestEventKind::RunPaused {
1103 setup_scripts_running,
1104 running,
1105 } => {
1106 write!(
1107 writer,
1108 "{:>12} due to {}",
1109 "Pausing".style(self.styles.pass),
1110 "signal".style(self.styles.count)
1111 )?;
1112
1113 if *setup_scripts_running > 0 {
1115 let s = plural::setup_scripts_str(*setup_scripts_running);
1116 write!(
1117 writer,
1118 ": {} {s} running",
1119 setup_scripts_running.style(self.styles.count),
1120 )?;
1121 } else if *running > 0 {
1122 let tests_str = plural::tests_str(self.mode, *running);
1123 write!(
1124 writer,
1125 ": {} {tests_str} running",
1126 running.style(self.styles.count),
1127 )?;
1128 }
1129 writeln!(writer)?;
1130 }
1131 TestEventKind::RunContinued {
1132 setup_scripts_running,
1133 running,
1134 } => {
1135 write!(
1136 writer,
1137 "{:>12} due to {}",
1138 "Continuing".style(self.styles.pass),
1139 "signal".style(self.styles.count)
1140 )?;
1141
1142 if *setup_scripts_running > 0 {
1144 let s = plural::setup_scripts_str(*setup_scripts_running);
1145 write!(
1146 writer,
1147 ": {} {s} running",
1148 setup_scripts_running.style(self.styles.count),
1149 )?;
1150 } else if *running > 0 {
1151 let tests_str = plural::tests_str(self.mode, *running);
1152 write!(
1153 writer,
1154 ": {} {tests_str} running",
1155 running.style(self.styles.count),
1156 )?;
1157 }
1158 writeln!(writer)?;
1159 }
1160 TestEventKind::InfoStarted { total, run_stats } => {
1161 let info_style = if run_stats.has_failures() {
1162 self.styles.fail
1163 } else {
1164 self.styles.pass
1165 };
1166
1167 let hbar = self.theme_characters.hbar(12);
1168
1169 write!(writer, "{hbar}\n{}: ", "info".style(info_style))?;
1170
1171 writeln!(
1173 writer,
1174 "{} in {:.3?}s",
1175 progress_bar_msg(run_stats, *total, &self.styles),
1181 event.elapsed.as_secs_f64(),
1182 )?;
1183 }
1184 TestEventKind::InfoResponse {
1185 index,
1186 total,
1187 response,
1188 } => {
1189 self.write_info_response(*index, *total, response, writer)?;
1190 }
1191 TestEventKind::InfoFinished { missing } => {
1192 let hbar = self.theme_characters.hbar(12);
1193
1194 if *missing > 0 {
1195 writeln!(
1198 writer,
1199 "{}: missing {} responses",
1200 "info".style(self.styles.skip),
1201 missing.style(self.styles.count)
1202 )?;
1203 }
1204
1205 writeln!(writer, "{hbar}")?;
1206 }
1207 TestEventKind::InputEnter {
1208 current_stats,
1209 running,
1210 } => {
1211 writeln!(
1214 writer,
1215 "{}",
1216 progress_str(event.elapsed, current_stats, *running, &self.styles)
1217 )?;
1218 }
1219 TestEventKind::StressSubRunFinished {
1220 progress,
1221 sub_elapsed,
1222 sub_stats,
1223 } => {
1224 let stats_summary = sub_stats.summarize_final();
1225 let summary_style = match stats_summary {
1226 FinalRunStats::Success => self.styles.pass,
1227 FinalRunStats::NoTestsRun => self.styles.skip,
1228 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1229 self.styles.fail
1230 }
1231 };
1232
1233 write!(
1234 writer,
1235 "{:>12} {}",
1236 "Stress test".style(summary_style),
1237 DisplayBracketedDuration(*sub_elapsed),
1238 )?;
1239 match progress {
1240 StressProgress::Count {
1241 total: StressCount::Count { count },
1242 elapsed: _,
1243 completed,
1244 } => {
1245 write!(
1246 writer,
1247 "iteration {}/{}: ",
1248 completed.style(self.styles.count),
1252 count.style(self.styles.count),
1253 )?;
1254 }
1255 StressProgress::Count {
1256 total: StressCount::Infinite,
1257 elapsed: _,
1258 completed,
1259 } => {
1260 write!(
1261 writer,
1262 "iteration {}: ",
1263 completed.style(self.styles.count),
1267 )?;
1268 }
1269 StressProgress::Time {
1270 total: _,
1271 elapsed: _,
1272 completed,
1273 } => {
1274 write!(
1275 writer,
1276 "iteration {}: ",
1277 completed.style(self.styles.count),
1281 )?;
1282 }
1283 }
1284
1285 write!(
1286 writer,
1287 "{}",
1288 sub_stats.finished_count.style(self.styles.count)
1289 )?;
1290 if sub_stats.finished_count != sub_stats.initial_run_count {
1291 write!(
1292 writer,
1293 "/{}",
1294 sub_stats.initial_run_count.style(self.styles.count)
1295 )?;
1296 }
1297
1298 let tests_str = plural::tests_plural_if(
1300 self.mode,
1301 sub_stats.initial_run_count != 1 || sub_stats.finished_count != 1,
1302 );
1303
1304 let mut summary_str = String::new();
1305 write_summary_str(sub_stats, &self.styles, &mut summary_str);
1306 writeln!(writer, " {tests_str} run: {summary_str}")?;
1307 }
1308 TestEventKind::RunFinished {
1309 start_time: _start_time,
1310 elapsed,
1311 run_stats,
1312 outstanding_not_seen: tests_not_seen,
1313 ..
1314 } => {
1315 match run_stats {
1316 RunFinishedStats::Single(run_stats) => {
1317 let stats_summary = run_stats.summarize_final();
1318 let summary_style = match stats_summary {
1319 FinalRunStats::Success => self.styles.pass,
1320 FinalRunStats::NoTestsRun => self.styles.skip,
1321 FinalRunStats::Failed { .. } | FinalRunStats::Cancelled { .. } => {
1322 self.styles.fail
1323 }
1324 };
1325 write!(
1326 writer,
1327 "{}\n{:>12} ",
1328 self.theme_characters.hbar(12),
1329 "Summary".style(summary_style)
1330 )?;
1331
1332 write!(writer, "[{:>8.3?}s] ", elapsed.as_secs_f64())?;
1337
1338 write!(
1339 writer,
1340 "{}",
1341 run_stats.finished_count.style(self.styles.count)
1342 )?;
1343 if run_stats.finished_count != run_stats.initial_run_count {
1344 write!(
1345 writer,
1346 "/{}",
1347 run_stats.initial_run_count.style(self.styles.count)
1348 )?;
1349 }
1350
1351 let tests_str = plural::tests_plural_if(
1353 self.mode,
1354 run_stats.initial_run_count != 1 || run_stats.finished_count != 1,
1355 );
1356
1357 let mut summary_str = String::new();
1358 write_summary_str(run_stats, &self.styles, &mut summary_str);
1359 writeln!(writer, " {tests_str} run: {summary_str}")?;
1360 }
1361 RunFinishedStats::Stress(stats) => {
1362 let stats_summary = stats.summarize_final();
1363 let summary_style = match stats_summary {
1364 StressFinalRunStats::Success => self.styles.pass,
1365 StressFinalRunStats::NoTestsRun => self.styles.skip,
1366 StressFinalRunStats::Cancelled | StressFinalRunStats::Failed => {
1367 self.styles.fail
1368 }
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 stats.completed.current.style(self.styles.count),
1388 )?;
1389 let iterations_str = if let Some(total) = stats.completed.total {
1390 write!(writer, "/{}", total.style(self.styles.count))?;
1391 plural::iterations_str(total.get())
1392 } else {
1393 plural::iterations_str(stats.completed.current)
1394 };
1395 write!(
1396 writer,
1397 " stress run {iterations_str}: {} {}",
1398 stats.success_count.style(self.styles.count),
1399 "passed".style(self.styles.pass),
1400 )?;
1401 if stats.failed_count > 0 {
1402 write!(
1403 writer,
1404 ", {} {}",
1405 stats.failed_count.style(self.styles.count),
1406 "failed".style(self.styles.fail),
1407 )?;
1408 }
1409
1410 match stats.last_final_stats {
1411 FinalRunStats::Cancelled { reason, kind: _ } => {
1412 if let Some(reason) = reason {
1413 write!(
1414 writer,
1415 "; cancelled due to {}",
1416 reason.to_static_str().style(self.styles.fail),
1417 )?;
1418 }
1419 }
1420 FinalRunStats::Failed { .. }
1421 | FinalRunStats::Success
1422 | FinalRunStats::NoTestsRun => {}
1423 }
1424
1425 writeln!(writer)?;
1426 }
1427 }
1428
1429 if self.cancel_status < Some(CancelReason::Interrupt) {
1432 sort_final_outputs(&mut self.final_outputs, self.counter_width.is_some());
1434
1435 for entry in &*self.final_outputs {
1436 match &entry.output {
1437 FinalOutput::Skipped(_) => {
1438 self.write_skip_line(entry.stress_index, entry.instance, writer)?;
1439 }
1440 FinalOutput::Executed {
1441 run_statuses,
1442 display_output,
1443 } => {
1444 let last_status = run_statuses.last_status();
1445
1446 self.write_final_status_line(
1447 entry.stress_index,
1448 entry.counter,
1449 entry.instance,
1450 run_statuses.describe(),
1451 writer,
1452 )?;
1453 if *display_output {
1454 self.write_test_execute_status(last_status, false, writer)?;
1455 }
1456 }
1457 }
1458 }
1459 }
1460
1461 if let Some(not_seen) = tests_not_seen
1462 && not_seen.total_not_seen > 0
1463 {
1464 writeln!(
1465 writer,
1466 "{:>12} {} outstanding {} not seen during this rerun:",
1467 "Note".style(self.styles.skip),
1468 not_seen.total_not_seen.style(self.styles.count),
1469 plural::tests_str(self.mode, not_seen.total_not_seen),
1470 )?;
1471
1472 for t in ¬_seen.not_seen {
1473 let display = DisplayTestInstance::new(
1474 None,
1475 None,
1476 t.as_ref(),
1477 &self.styles.list_styles,
1478 );
1479 writeln!(writer, " {}", display)?;
1480 }
1481
1482 let remaining = not_seen
1483 .total_not_seen
1484 .saturating_sub(not_seen.not_seen.len());
1485 if remaining > 0 {
1486 writeln!(
1487 writer,
1488 " ... and {} more {}",
1489 remaining.style(self.styles.count),
1490 plural::tests_str(self.mode, remaining),
1491 )?;
1492 }
1493 }
1494
1495 write_final_warnings(self.mode, run_stats.final_stats(), &self.styles, writer)?;
1497 }
1498 }
1499
1500 Ok(())
1501 }
1502
1503 fn write_run_stop_line(
1504 &self,
1505 kind: RunStopKind,
1506 cancel_reason: Option<CancelReason>,
1507 setup_scripts_running: usize,
1508 running: usize,
1509 writer: &mut dyn WriteStr,
1510 ) -> io::Result<()> {
1511 let (verb, immediately_terminating) = match kind {
1512 RunStopKind::Cancel => (
1513 "Cancelling",
1514 cancel_reason == Some(CancelReason::TestFailureImmediate),
1515 ),
1516 RunStopKind::Kill => ("Killing", false),
1517 };
1518
1519 write!(writer, "{:>12}", verb.style(self.styles.fail))?;
1520 if let Some(reason) = cancel_reason {
1521 write!(
1522 writer,
1523 " due to {}",
1524 reason.to_static_str().style(self.styles.fail)
1525 )?;
1526 }
1527
1528 let still_running = if setup_scripts_running > 0 {
1530 Some((
1531 setup_scripts_running,
1532 plural::setup_scripts_str(setup_scripts_running),
1533 ))
1534 } else if running > 0 {
1535 Some((running, plural::tests_str(self.mode, running)))
1536 } else {
1537 None
1538 };
1539
1540 if let Some((count, units_str)) = still_running {
1541 write!(writer, ": ")?;
1542 if immediately_terminating {
1543 write!(
1544 writer,
1545 "immediately {} ",
1546 "terminating".style(self.styles.fail)
1547 )?;
1548 }
1549 write!(
1550 writer,
1551 "{} {units_str} still running",
1552 count.style(self.styles.count)
1553 )?;
1554 }
1555 writeln!(writer)
1556 }
1557
1558 fn write_skip_line(
1559 &self,
1560 stress_index: Option<StressIndex>,
1561 test_instance: TestInstanceId<'a>,
1562 writer: &mut dyn WriteStr,
1563 ) -> io::Result<()> {
1564 write!(writer, "{:>12} ", "SKIP".style(self.styles.skip))?;
1565 writeln!(
1567 writer,
1568 "[ ] {}",
1569 self.display_test_instance(stress_index, TestInstanceCounter::Padded, test_instance)
1570 )?;
1571
1572 Ok(())
1573 }
1574
1575 fn write_setup_script_status_line(
1576 &self,
1577 stress_index: Option<StressIndex>,
1578 script_id: &ScriptId,
1579 command: &str,
1580 args: &[String],
1581 status: &SetupScriptExecuteStatus<LiveSpec>,
1582 writer: &mut dyn WriteStr,
1583 ) -> io::Result<()> {
1584 match status.result {
1585 ExecutionResultDescription::Pass => {
1586 write!(writer, "{:>12} ", "SETUP PASS".style(self.styles.pass))?;
1587 }
1588 ExecutionResultDescription::Leak { result } => match result {
1589 LeakTimeoutResult::Pass => {
1590 write!(writer, "{:>12} ", "SETUP LEAK".style(self.styles.skip))?;
1591 }
1592 LeakTimeoutResult::Fail => {
1593 write!(writer, "{:>12} ", "SETUP LKFAIL".style(self.styles.fail))?;
1594 }
1595 },
1596 ref other => {
1597 let status_str = short_status_str(other);
1598 write!(
1599 writer,
1600 "{:>12} ",
1601 format!("SETUP {status_str}").style(self.styles.fail),
1602 )?;
1603 }
1604 }
1605
1606 writeln!(
1607 writer,
1608 "{}{}",
1609 DisplayBracketedDuration(status.time_taken),
1610 self.display_script_instance(stress_index, script_id.clone(), command, args)
1611 )?;
1612
1613 Ok(())
1614 }
1615
1616 fn write_status_line(
1617 &self,
1618 stress_index: Option<StressIndex>,
1619 counter: TestInstanceCounter,
1620 test_instance: TestInstanceId<'a>,
1621 describe: ExecutionDescription<'_, LiveSpec>,
1622 writer: &mut dyn WriteStr,
1623 ) -> io::Result<()> {
1624 self.write_status_line_impl(
1625 stress_index,
1626 counter,
1627 test_instance,
1628 describe,
1629 StatusLineKind::Intermediate,
1630 writer,
1631 )
1632 }
1633
1634 fn write_final_status_line(
1635 &self,
1636 stress_index: Option<StressIndex>,
1637 counter: TestInstanceCounter,
1638 test_instance: TestInstanceId<'a>,
1639 describe: ExecutionDescription<'_, LiveSpec>,
1640 writer: &mut dyn WriteStr,
1641 ) -> io::Result<()> {
1642 self.write_status_line_impl(
1643 stress_index,
1644 counter,
1645 test_instance,
1646 describe,
1647 StatusLineKind::Final,
1648 writer,
1649 )
1650 }
1651
1652 fn write_status_line_impl(
1653 &self,
1654 stress_index: Option<StressIndex>,
1655 counter: TestInstanceCounter,
1656 test_instance: TestInstanceId<'a>,
1657 describe: ExecutionDescription<'_, LiveSpec>,
1658 kind: StatusLineKind,
1659 writer: &mut dyn WriteStr,
1660 ) -> io::Result<()> {
1661 let last_status = describe.last_status();
1662
1663 self.write_status_line_prefix(describe, kind, writer)?;
1665
1666 writeln!(
1668 writer,
1669 "{}{}",
1670 DisplayBracketedDuration(last_status.time_taken),
1671 self.display_test_instance(stress_index, counter, test_instance),
1672 )?;
1673
1674 if let ExecutionResultDescription::Fail {
1676 failure: FailureDescription::Abort { ref abort },
1677 leaked: _,
1678 } = last_status.result
1679 {
1680 write_windows_abort_line(abort, &self.styles, writer)?;
1681 }
1682
1683 if kind == StatusLineKind::Intermediate
1687 && let ExecutionDescription::Flaky {
1688 result: FlakyResult::Fail,
1689 ..
1690 } = describe
1691 {
1692 writeln!(
1693 writer,
1694 "{:>12} test configured to {} if flaky",
1695 "-",
1696 "fail".style(self.styles.fail),
1697 )?;
1698 }
1699
1700 Ok(())
1701 }
1702
1703 fn write_status_line_prefix(
1704 &self,
1705 describe: ExecutionDescription<'_, LiveSpec>,
1706 kind: StatusLineKind,
1707 writer: &mut dyn WriteStr,
1708 ) -> io::Result<()> {
1709 let last_status = describe.last_status();
1710 match describe {
1711 ExecutionDescription::Success { .. } => {
1712 match (kind, last_status.is_slow, &last_status.result) {
1716 (StatusLineKind::Final, true, ExecutionResultDescription::Pass) => {
1718 write!(writer, "{:>12} ", "SLOW".style(self.styles.skip))?;
1719 }
1720 (
1721 StatusLineKind::Final,
1722 true,
1723 ExecutionResultDescription::Leak {
1724 result: LeakTimeoutResult::Pass,
1725 },
1726 ) => {
1727 write!(writer, "{:>12} ", "SLOW + LEAK".style(self.styles.skip))?;
1728 }
1729 (
1730 StatusLineKind::Final,
1731 true,
1732 ExecutionResultDescription::Timeout {
1733 result: SlowTimeoutResult::Pass,
1734 },
1735 ) => {
1736 write!(writer, "{:>12} ", "SLOW+TMPASS".style(self.styles.skip))?;
1737 }
1738 (_, _, ExecutionResultDescription::Pass) => {
1740 write!(writer, "{:>12} ", "PASS".style(self.styles.pass))?;
1741 }
1742 (
1743 _,
1744 _,
1745 ExecutionResultDescription::Leak {
1746 result: LeakTimeoutResult::Pass,
1747 },
1748 ) => {
1749 write!(writer, "{:>12} ", "LEAK".style(self.styles.skip))?;
1750 }
1751 (
1752 _,
1753 _,
1754 ExecutionResultDescription::Timeout {
1755 result: SlowTimeoutResult::Pass,
1756 },
1757 ) => {
1758 write!(writer, "{:>12} ", "TIMEOUT-PASS".style(self.styles.skip))?;
1759 }
1760 (
1762 _,
1763 _,
1764 ExecutionResultDescription::Leak {
1765 result: LeakTimeoutResult::Fail,
1766 },
1767 )
1768 | (
1769 _,
1770 _,
1771 ExecutionResultDescription::Timeout {
1772 result: SlowTimeoutResult::Fail,
1773 },
1774 )
1775 | (_, _, ExecutionResultDescription::Fail { .. })
1776 | (_, _, ExecutionResultDescription::ExecFail) => {
1777 unreachable!(
1778 "success description cannot have failure result: {:?}",
1779 last_status.result
1780 )
1781 }
1782 }
1783 }
1784 ExecutionDescription::Flaky {
1785 result: FlakyResult::Pass,
1786 ..
1787 } => {
1788 let status = match kind {
1790 StatusLineKind::Intermediate => {
1791 format!("TRY {} PASS", last_status.retry_data.attempt)
1792 }
1793 StatusLineKind::Final => {
1794 format!(
1795 "FLAKY {}/{}",
1796 last_status.retry_data.attempt, last_status.retry_data.total_attempts
1797 )
1798 }
1799 };
1800 write!(writer, "{:>12} ", status.style(self.styles.skip))?;
1801 }
1802 ExecutionDescription::Flaky {
1803 result: FlakyResult::Fail,
1804 ..
1805 } => {
1806 let status = match kind {
1808 StatusLineKind::Intermediate => {
1809 format!("TRY {} PASS", last_status.retry_data.attempt)
1810 }
1811 StatusLineKind::Final => {
1812 format!(
1813 "FLKY-FL {}/{}",
1814 last_status.retry_data.attempt, last_status.retry_data.total_attempts
1815 )
1816 }
1817 };
1818 write!(writer, "{:>12} ", status.style(self.styles.fail))?;
1819 }
1820 ExecutionDescription::Failure { .. } => {
1821 if last_status.retry_data.attempt == 1 {
1822 write!(
1823 writer,
1824 "{:>12} ",
1825 status_str(&last_status.result).style(self.styles.fail)
1826 )?;
1827 } else {
1828 let status_str = short_status_str(&last_status.result);
1829 write!(
1830 writer,
1831 "{:>12} ",
1832 format!("TRY {} {}", last_status.retry_data.attempt, status_str)
1833 .style(self.styles.fail)
1834 )?;
1835 }
1836 }
1837 }
1838 Ok(())
1839 }
1840
1841 fn display_test_instance(
1842 &self,
1843 stress_index: Option<StressIndex>,
1844 counter: TestInstanceCounter,
1845 instance: TestInstanceId<'a>,
1846 ) -> DisplayTestInstance<'_> {
1847 let counter_index = match (counter, self.counter_width) {
1848 (TestInstanceCounter::Counter { current, total }, Some(_)) => {
1849 Some(DisplayCounterIndex::new_counter(current, total))
1850 }
1851 (TestInstanceCounter::Padded, Some(counter_width)) => Some(
1852 DisplayCounterIndex::new_padded(self.theme_characters.hbar_char(), counter_width),
1853 ),
1854 (TestInstanceCounter::None, _) | (_, None) => None,
1855 };
1856
1857 DisplayTestInstance::new(
1858 stress_index,
1859 counter_index,
1860 instance,
1861 &self.styles.list_styles,
1862 )
1863 }
1864
1865 fn write_command_line(
1866 &self,
1867 command_line: &[String],
1868 writer: &mut dyn WriteStr,
1869 ) -> io::Result<()> {
1870 writeln!(
1872 writer,
1873 "{:>20}: {}",
1874 "command".style(self.styles.count),
1875 shell_words::join(command_line),
1876 )
1877 }
1878
1879 fn display_script_instance(
1880 &self,
1881 stress_index: Option<StressIndex>,
1882 script_id: ScriptId,
1883 command: &str,
1884 args: &[String],
1885 ) -> DisplayScriptInstance {
1886 DisplayScriptInstance::new(
1887 stress_index,
1888 script_id,
1889 command,
1890 args,
1891 self.styles.script_id,
1892 self.styles.count,
1893 )
1894 }
1895
1896 fn write_info_response(
1897 &self,
1898 index: usize,
1899 total: usize,
1900 response: &InfoResponse<'_>,
1901 writer: &mut dyn WriteStr,
1902 ) -> io::Result<()> {
1903 if index > 0 {
1904 writeln!(writer, "{}", self.theme_characters.hbar(8))?;
1907 }
1908
1909 let count_width = decimal_char_width(index + 1) + decimal_char_width(total) + 3;
1915 let padding = 8usize.saturating_sub(count_width);
1916
1917 write!(
1918 writer,
1919 "\n* {}/{}: {:padding$}",
1920 (index + 1).style(self.styles.count),
1922 total.style(self.styles.count),
1923 "",
1924 )?;
1925
1926 let mut writer = indented(writer).with_str(" ").skip_initial();
1929
1930 match response {
1931 InfoResponse::SetupScript(SetupScriptInfoResponse {
1932 stress_index,
1933 script_id,
1934 program,
1935 args,
1936 state,
1937 output,
1938 }) => {
1939 writeln!(
1941 writer,
1942 "{}",
1943 self.display_script_instance(*stress_index, script_id.clone(), program, args)
1944 )?;
1945
1946 self.write_unit_state(
1948 UnitKind::Script,
1949 "",
1950 state,
1951 output.has_errors(),
1952 &mut writer,
1953 )?;
1954
1955 if state.has_valid_output() {
1957 self.unit_output.write_child_execution_output(
1958 &self.styles,
1959 &self.output_spec_for_info(UnitKind::Script),
1960 output,
1961 &mut writer,
1962 )?;
1963 }
1964 }
1965 InfoResponse::Test(TestInfoResponse {
1966 stress_index,
1967 test_instance,
1968 retry_data,
1969 state,
1970 output,
1971 }) => {
1972 writeln!(
1974 writer,
1975 "{}",
1976 self.display_test_instance(
1977 *stress_index,
1978 TestInstanceCounter::None,
1979 *test_instance
1980 )
1981 )?;
1982
1983 let show_attempt_str = (retry_data.attempt > 1 && retry_data.total_attempts > 1)
1987 || matches!(state, UnitState::DelayBeforeNextAttempt { .. });
1988 let attempt_str = if show_attempt_str {
1989 format!(
1990 "(attempt {}/{}) ",
1991 retry_data.attempt, retry_data.total_attempts
1992 )
1993 } else {
1994 String::new()
1995 };
1996
1997 self.write_unit_state(
1999 UnitKind::Test,
2000 &attempt_str,
2001 state,
2002 output.has_errors(),
2003 &mut writer,
2004 )?;
2005
2006 if state.has_valid_output() {
2008 self.unit_output.write_child_execution_output(
2009 &self.styles,
2010 &self.output_spec_for_info(UnitKind::Test),
2011 output,
2012 &mut writer,
2013 )?;
2014 }
2015 }
2016 }
2017
2018 writer.write_str_flush()?;
2019 let inner_writer = writer.into_inner();
2020
2021 writeln!(inner_writer)?;
2023
2024 Ok(())
2025 }
2026
2027 fn write_unit_state(
2028 &self,
2029 kind: UnitKind,
2030 attempt_str: &str,
2031 state: &UnitState,
2032 output_has_errors: bool,
2033 writer: &mut dyn WriteStr,
2034 ) -> io::Result<()> {
2035 let status_str = "status".style(self.styles.count);
2036 match state {
2037 UnitState::Running {
2038 pid,
2039 time_taken,
2040 slow_after,
2041 } => {
2042 let running_style = if output_has_errors {
2043 self.styles.fail
2044 } else if slow_after.is_some() {
2045 self.styles.skip
2046 } else {
2047 self.styles.pass
2048 };
2049 write!(
2050 writer,
2051 "{status_str}: {attempt_str}{} {} for {:.3?}s as PID {}",
2052 DisplayUnitKind::new(self.mode, kind),
2053 "running".style(running_style),
2054 time_taken.as_secs_f64(),
2055 pid.style(self.styles.count),
2056 )?;
2057 if let Some(slow_after) = slow_after {
2058 write!(
2059 writer,
2060 " (marked slow after {:.3?}s)",
2061 slow_after.as_secs_f64()
2062 )?;
2063 }
2064 writeln!(writer)?;
2065 }
2066 UnitState::Exiting {
2067 pid,
2068 time_taken,
2069 slow_after,
2070 tentative_result,
2071 waiting_duration,
2072 remaining,
2073 } => {
2074 write!(
2075 writer,
2076 "{status_str}: {attempt_str}{} ",
2077 DisplayUnitKind::new(self.mode, kind)
2078 )?;
2079
2080 self.write_info_execution_result(
2081 tentative_result.as_ref(),
2082 slow_after.is_some(),
2083 writer,
2084 )?;
2085 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2086 if let Some(slow_after) = slow_after {
2087 write!(
2088 writer,
2089 " (marked slow after {:.3?}s)",
2090 slow_after.as_secs_f64()
2091 )?;
2092 }
2093 writeln!(writer)?;
2094
2095 if *waiting_duration >= Duration::from_secs(1) {
2098 writeln!(
2099 writer,
2100 "{}: spent {:.3?}s waiting for {} PID {} to shut down, \
2101 will mark as leaky after another {:.3?}s",
2102 "note".style(self.styles.count),
2103 waiting_duration.as_secs_f64(),
2104 DisplayUnitKind::new(self.mode, kind),
2105 pid.style(self.styles.count),
2106 remaining.as_secs_f64(),
2107 )?;
2108 }
2109 }
2110 UnitState::Terminating(state) => {
2111 self.write_terminating_state(kind, attempt_str, state, writer)?;
2112 }
2113 UnitState::Exited {
2114 result,
2115 time_taken,
2116 slow_after,
2117 } => {
2118 write!(
2119 writer,
2120 "{status_str}: {attempt_str}{} ",
2121 DisplayUnitKind::new(self.mode, kind)
2122 )?;
2123 self.write_info_execution_result(Some(result), slow_after.is_some(), writer)?;
2124 write!(writer, " after {:.3?}s", time_taken.as_secs_f64())?;
2125 if let Some(slow_after) = slow_after {
2126 write!(
2127 writer,
2128 " (marked slow after {:.3?}s)",
2129 slow_after.as_secs_f64()
2130 )?;
2131 }
2132 writeln!(writer)?;
2133 }
2134 UnitState::DelayBeforeNextAttempt {
2135 previous_result,
2136 previous_slow,
2137 waiting_duration,
2138 remaining,
2139 } => {
2140 write!(
2141 writer,
2142 "{status_str}: {attempt_str}{} ",
2143 DisplayUnitKind::new(self.mode, kind)
2144 )?;
2145 self.write_info_execution_result(Some(previous_result), *previous_slow, writer)?;
2146 writeln!(
2147 writer,
2148 ", currently {} before next attempt",
2149 "waiting".style(self.styles.count)
2150 )?;
2151 writeln!(
2152 writer,
2153 "{}: waited {:.3?}s so far, will wait another {:.3?}s before retrying {}",
2154 "note".style(self.styles.count),
2155 waiting_duration.as_secs_f64(),
2156 remaining.as_secs_f64(),
2157 DisplayUnitKind::new(self.mode, kind),
2158 )?;
2159 }
2160 }
2161
2162 Ok(())
2163 }
2164
2165 fn write_terminating_state(
2166 &self,
2167 kind: UnitKind,
2168 attempt_str: &str,
2169 state: &UnitTerminatingState,
2170 writer: &mut dyn WriteStr,
2171 ) -> io::Result<()> {
2172 let UnitTerminatingState {
2173 pid,
2174 time_taken,
2175 reason,
2176 method,
2177 waiting_duration,
2178 remaining,
2179 } = state;
2180
2181 writeln!(
2182 writer,
2183 "{}: {attempt_str}{} {} PID {} due to {} ({} ran for {:.3?}s)",
2184 "status".style(self.styles.count),
2185 "terminating".style(self.styles.fail),
2186 DisplayUnitKind::new(self.mode, kind),
2187 pid.style(self.styles.count),
2188 reason.style(self.styles.count),
2189 DisplayUnitKind::new(self.mode, kind),
2190 time_taken.as_secs_f64(),
2191 )?;
2192
2193 match method {
2194 #[cfg(unix)]
2195 UnitTerminateMethod::Signal(signal) => {
2196 writeln!(
2197 writer,
2198 "{}: sent {} to process group; spent {:.3?}s waiting for {} to exit, \
2199 will SIGKILL after another {:.3?}s",
2200 "note".style(self.styles.count),
2201 signal,
2202 waiting_duration.as_secs_f64(),
2203 DisplayUnitKind::new(self.mode, kind),
2204 remaining.as_secs_f64(),
2205 )?;
2206 }
2207 #[cfg(windows)]
2208 UnitTerminateMethod::JobObject => {
2209 writeln!(
2210 writer,
2211 "{}: instructed job object to terminate",
2215 "note".style(self.styles.count),
2216 )?;
2217 }
2218 #[cfg(windows)]
2219 UnitTerminateMethod::Wait => {
2220 writeln!(
2221 writer,
2222 "{}: waiting for {} to exit on its own; spent {:.3?}s, will terminate \
2223 job object after another {:.3?}s",
2224 "note".style(self.styles.count),
2225 DisplayUnitKind::new(self.mode, kind),
2226 waiting_duration.as_secs_f64(),
2227 remaining.as_secs_f64(),
2228 )?;
2229 }
2230 #[cfg(test)]
2231 UnitTerminateMethod::Fake => {
2232 writeln!(
2234 writer,
2235 "{}: fake termination method; spent {:.3?}s waiting for {} to exit, \
2236 will kill after another {:.3?}s",
2237 "note".style(self.styles.count),
2238 waiting_duration.as_secs_f64(),
2239 DisplayUnitKind::new(self.mode, kind),
2240 remaining.as_secs_f64(),
2241 )?;
2242 }
2243 }
2244
2245 Ok(())
2246 }
2247
2248 fn write_info_execution_result(
2252 &self,
2253 result: Option<&ExecutionResultDescription>,
2254 is_slow: bool,
2255 writer: &mut dyn WriteStr,
2256 ) -> io::Result<()> {
2257 match result {
2258 Some(ExecutionResultDescription::Pass) => {
2259 let style = if is_slow {
2260 self.styles.skip
2261 } else {
2262 self.styles.pass
2263 };
2264
2265 write!(writer, "{}", "passed".style(style))
2266 }
2267 Some(ExecutionResultDescription::Leak {
2268 result: LeakTimeoutResult::Pass,
2269 }) => write!(
2270 writer,
2271 "{}",
2272 "passed with leaked handles".style(self.styles.skip)
2273 ),
2274 Some(ExecutionResultDescription::Leak {
2275 result: LeakTimeoutResult::Fail,
2276 }) => write!(
2277 writer,
2278 "{}: exited with code 0, but leaked handles",
2279 "failed".style(self.styles.fail),
2280 ),
2281 Some(ExecutionResultDescription::Timeout {
2282 result: SlowTimeoutResult::Pass,
2283 }) => {
2284 write!(writer, "{}", "passed with timeout".style(self.styles.skip))
2285 }
2286 Some(ExecutionResultDescription::Timeout {
2287 result: SlowTimeoutResult::Fail,
2288 }) => {
2289 write!(writer, "{}", "timed out".style(self.styles.fail))
2290 }
2291 Some(ExecutionResultDescription::Fail {
2292 failure: FailureDescription::Abort { abort },
2293 leaked,
2294 }) => {
2295 write!(writer, "{}", "aborted".style(self.styles.fail))?;
2297 if let AbortDescription::UnixSignal { signal, name } = abort {
2301 write!(writer, " with signal {}", signal.style(self.styles.count))?;
2302 if let Some(s) = name {
2303 write!(writer, ": SIG{s}")?;
2304 }
2305 }
2306 if *leaked {
2307 write!(writer, " (leaked handles)")?;
2308 }
2309 Ok(())
2310 }
2311 Some(ExecutionResultDescription::Fail {
2312 failure: FailureDescription::ExitCode { code },
2313 leaked,
2314 }) => {
2315 write!(
2316 writer,
2317 "{} with exit code {}",
2318 "failed".style(self.styles.fail),
2319 code.style(self.styles.count),
2320 )?;
2321 if *leaked {
2322 write!(writer, " (leaked handles)")?;
2323 }
2324 Ok(())
2325 }
2326 Some(ExecutionResultDescription::ExecFail) => {
2327 write!(writer, "{}", "failed to execute".style(self.styles.fail))
2328 }
2329 None => {
2330 write!(
2331 writer,
2332 "{} with unknown status",
2333 "failed".style(self.styles.fail)
2334 )
2335 }
2336 }
2337 }
2338
2339 fn write_setup_script_execute_status(
2340 &self,
2341 run_status: &SetupScriptExecuteStatus<LiveSpec>,
2342 writer: &mut dyn WriteStr,
2343 ) -> io::Result<()> {
2344 let spec = self.output_spec_for_finished(&run_status.result, false);
2345 self.unit_output.write_child_execution_output(
2346 &self.styles,
2347 &spec,
2348 &run_status.output,
2349 writer,
2350 )?;
2351
2352 if show_finished_status_info_line(&run_status.result) {
2353 write!(
2354 writer,
2355 " (script ",
2357 )?;
2358 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2359 writeln!(writer, ")\n")?;
2360 }
2361
2362 Ok(())
2363 }
2364
2365 fn write_test_execute_status(
2366 &self,
2367 run_status: &ExecuteStatus<LiveSpec>,
2368 is_retry: bool,
2369 writer: &mut dyn WriteStr,
2370 ) -> io::Result<()> {
2371 let spec = self.output_spec_for_finished(&run_status.result, is_retry);
2376 self.unit_output.write_child_execution_output(
2377 &self.styles,
2378 &spec,
2379 &run_status.output,
2380 writer,
2381 )?;
2382
2383 if show_finished_status_info_line(&run_status.result) {
2384 write!(
2385 writer,
2386 " (test ",
2388 )?;
2389 self.write_info_execution_result(Some(&run_status.result), run_status.is_slow, writer)?;
2390 writeln!(writer, ")\n")?;
2391 }
2392
2393 Ok(())
2394 }
2395
2396 fn output_spec_for_finished(
2397 &self,
2398 result: &ExecutionResultDescription,
2399 is_retry: bool,
2400 ) -> ChildOutputSpec {
2401 let header_style = if is_retry {
2402 self.styles.retry
2403 } else {
2404 match result {
2405 ExecutionResultDescription::Pass => self.styles.pass,
2406 ExecutionResultDescription::Leak {
2407 result: LeakTimeoutResult::Pass,
2408 } => self.styles.skip,
2409 ExecutionResultDescription::Leak {
2410 result: LeakTimeoutResult::Fail,
2411 } => self.styles.fail,
2412 ExecutionResultDescription::Timeout {
2413 result: SlowTimeoutResult::Pass,
2414 } => self.styles.skip,
2415 ExecutionResultDescription::Timeout {
2416 result: SlowTimeoutResult::Fail,
2417 } => self.styles.fail,
2418 ExecutionResultDescription::Fail { .. } => self.styles.fail,
2419 ExecutionResultDescription::ExecFail => self.styles.fail,
2420 }
2421 };
2422
2423 let (six_char_start, six_char_end, eight_char_start, eight_char_end, output_indent) =
2444 if self.no_output_indent {
2445 (
2446 self.theme_characters.hbar(2),
2447 self.theme_characters.hbar(2),
2448 self.theme_characters.hbar(1),
2449 self.theme_characters.hbar(1),
2450 "",
2451 )
2452 } else {
2453 (
2454 " ".to_owned(),
2455 self.theme_characters.hbar(3),
2456 " ".to_owned(),
2457 self.theme_characters.hbar(1),
2458 " ",
2459 )
2460 };
2461
2462 let stdout_header = format!(
2463 "{} {} {}",
2464 six_char_start.style(header_style),
2465 "stdout".style(header_style),
2466 six_char_end.style(header_style),
2467 );
2468 let stderr_header = format!(
2469 "{} {} {}",
2470 six_char_start.style(header_style),
2471 "stderr".style(header_style),
2472 six_char_end.style(header_style),
2473 );
2474 let combined_header = format!(
2475 "{} {} {}",
2476 six_char_start.style(header_style),
2477 "output".style(header_style),
2478 six_char_end.style(header_style),
2479 );
2480 let exec_fail_header = format!(
2481 "{} {} {}",
2482 eight_char_start.style(header_style),
2483 "execfail".style(header_style),
2484 eight_char_end.style(header_style),
2485 );
2486
2487 ChildOutputSpec {
2488 kind: UnitKind::Test,
2489 stdout_header,
2490 stderr_header,
2491 combined_header,
2492 exec_fail_header,
2493 output_indent,
2494 }
2495 }
2496
2497 fn output_spec_for_info(&self, kind: UnitKind) -> ChildOutputSpec {
2501 let stdout_header = format!("{}:", "stdout".style(self.styles.count));
2502 let stderr_header = format!("{}:", "stderr".style(self.styles.count));
2503 let combined_header = format!("{}:", "output".style(self.styles.count));
2504 let exec_fail_header = format!("{}:", "errors".style(self.styles.count));
2505
2506 ChildOutputSpec {
2507 kind,
2508 stdout_header,
2509 stderr_header,
2510 combined_header,
2511 exec_fail_header,
2512 output_indent: " ",
2513 }
2514 }
2515}
2516
2517#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
2518enum TestInstanceCounter {
2519 Counter { current: usize, total: usize },
2520 Padded,
2521 None,
2522}
2523
2524#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2527enum StatusLineKind {
2528 Intermediate,
2530 Final,
2532}
2533
2534#[derive(Clone, Copy, Debug)]
2535enum RunStopKind {
2536 Cancel,
2537 Kill,
2538}
2539
2540const LIBTEST_PANIC_EXIT_CODE: i32 = 101;
2541
2542fn show_finished_status_info_line(result: &ExecutionResultDescription) -> bool {
2545 match result {
2547 ExecutionResultDescription::Pass => false,
2548 ExecutionResultDescription::Leak {
2549 result: LeakTimeoutResult::Pass,
2550 } => {
2551 true
2553 }
2554 ExecutionResultDescription::Leak {
2555 result: LeakTimeoutResult::Fail,
2556 } => {
2557 true
2559 }
2560 ExecutionResultDescription::Fail {
2561 failure: FailureDescription::ExitCode { code },
2562 leaked,
2563 } => {
2564 *code != LIBTEST_PANIC_EXIT_CODE && !leaked
2567 }
2568 ExecutionResultDescription::Fail {
2569 failure: FailureDescription::Abort { .. },
2570 leaked: _,
2571 } => {
2572 true
2574 }
2575 ExecutionResultDescription::ExecFail => {
2576 false
2579 }
2580 ExecutionResultDescription::Timeout { .. } => {
2581 true
2583 }
2584 }
2585}
2586
2587fn status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2588 match result {
2590 ExecutionResultDescription::Fail {
2591 failure:
2592 FailureDescription::Abort {
2593 abort: AbortDescription::UnixSignal { signal, name },
2594 },
2595 leaked: _,
2596 } => match name {
2597 Some(s) => format!("SIG{s}").into(),
2598 None => format!("ABORT SIG {signal}").into(),
2599 },
2600 ExecutionResultDescription::Fail {
2601 failure:
2602 FailureDescription::Abort {
2603 abort: AbortDescription::WindowsNtStatus { .. },
2604 }
2605 | FailureDescription::Abort {
2606 abort: AbortDescription::WindowsJobObject,
2607 },
2608 leaked: _,
2609 } => {
2610 "ABORT".into()
2613 }
2614 ExecutionResultDescription::Fail {
2615 failure: FailureDescription::ExitCode { .. },
2616 leaked: true,
2617 } => "FAIL + LEAK".into(),
2618 ExecutionResultDescription::Fail {
2619 failure: FailureDescription::ExitCode { .. },
2620 leaked: false,
2621 } => "FAIL".into(),
2622 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2623 ExecutionResultDescription::Pass => "PASS".into(),
2624 ExecutionResultDescription::Leak {
2625 result: LeakTimeoutResult::Pass,
2626 } => "LEAK".into(),
2627 ExecutionResultDescription::Leak {
2628 result: LeakTimeoutResult::Fail,
2629 } => "LEAK-FAIL".into(),
2630 ExecutionResultDescription::Timeout {
2631 result: SlowTimeoutResult::Pass,
2632 } => "TIMEOUT-PASS".into(),
2633 ExecutionResultDescription::Timeout {
2634 result: SlowTimeoutResult::Fail,
2635 } => "TIMEOUT".into(),
2636 }
2637}
2638
2639fn short_status_str(result: &ExecutionResultDescription) -> Cow<'static, str> {
2640 match result {
2642 ExecutionResultDescription::Fail {
2643 failure:
2644 FailureDescription::Abort {
2645 abort: AbortDescription::UnixSignal { signal, name },
2646 },
2647 leaked: _,
2648 } => match name {
2649 Some(s) => s.to_string().into(),
2650 None => format!("SIG {signal}").into(),
2651 },
2652 ExecutionResultDescription::Fail {
2653 failure:
2654 FailureDescription::Abort {
2655 abort: AbortDescription::WindowsNtStatus { .. },
2656 }
2657 | FailureDescription::Abort {
2658 abort: AbortDescription::WindowsJobObject,
2659 },
2660 leaked: _,
2661 } => {
2662 "ABORT".into()
2665 }
2666 ExecutionResultDescription::Fail {
2667 failure: FailureDescription::ExitCode { .. },
2668 leaked: true,
2669 } => "FL+LK".into(),
2670 ExecutionResultDescription::Fail {
2671 failure: FailureDescription::ExitCode { .. },
2672 leaked: false,
2673 } => "FAIL".into(),
2674 ExecutionResultDescription::ExecFail => "XFAIL".into(),
2675 ExecutionResultDescription::Pass => "PASS".into(),
2676 ExecutionResultDescription::Leak {
2677 result: LeakTimeoutResult::Pass,
2678 } => "LEAK".into(),
2679 ExecutionResultDescription::Leak {
2680 result: LeakTimeoutResult::Fail,
2681 } => "LKFAIL".into(),
2682 ExecutionResultDescription::Timeout {
2683 result: SlowTimeoutResult::Pass,
2684 } => "TMPASS".into(),
2685 ExecutionResultDescription::Timeout {
2686 result: SlowTimeoutResult::Fail,
2687 } => "TMT".into(),
2688 }
2689}
2690
2691fn write_windows_abort_line(
2695 status: &AbortDescription,
2696 styles: &Styles,
2697 writer: &mut dyn WriteStr,
2698) -> io::Result<()> {
2699 match status {
2700 AbortDescription::UnixSignal { .. } => {
2701 Ok(())
2703 }
2704 AbortDescription::WindowsNtStatus { code, message } => {
2705 const INDENT: &str = " - ";
2708 let mut indented = indented(writer).with_str(INDENT).skip_initial();
2709 let code_str = format!("{:#010x}", code.style(styles.count));
2711 let status_str = match message {
2712 Some(msg) => format!("{code_str}: {msg}"),
2713 None => code_str,
2714 };
2715 writeln!(
2716 indented,
2717 "{:>12} {} {}",
2718 "-",
2719 "with code".style(styles.fail),
2720 status_str,
2721 )?;
2722 indented.write_str_flush()
2723 }
2724 AbortDescription::WindowsJobObject => {
2725 writeln!(
2726 writer,
2727 "{:>12} {} via {}",
2728 "-",
2729 "terminated".style(styles.fail),
2730 "job object".style(styles.count),
2731 )
2732 }
2733 }
2734}
2735
2736#[cfg(test)]
2737mod tests {
2738 use super::*;
2739 use crate::{
2740 errors::{ChildError, ChildFdError, ChildStartError, ErrorList},
2741 reporter::{
2742 ShowProgress,
2743 events::{
2744 ChildExecutionOutputDescription, ExecutionResult, FailureStatus,
2745 UnitTerminateReason,
2746 },
2747 test_helpers::global_slot_assignment,
2748 },
2749 test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
2750 };
2751 use bytes::Bytes;
2752 use chrono::Local;
2753 use nextest_metadata::{RustBinaryId, TestCaseName};
2754 use quick_junit::ReportUuid;
2755 use smol_str::SmolStr;
2756 use std::{num::NonZero, sync::Arc};
2757 use swrite::{SWrite, swriteln};
2758 use test_case::test_case;
2759
2760 fn with_reporter<'a, F>(f: F, out: &'a mut String)
2764 where
2765 F: FnOnce(DisplayReporter<'a>),
2766 {
2767 with_reporter_impl(f, out, false)
2768 }
2769
2770 fn with_verbose_reporter<'a, F>(f: F, out: &'a mut String)
2772 where
2773 F: FnOnce(DisplayReporter<'a>),
2774 {
2775 with_reporter_impl(f, out, true)
2776 }
2777
2778 fn with_reporter_impl<'a, F>(f: F, out: &'a mut String, verbose: bool)
2779 where
2780 F: FnOnce(DisplayReporter<'a>),
2781 {
2782 let builder = DisplayReporterBuilder {
2783 mode: NextestRunMode::Test,
2784 default_filter: CompiledDefaultFilter::for_default_config(),
2785 display_config: DisplayConfig {
2786 show_progress: ShowProgress::Counter,
2787 no_capture: true,
2788 status_level: Some(StatusLevel::Fail),
2789 final_status_level: Some(FinalStatusLevel::Fail),
2790 profile_status_level: StatusLevel::Fail,
2791 profile_final_status_level: FinalStatusLevel::Fail,
2792 },
2793 run_count: 5000,
2794 success_output: Some(TestOutputDisplay::Immediate),
2795 failure_output: Some(TestOutputDisplay::Immediate),
2796 should_colorize: false,
2797 verbose,
2798 no_output_indent: false,
2799 max_progress_running: MaxProgressRunning::default(),
2800 show_term_progress: ShowTerminalProgress::No,
2801 displayer_kind: DisplayerKind::Live,
2802 redactor: Redactor::noop(),
2803 };
2804
2805 let output = ReporterOutput::Writer {
2806 writer: out,
2807 use_unicode: true,
2808 };
2809 let reporter = builder.build(output);
2810 f(reporter);
2811 }
2812
2813 fn with_reporter_at_status_level<'a, F>(f: F, out: &'a mut String, status_level: StatusLevel)
2816 where
2817 F: FnOnce(DisplayReporter<'a>),
2818 {
2819 let builder = DisplayReporterBuilder {
2820 mode: NextestRunMode::Test,
2821 default_filter: CompiledDefaultFilter::for_default_config(),
2822 display_config: DisplayConfig {
2823 show_progress: ShowProgress::Counter,
2824 no_capture: false,
2825 status_level: Some(status_level),
2826 final_status_level: Some(FinalStatusLevel::Fail),
2827 profile_status_level: StatusLevel::Fail,
2828 profile_final_status_level: FinalStatusLevel::Fail,
2829 },
2830 run_count: 5000,
2831 success_output: Some(TestOutputDisplay::Immediate),
2832 failure_output: Some(TestOutputDisplay::Immediate),
2833 should_colorize: false,
2834 verbose: false,
2835 no_output_indent: false,
2836 max_progress_running: MaxProgressRunning::default(),
2837 show_term_progress: ShowTerminalProgress::No,
2838 displayer_kind: DisplayerKind::Live,
2839 redactor: Redactor::noop(),
2840 };
2841
2842 let output = ReporterOutput::Writer {
2843 writer: out,
2844 use_unicode: true,
2845 };
2846 let reporter = builder.build(output);
2847 f(reporter);
2848 }
2849
2850 fn make_split_output(
2851 result: Option<ExecutionResult>,
2852 stdout: &str,
2853 stderr: &str,
2854 ) -> ChildExecutionOutputDescription<LiveSpec> {
2855 ChildExecutionOutput::Output {
2856 result,
2857 output: ChildOutput::Split(ChildSplitOutput {
2858 stdout: Some(Bytes::from(stdout.to_owned()).into()),
2859 stderr: Some(Bytes::from(stderr.to_owned()).into()),
2860 }),
2861 errors: None,
2862 }
2863 .into()
2864 }
2865
2866 fn make_split_output_with_errors(
2867 result: Option<ExecutionResult>,
2868 stdout: &str,
2869 stderr: &str,
2870 errors: Vec<ChildError>,
2871 ) -> ChildExecutionOutputDescription<LiveSpec> {
2872 ChildExecutionOutput::Output {
2873 result,
2874 output: ChildOutput::Split(ChildSplitOutput {
2875 stdout: Some(Bytes::from(stdout.to_owned()).into()),
2876 stderr: Some(Bytes::from(stderr.to_owned()).into()),
2877 }),
2878 errors: ErrorList::new("testing split output", errors),
2879 }
2880 .into()
2881 }
2882
2883 fn make_combined_output_with_errors(
2884 result: Option<ExecutionResult>,
2885 output: &str,
2886 errors: Vec<ChildError>,
2887 ) -> ChildExecutionOutputDescription<LiveSpec> {
2888 ChildExecutionOutput::Output {
2889 result,
2890 output: ChildOutput::Combined {
2891 output: Bytes::from(output.to_owned()).into(),
2892 },
2893 errors: ErrorList::new("testing split output", errors),
2894 }
2895 .into()
2896 }
2897
2898 fn make_pass_output() -> FinalOutput {
2900 let status = ExecuteStatus {
2901 retry_data: RetryData {
2902 attempt: 1,
2903 total_attempts: 1,
2904 },
2905 output: make_split_output(Some(ExecutionResult::Pass), "", ""),
2906 result: ExecutionResultDescription::Pass,
2907 start_time: Local::now().into(),
2908 time_taken: Duration::from_secs(1),
2909 is_slow: false,
2910 delay_before_start: Duration::ZERO,
2911 error_summary: None,
2912 output_error_slice: None,
2913 };
2914 FinalOutput::Executed {
2915 run_statuses: ExecutionStatuses::new(vec![status], FlakyResult::default()),
2916 display_output: false,
2917 }
2918 }
2919
2920 fn make_fail_output() -> FinalOutput {
2922 let result = ExecutionResult::Fail {
2923 failure_status: FailureStatus::ExitCode(1),
2924 leaked: false,
2925 };
2926 let status = ExecuteStatus {
2927 retry_data: RetryData {
2928 attempt: 1,
2929 total_attempts: 1,
2930 },
2931 output: make_split_output(Some(result), "", ""),
2932 result: ExecutionResultDescription::from(result),
2933 start_time: Local::now().into(),
2934 time_taken: Duration::from_secs(1),
2935 is_slow: false,
2936 delay_before_start: Duration::ZERO,
2937 error_summary: None,
2938 output_error_slice: None,
2939 };
2940 FinalOutput::Executed {
2941 run_statuses: ExecutionStatuses::new(vec![status], FlakyResult::default()),
2942 display_output: false,
2943 }
2944 }
2945
2946 fn make_skip_output() -> FinalOutput {
2948 FinalOutput::Skipped(MismatchReason::Ignored)
2949 }
2950
2951 fn extract_ids<'a>(entries: &[FinalOutputEntry<'a>]) -> Vec<(&'a str, &'a str)> {
2953 entries
2954 .iter()
2955 .map(|e| (e.instance.binary_id.as_str(), e.instance.test_name.as_str()))
2956 .collect()
2957 }
2958
2959 #[test]
2960 fn final_status_line() {
2961 let binary_id = RustBinaryId::new("my-binary-id");
2962 let test_name = TestCaseName::new("test1");
2963 let test_instance = TestInstanceId {
2964 binary_id: &binary_id,
2965 test_name: &test_name,
2966 };
2967
2968 let fail_result_internal = ExecutionResult::Fail {
2969 failure_status: FailureStatus::ExitCode(1),
2970 leaked: false,
2971 };
2972 let fail_result = ExecutionResultDescription::from(fail_result_internal);
2973
2974 let fail_status = ExecuteStatus {
2975 retry_data: RetryData {
2976 attempt: 1,
2977 total_attempts: 2,
2978 },
2979 output: make_split_output(Some(fail_result_internal), "", ""),
2981 result: fail_result.clone(),
2982 start_time: Local::now().into(),
2983 time_taken: Duration::from_secs(1),
2984 is_slow: false,
2985 delay_before_start: Duration::ZERO,
2986 error_summary: None,
2987 output_error_slice: None,
2988 };
2989 let fail_describe = ExecutionDescription::Failure {
2990 first_status: &fail_status,
2991 last_status: &fail_status,
2992 retries: &[],
2993 };
2994
2995 let flaky_status = ExecuteStatus {
2996 retry_data: RetryData {
2997 attempt: 2,
2998 total_attempts: 2,
2999 },
3000 output: make_split_output(Some(fail_result_internal), "", ""),
3002 result: ExecutionResultDescription::Pass,
3003 start_time: Local::now().into(),
3004 time_taken: Duration::from_secs(2),
3005 is_slow: false,
3006 delay_before_start: Duration::ZERO,
3007 error_summary: None,
3008 output_error_slice: None,
3009 };
3010
3011 let statuses =
3013 ExecutionStatuses::new(vec![fail_status.clone(), flaky_status], FlakyResult::Pass);
3014 let flaky_describe = statuses.describe();
3015
3016 let mut out = String::new();
3017
3018 with_reporter(
3019 |mut reporter| {
3020 reporter
3022 .inner
3023 .write_final_status_line(
3024 None,
3025 TestInstanceCounter::None,
3026 test_instance,
3027 fail_describe,
3028 reporter.output.writer_mut().unwrap(),
3029 )
3030 .unwrap();
3031
3032 reporter
3033 .inner
3034 .write_final_status_line(
3035 Some(StressIndex {
3036 current: 1,
3037 total: None,
3038 }),
3039 TestInstanceCounter::Padded,
3040 test_instance,
3041 flaky_describe,
3042 reporter.output.writer_mut().unwrap(),
3043 )
3044 .unwrap();
3045
3046 reporter
3047 .inner
3048 .write_final_status_line(
3049 Some(StressIndex {
3050 current: 2,
3051 total: Some(NonZero::new(3).unwrap()),
3052 }),
3053 TestInstanceCounter::Counter {
3054 current: 20,
3055 total: 5000,
3056 },
3057 test_instance,
3058 flaky_describe,
3059 reporter.output.writer_mut().unwrap(),
3060 )
3061 .unwrap();
3062 },
3063 &mut out,
3064 );
3065
3066 insta::assert_snapshot!("final_status_output", out,);
3067 }
3068
3069 #[test]
3070 fn status_line_all_variants() {
3071 let binary_id = RustBinaryId::new("my-binary-id");
3072 let test_name = TestCaseName::new("test_name");
3073 let test_instance = TestInstanceId {
3074 binary_id: &binary_id,
3075 test_name: &test_name,
3076 };
3077
3078 let pass_result_internal = ExecutionResult::Pass;
3080 let pass_result = ExecutionResultDescription::from(pass_result_internal);
3081
3082 let leak_pass_result_internal = ExecutionResult::Leak {
3083 result: LeakTimeoutResult::Pass,
3084 };
3085 let leak_pass_result = ExecutionResultDescription::from(leak_pass_result_internal);
3086
3087 let timeout_pass_result_internal = ExecutionResult::Timeout {
3088 result: SlowTimeoutResult::Pass,
3089 };
3090 let timeout_pass_result = ExecutionResultDescription::from(timeout_pass_result_internal);
3091
3092 let fail_result_internal = ExecutionResult::Fail {
3094 failure_status: FailureStatus::ExitCode(1),
3095 leaked: false,
3096 };
3097 let fail_result = ExecutionResultDescription::from(fail_result_internal);
3098
3099 let fail_leak_result_internal = ExecutionResult::Fail {
3100 failure_status: FailureStatus::ExitCode(1),
3101 leaked: true,
3102 };
3103 let fail_leak_result = ExecutionResultDescription::from(fail_leak_result_internal);
3104
3105 let exec_fail_result_internal = ExecutionResult::ExecFail;
3106 let exec_fail_result = ExecutionResultDescription::from(exec_fail_result_internal);
3107
3108 let leak_fail_result_internal = ExecutionResult::Leak {
3109 result: LeakTimeoutResult::Fail,
3110 };
3111 let leak_fail_result = ExecutionResultDescription::from(leak_fail_result_internal);
3112
3113 let timeout_fail_result_internal = ExecutionResult::Timeout {
3114 result: SlowTimeoutResult::Fail,
3115 };
3116 let timeout_fail_result = ExecutionResultDescription::from(timeout_fail_result_internal);
3117
3118 let abort_unix_result = ExecutionResultDescription::Fail {
3120 failure: FailureDescription::Abort {
3121 abort: AbortDescription::UnixSignal {
3122 signal: 11,
3123 name: Some("SEGV".into()),
3124 },
3125 },
3126 leaked: false,
3127 };
3128 let abort_windows_result = ExecutionResultDescription::Fail {
3129 failure: FailureDescription::Abort {
3130 abort: AbortDescription::WindowsNtStatus {
3131 code: 0xC0000005_u32 as i32,
3133 message: Some("Access violation".into()),
3134 },
3135 },
3136 leaked: false,
3137 };
3138
3139 let pass_status = ExecuteStatus {
3141 retry_data: RetryData {
3142 attempt: 1,
3143 total_attempts: 1,
3144 },
3145 output: make_split_output(Some(pass_result_internal), "", ""),
3146 result: pass_result.clone(),
3147 start_time: Local::now().into(),
3148 time_taken: Duration::from_secs(1),
3149 is_slow: false,
3150 delay_before_start: Duration::ZERO,
3151 error_summary: None,
3152 output_error_slice: None,
3153 };
3154
3155 let leak_pass_status = ExecuteStatus {
3156 retry_data: RetryData {
3157 attempt: 1,
3158 total_attempts: 1,
3159 },
3160 output: make_split_output(Some(leak_pass_result_internal), "", ""),
3161 result: leak_pass_result.clone(),
3162 start_time: Local::now().into(),
3163 time_taken: Duration::from_secs(2),
3164 is_slow: false,
3165 delay_before_start: Duration::ZERO,
3166 error_summary: None,
3167 output_error_slice: None,
3168 };
3169
3170 let timeout_pass_status = ExecuteStatus {
3171 retry_data: RetryData {
3172 attempt: 1,
3173 total_attempts: 1,
3174 },
3175 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3176 result: timeout_pass_result.clone(),
3177 start_time: Local::now().into(),
3178 time_taken: Duration::from_secs(240),
3179 is_slow: false,
3180 delay_before_start: Duration::ZERO,
3181 error_summary: None,
3182 output_error_slice: None,
3183 };
3184
3185 let pass_slow_status = ExecuteStatus {
3187 retry_data: RetryData {
3188 attempt: 1,
3189 total_attempts: 1,
3190 },
3191 output: make_split_output(Some(pass_result_internal), "", ""),
3192 result: pass_result.clone(),
3193 start_time: Local::now().into(),
3194 time_taken: Duration::from_secs(30),
3195 is_slow: true,
3196 delay_before_start: Duration::ZERO,
3197 error_summary: None,
3198 output_error_slice: None,
3199 };
3200
3201 let leak_pass_slow_status = ExecuteStatus {
3202 retry_data: RetryData {
3203 attempt: 1,
3204 total_attempts: 1,
3205 },
3206 output: make_split_output(Some(leak_pass_result_internal), "", ""),
3207 result: leak_pass_result.clone(),
3208 start_time: Local::now().into(),
3209 time_taken: Duration::from_secs(30),
3210 is_slow: true,
3211 delay_before_start: Duration::ZERO,
3212 error_summary: None,
3213 output_error_slice: None,
3214 };
3215
3216 let timeout_pass_slow_status = ExecuteStatus {
3217 retry_data: RetryData {
3218 attempt: 1,
3219 total_attempts: 1,
3220 },
3221 output: make_split_output(Some(timeout_pass_result_internal), "", ""),
3222 result: timeout_pass_result.clone(),
3223 start_time: Local::now().into(),
3224 time_taken: Duration::from_secs(300),
3225 is_slow: true,
3226 delay_before_start: Duration::ZERO,
3227 error_summary: None,
3228 output_error_slice: None,
3229 };
3230
3231 let flaky_first_status = ExecuteStatus {
3233 retry_data: RetryData {
3234 attempt: 1,
3235 total_attempts: 2,
3236 },
3237 output: make_split_output(Some(fail_result_internal), "", ""),
3238 result: fail_result.clone(),
3239 start_time: Local::now().into(),
3240 time_taken: Duration::from_secs(1),
3241 is_slow: false,
3242 delay_before_start: Duration::ZERO,
3243 error_summary: None,
3244 output_error_slice: None,
3245 };
3246 let flaky_last_status = ExecuteStatus {
3247 retry_data: RetryData {
3248 attempt: 2,
3249 total_attempts: 2,
3250 },
3251 output: make_split_output(Some(pass_result_internal), "", ""),
3252 result: pass_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_status = ExecuteStatus {
3263 retry_data: RetryData {
3264 attempt: 1,
3265 total_attempts: 1,
3266 },
3267 output: make_split_output(Some(fail_result_internal), "", ""),
3268 result: fail_result.clone(),
3269 start_time: Local::now().into(),
3270 time_taken: Duration::from_secs(1),
3271 is_slow: false,
3272 delay_before_start: Duration::ZERO,
3273 error_summary: None,
3274 output_error_slice: None,
3275 };
3276
3277 let fail_leak_status = ExecuteStatus {
3278 retry_data: RetryData {
3279 attempt: 1,
3280 total_attempts: 1,
3281 },
3282 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3283 result: fail_leak_result.clone(),
3284 start_time: Local::now().into(),
3285 time_taken: Duration::from_secs(1),
3286 is_slow: false,
3287 delay_before_start: Duration::ZERO,
3288 error_summary: None,
3289 output_error_slice: None,
3290 };
3291
3292 let exec_fail_status = ExecuteStatus {
3293 retry_data: RetryData {
3294 attempt: 1,
3295 total_attempts: 1,
3296 },
3297 output: make_split_output(Some(exec_fail_result_internal), "", ""),
3298 result: exec_fail_result.clone(),
3299 start_time: Local::now().into(),
3300 time_taken: Duration::from_secs(1),
3301 is_slow: false,
3302 delay_before_start: Duration::ZERO,
3303 error_summary: None,
3304 output_error_slice: None,
3305 };
3306
3307 let leak_fail_status = ExecuteStatus {
3308 retry_data: RetryData {
3309 attempt: 1,
3310 total_attempts: 1,
3311 },
3312 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3313 result: leak_fail_result.clone(),
3314 start_time: Local::now().into(),
3315 time_taken: Duration::from_secs(1),
3316 is_slow: false,
3317 delay_before_start: Duration::ZERO,
3318 error_summary: None,
3319 output_error_slice: None,
3320 };
3321
3322 let timeout_fail_status = ExecuteStatus {
3323 retry_data: RetryData {
3324 attempt: 1,
3325 total_attempts: 1,
3326 },
3327 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3328 result: timeout_fail_result.clone(),
3329 start_time: Local::now().into(),
3330 time_taken: Duration::from_secs(60),
3331 is_slow: false,
3332 delay_before_start: Duration::ZERO,
3333 error_summary: None,
3334 output_error_slice: None,
3335 };
3336
3337 let abort_unix_status = ExecuteStatus {
3338 retry_data: RetryData {
3339 attempt: 1,
3340 total_attempts: 1,
3341 },
3342 output: make_split_output(None, "", ""),
3343 result: abort_unix_result.clone(),
3344 start_time: Local::now().into(),
3345 time_taken: Duration::from_secs(1),
3346 is_slow: false,
3347 delay_before_start: Duration::ZERO,
3348 error_summary: None,
3349 output_error_slice: None,
3350 };
3351
3352 let abort_windows_status = ExecuteStatus {
3353 retry_data: RetryData {
3354 attempt: 1,
3355 total_attempts: 1,
3356 },
3357 output: make_split_output(None, "", ""),
3358 result: abort_windows_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_retry_status = ExecuteStatus {
3369 retry_data: RetryData {
3370 attempt: 2,
3371 total_attempts: 2,
3372 },
3373 output: make_split_output(Some(fail_result_internal), "", ""),
3374 result: fail_result.clone(),
3375 start_time: Local::now().into(),
3376 time_taken: Duration::from_secs(1),
3377 is_slow: false,
3378 delay_before_start: Duration::ZERO,
3379 error_summary: None,
3380 output_error_slice: None,
3381 };
3382
3383 let fail_leak_retry_status = ExecuteStatus {
3384 retry_data: RetryData {
3385 attempt: 2,
3386 total_attempts: 2,
3387 },
3388 output: make_split_output(Some(fail_leak_result_internal), "", ""),
3389 result: fail_leak_result.clone(),
3390 start_time: Local::now().into(),
3391 time_taken: Duration::from_secs(1),
3392 is_slow: false,
3393 delay_before_start: Duration::ZERO,
3394 error_summary: None,
3395 output_error_slice: None,
3396 };
3397
3398 let leak_fail_retry_status = ExecuteStatus {
3399 retry_data: RetryData {
3400 attempt: 2,
3401 total_attempts: 2,
3402 },
3403 output: make_split_output(Some(leak_fail_result_internal), "", ""),
3404 result: leak_fail_result.clone(),
3405 start_time: Local::now().into(),
3406 time_taken: Duration::from_secs(1),
3407 is_slow: false,
3408 delay_before_start: Duration::ZERO,
3409 error_summary: None,
3410 output_error_slice: None,
3411 };
3412
3413 let timeout_fail_retry_status = ExecuteStatus {
3414 retry_data: RetryData {
3415 attempt: 2,
3416 total_attempts: 2,
3417 },
3418 output: make_split_output(Some(timeout_fail_result_internal), "", ""),
3419 result: timeout_fail_result.clone(),
3420 start_time: Local::now().into(),
3421 time_taken: Duration::from_secs(60),
3422 is_slow: false,
3423 delay_before_start: Duration::ZERO,
3424 error_summary: None,
3425 output_error_slice: None,
3426 };
3427
3428 let pass_describe = ExecutionDescription::Success {
3430 single_status: &pass_status,
3431 };
3432 let leak_pass_describe = ExecutionDescription::Success {
3433 single_status: &leak_pass_status,
3434 };
3435 let timeout_pass_describe = ExecutionDescription::Success {
3436 single_status: &timeout_pass_status,
3437 };
3438 let pass_slow_describe = ExecutionDescription::Success {
3439 single_status: &pass_slow_status,
3440 };
3441 let leak_pass_slow_describe = ExecutionDescription::Success {
3442 single_status: &leak_pass_slow_status,
3443 };
3444 let timeout_pass_slow_describe = ExecutionDescription::Success {
3445 single_status: &timeout_pass_slow_status,
3446 };
3447 let flaky_describe = ExecutionDescription::Flaky {
3448 last_status: &flaky_last_status,
3449 prior_statuses: std::slice::from_ref(&flaky_first_status),
3450 result: FlakyResult::Pass,
3451 };
3452 let flaky_fail_describe = ExecutionDescription::Flaky {
3453 last_status: &flaky_last_status,
3454 prior_statuses: std::slice::from_ref(&flaky_first_status),
3455 result: FlakyResult::Fail,
3456 };
3457 let fail_describe = ExecutionDescription::Failure {
3458 first_status: &fail_status,
3459 last_status: &fail_status,
3460 retries: &[],
3461 };
3462 let fail_leak_describe = ExecutionDescription::Failure {
3463 first_status: &fail_leak_status,
3464 last_status: &fail_leak_status,
3465 retries: &[],
3466 };
3467 let exec_fail_describe = ExecutionDescription::Failure {
3468 first_status: &exec_fail_status,
3469 last_status: &exec_fail_status,
3470 retries: &[],
3471 };
3472 let leak_fail_describe = ExecutionDescription::Failure {
3473 first_status: &leak_fail_status,
3474 last_status: &leak_fail_status,
3475 retries: &[],
3476 };
3477 let timeout_fail_describe = ExecutionDescription::Failure {
3478 first_status: &timeout_fail_status,
3479 last_status: &timeout_fail_status,
3480 retries: &[],
3481 };
3482 let abort_unix_describe = ExecutionDescription::Failure {
3483 first_status: &abort_unix_status,
3484 last_status: &abort_unix_status,
3485 retries: &[],
3486 };
3487 let abort_windows_describe = ExecutionDescription::Failure {
3488 first_status: &abort_windows_status,
3489 last_status: &abort_windows_status,
3490 retries: &[],
3491 };
3492 let fail_retry_describe = ExecutionDescription::Failure {
3493 first_status: &fail_status,
3494 last_status: &fail_retry_status,
3495 retries: std::slice::from_ref(&fail_retry_status),
3496 };
3497 let fail_leak_retry_describe = ExecutionDescription::Failure {
3498 first_status: &fail_leak_status,
3499 last_status: &fail_leak_retry_status,
3500 retries: std::slice::from_ref(&fail_leak_retry_status),
3501 };
3502 let leak_fail_retry_describe = ExecutionDescription::Failure {
3503 first_status: &leak_fail_status,
3504 last_status: &leak_fail_retry_status,
3505 retries: std::slice::from_ref(&leak_fail_retry_status),
3506 };
3507 let timeout_fail_retry_describe = ExecutionDescription::Failure {
3508 first_status: &timeout_fail_status,
3509 last_status: &timeout_fail_retry_status,
3510 retries: std::slice::from_ref(&timeout_fail_retry_status),
3511 };
3512
3513 let test_cases: Vec<(&str, ExecutionDescription<'_, LiveSpec>)> = vec![
3516 ("pass", pass_describe),
3518 ("leak pass", leak_pass_describe),
3519 ("timeout pass", timeout_pass_describe),
3520 ("pass slow", pass_slow_describe),
3522 ("leak pass slow", leak_pass_slow_describe),
3523 ("timeout pass slow", timeout_pass_slow_describe),
3524 ("flaky", flaky_describe),
3526 ("flaky fail", flaky_fail_describe),
3527 ("fail", fail_describe),
3529 ("fail leak", fail_leak_describe),
3530 ("exec fail", exec_fail_describe),
3531 ("leak fail", leak_fail_describe),
3532 ("timeout fail", timeout_fail_describe),
3533 ("abort unix", abort_unix_describe),
3534 ("abort windows", abort_windows_describe),
3535 ("fail retry", fail_retry_describe),
3537 ("fail leak retry", fail_leak_retry_describe),
3538 ("leak fail retry", leak_fail_retry_describe),
3539 ("timeout fail retry", timeout_fail_retry_describe),
3540 ];
3541
3542 let mut out = String::new();
3543 let mut counter = 0usize;
3544
3545 with_reporter(
3546 |mut reporter| {
3547 let writer = reporter.output.writer_mut().unwrap();
3548
3549 for (kind_name, kind) in [
3551 ("intermediate", StatusLineKind::Intermediate),
3552 ("final", StatusLineKind::Final),
3553 ] {
3554 writeln!(writer, "=== {kind_name} ===").unwrap();
3555
3556 for (label, describe) in &test_cases {
3557 counter += 1;
3558 let test_counter = TestInstanceCounter::Counter {
3559 current: counter,
3560 total: 100,
3561 };
3562
3563 writeln!(writer, "# {label}: ").unwrap();
3565
3566 reporter
3567 .inner
3568 .write_status_line_impl(
3569 None,
3570 test_counter,
3571 test_instance,
3572 *describe,
3573 kind,
3574 writer,
3575 )
3576 .unwrap();
3577 }
3578 }
3579 },
3580 &mut out,
3581 );
3582
3583 insta::assert_snapshot!("status_line_all_variants", out);
3584 }
3585
3586 #[test]
3587 fn test_summary_line() {
3588 let run_id = ReportUuid::nil();
3589 let mut out = String::new();
3590
3591 with_reporter(
3592 |mut reporter| {
3593 let run_stats_success = RunStats {
3595 initial_run_count: 5,
3596 finished_count: 5,
3597 setup_scripts_initial_count: 0,
3598 setup_scripts_finished_count: 0,
3599 setup_scripts_passed: 0,
3600 setup_scripts_failed: 0,
3601 setup_scripts_exec_failed: 0,
3602 setup_scripts_timed_out: 0,
3603 passed: 5,
3604 passed_slow: 0,
3605 passed_timed_out: 0,
3606 flaky: 0,
3607 failed: 0,
3608 failed_slow: 0,
3609 failed_timed_out: 0,
3610 leaky: 0,
3611 leaky_failed: 0,
3612 exec_failed: 0,
3613 skipped: 0,
3614 cancel_reason: None,
3615 };
3616
3617 reporter
3618 .write_event(&TestEvent {
3619 timestamp: Local::now().into(),
3620 elapsed: Duration::ZERO,
3621 kind: TestEventKind::RunFinished {
3622 run_id,
3623 start_time: Local::now().into(),
3624 elapsed: Duration::from_secs(2),
3625 run_stats: RunFinishedStats::Single(run_stats_success),
3626 outstanding_not_seen: None,
3627 },
3628 })
3629 .unwrap();
3630
3631 let run_stats_mixed = RunStats {
3633 initial_run_count: 10,
3634 finished_count: 8,
3635 setup_scripts_initial_count: 1,
3636 setup_scripts_finished_count: 1,
3637 setup_scripts_passed: 1,
3638 setup_scripts_failed: 0,
3639 setup_scripts_exec_failed: 0,
3640 setup_scripts_timed_out: 0,
3641 passed: 5,
3642 passed_slow: 1,
3643 passed_timed_out: 2,
3644 flaky: 1,
3645 failed: 2,
3646 failed_slow: 0,
3647 failed_timed_out: 1,
3648 leaky: 1,
3649 leaky_failed: 0,
3650 exec_failed: 1,
3651 skipped: 2,
3652 cancel_reason: Some(CancelReason::Signal),
3653 };
3654
3655 reporter
3656 .write_event(&TestEvent {
3657 timestamp: Local::now().into(),
3658 elapsed: Duration::ZERO,
3659 kind: TestEventKind::RunFinished {
3660 run_id,
3661 start_time: Local::now().into(),
3662 elapsed: Duration::from_millis(15750),
3663 run_stats: RunFinishedStats::Single(run_stats_mixed),
3664 outstanding_not_seen: None,
3665 },
3666 })
3667 .unwrap();
3668
3669 let stress_stats_success = StressRunStats {
3671 completed: StressIndex {
3672 current: 25,
3673 total: Some(NonZero::new(50).unwrap()),
3674 },
3675 success_count: 25,
3676 failed_count: 0,
3677 last_final_stats: FinalRunStats::Success,
3678 };
3679
3680 reporter
3681 .write_event(&TestEvent {
3682 timestamp: Local::now().into(),
3683 elapsed: Duration::ZERO,
3684 kind: TestEventKind::RunFinished {
3685 run_id,
3686 start_time: Local::now().into(),
3687 elapsed: Duration::from_secs(120),
3688 run_stats: RunFinishedStats::Stress(stress_stats_success),
3689 outstanding_not_seen: None,
3690 },
3691 })
3692 .unwrap();
3693
3694 let stress_stats_failed = StressRunStats {
3696 completed: StressIndex {
3697 current: 15,
3698 total: None, },
3700 success_count: 12,
3701 failed_count: 3,
3702 last_final_stats: FinalRunStats::Cancelled {
3703 reason: Some(CancelReason::Interrupt),
3704 kind: RunStatsFailureKind::SetupScript,
3705 },
3706 };
3707
3708 reporter
3709 .write_event(&TestEvent {
3710 timestamp: Local::now().into(),
3711 elapsed: Duration::ZERO,
3712 kind: TestEventKind::RunFinished {
3713 run_id,
3714 start_time: Local::now().into(),
3715 elapsed: Duration::from_millis(45250),
3716 run_stats: RunFinishedStats::Stress(stress_stats_failed),
3717 outstanding_not_seen: None,
3718 },
3719 })
3720 .unwrap();
3721
3722 let run_stats_empty = RunStats {
3724 initial_run_count: 0,
3725 finished_count: 0,
3726 setup_scripts_initial_count: 0,
3727 setup_scripts_finished_count: 0,
3728 setup_scripts_passed: 0,
3729 setup_scripts_failed: 0,
3730 setup_scripts_exec_failed: 0,
3731 setup_scripts_timed_out: 0,
3732 passed: 0,
3733 passed_slow: 0,
3734 passed_timed_out: 0,
3735 flaky: 0,
3736 failed: 0,
3737 failed_slow: 0,
3738 failed_timed_out: 0,
3739 leaky: 0,
3740 leaky_failed: 0,
3741 exec_failed: 0,
3742 skipped: 0,
3743 cancel_reason: None,
3744 };
3745
3746 reporter
3747 .write_event(&TestEvent {
3748 timestamp: Local::now().into(),
3749 elapsed: Duration::ZERO,
3750 kind: TestEventKind::RunFinished {
3751 run_id,
3752 start_time: Local::now().into(),
3753 elapsed: Duration::from_millis(100),
3754 run_stats: RunFinishedStats::Single(run_stats_empty),
3755 outstanding_not_seen: None,
3756 },
3757 })
3758 .unwrap();
3759 },
3760 &mut out,
3761 );
3762
3763 insta::assert_snapshot!("summary_line_output", out,);
3764 }
3765
3766 #[test]
3770 fn test_info_response() {
3771 let args = vec!["arg1".to_string(), "arg2".to_string()];
3772 let binary_id = RustBinaryId::new("my-binary-id");
3773 let test_name1 = TestCaseName::new("test1");
3774 let test_name2 = TestCaseName::new("test2");
3775 let test_name3 = TestCaseName::new("test3");
3776 let test_name4 = TestCaseName::new("test4");
3777 let test_name5 = TestCaseName::new("test5");
3778
3779 let mut out = String::new();
3780
3781 with_reporter(
3782 |mut reporter| {
3783 reporter
3785 .write_event(&TestEvent {
3786 timestamp: Local::now().into(),
3787 elapsed: Duration::ZERO,
3788 kind: TestEventKind::InfoStarted {
3789 total: 30,
3790 run_stats: RunStats {
3791 initial_run_count: 40,
3792 finished_count: 20,
3793 setup_scripts_initial_count: 1,
3794 setup_scripts_finished_count: 1,
3795 setup_scripts_passed: 1,
3796 setup_scripts_failed: 0,
3797 setup_scripts_exec_failed: 0,
3798 setup_scripts_timed_out: 0,
3799 passed: 17,
3800 passed_slow: 4,
3801 passed_timed_out: 3,
3802 flaky: 2,
3803 failed: 2,
3804 failed_slow: 1,
3805 failed_timed_out: 1,
3806 leaky: 1,
3807 leaky_failed: 2,
3808 exec_failed: 1,
3809 skipped: 5,
3810 cancel_reason: None,
3811 },
3812 },
3813 })
3814 .unwrap();
3815
3816 reporter
3818 .write_event(&TestEvent {
3819 timestamp: Local::now().into(),
3820 elapsed: Duration::ZERO,
3821 kind: TestEventKind::InfoResponse {
3822 index: 0,
3823 total: 21,
3824 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3827 stress_index: None,
3828 script_id: ScriptId::new(SmolStr::new("setup")).unwrap(),
3829 program: "setup".to_owned(),
3830 args: args.clone(),
3831 state: UnitState::Running {
3832 pid: 4567,
3833 time_taken: Duration::from_millis(1234),
3834 slow_after: None,
3835 },
3836 output: make_split_output(
3837 None,
3838 "script stdout 1",
3839 "script stderr 1",
3840 ),
3841 }),
3842 },
3843 })
3844 .unwrap();
3845
3846 reporter
3849 .write_event(&TestEvent {
3850 timestamp: Local::now().into(),
3851 elapsed: Duration::ZERO,
3852 kind: TestEventKind::InfoResponse {
3853 index: 1,
3854 total: 21,
3855 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3856 stress_index: None,
3857 script_id: ScriptId::new(SmolStr::new("setup-slow")).unwrap(),
3858 program: "setup-slow".to_owned(),
3859 args: args.clone(),
3860 state: UnitState::Running {
3861 pid: 4568,
3862 time_taken: Duration::from_millis(1234),
3863 slow_after: Some(Duration::from_millis(1000)),
3864 },
3865 output: make_combined_output_with_errors(
3866 None,
3867 "script output 2\n",
3868 vec![ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3869 std::io::Error::other("read stdout error"),
3870 )))],
3871 ),
3872 }),
3873 },
3874 })
3875 .unwrap();
3876
3877 reporter
3879 .write_event(&TestEvent {
3880 timestamp: Local::now().into(),
3881 elapsed: Duration::ZERO,
3882 kind: TestEventKind::InfoResponse {
3883 index: 2,
3884 total: 21,
3885 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3886 stress_index: None,
3887 script_id: ScriptId::new(SmolStr::new("setup-terminating"))
3888 .unwrap(),
3889 program: "setup-terminating".to_owned(),
3890 args: args.clone(),
3891 state: UnitState::Terminating(UnitTerminatingState {
3892 pid: 5094,
3893 time_taken: Duration::from_millis(1234),
3894 reason: UnitTerminateReason::Signal,
3895 method: UnitTerminateMethod::Fake,
3896 waiting_duration: Duration::from_millis(6789),
3897 remaining: Duration::from_millis(9786),
3898 }),
3899 output: make_split_output_with_errors(
3900 None,
3901 "script output 3\n",
3902 "script stderr 3\n",
3903 vec![
3904 ChildError::Fd(ChildFdError::ReadStdout(Arc::new(
3905 std::io::Error::other("read stdout error"),
3906 ))),
3907 ChildError::Fd(ChildFdError::ReadStderr(Arc::new(
3908 std::io::Error::other("read stderr error"),
3909 ))),
3910 ],
3911 ),
3912 }),
3913 },
3914 })
3915 .unwrap();
3916
3917 reporter
3921 .write_event(&TestEvent {
3922 timestamp: Local::now().into(),
3923 elapsed: Duration::ZERO,
3924 kind: TestEventKind::InfoResponse {
3925 index: 3,
3926 total: 21,
3927 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3928 stress_index: Some(StressIndex {
3929 current: 0,
3930 total: None,
3931 }),
3932 script_id: ScriptId::new(SmolStr::new("setup-exiting")).unwrap(),
3933 program: "setup-exiting".to_owned(),
3934 args: args.clone(),
3935 state: UnitState::Exiting {
3936 pid: 9987,
3937 time_taken: Duration::from_millis(1234),
3938 slow_after: Some(Duration::from_millis(1000)),
3939 tentative_result: Some(ExecutionResultDescription::ExecFail),
3943 waiting_duration: Duration::from_millis(10467),
3944 remaining: Duration::from_millis(335),
3945 },
3946 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3947 Arc::new(std::io::Error::other("exec error")),
3948 ))
3949 .into(),
3950 }),
3951 },
3952 })
3953 .unwrap();
3954
3955 reporter
3957 .write_event(&TestEvent {
3958 timestamp: Local::now().into(),
3959 elapsed: Duration::ZERO,
3960 kind: TestEventKind::InfoResponse {
3961 index: 4,
3962 total: 21,
3963 response: InfoResponse::SetupScript(SetupScriptInfoResponse {
3964 stress_index: Some(StressIndex {
3965 current: 1,
3966 total: Some(NonZero::new(3).unwrap()),
3967 }),
3968 script_id: ScriptId::new(SmolStr::new("setup-exited")).unwrap(),
3969 program: "setup-exited".to_owned(),
3970 args: args.clone(),
3971 state: UnitState::Exited {
3972 result: ExecutionResultDescription::Fail {
3973 failure: FailureDescription::ExitCode { code: 1 },
3974 leaked: true,
3975 },
3976 time_taken: Duration::from_millis(9999),
3977 slow_after: Some(Duration::from_millis(3000)),
3978 },
3979 output: ChildExecutionOutput::StartError(ChildStartError::Spawn(
3980 Arc::new(std::io::Error::other("exec error")),
3981 ))
3982 .into(),
3983 }),
3984 },
3985 })
3986 .unwrap();
3987
3988 reporter
3990 .write_event(&TestEvent {
3991 timestamp: Local::now().into(),
3992 elapsed: Duration::ZERO,
3993 kind: TestEventKind::InfoResponse {
3994 index: 5,
3995 total: 21,
3996 response: InfoResponse::Test(TestInfoResponse {
3997 stress_index: None,
3998 test_instance: TestInstanceId {
3999 binary_id: &binary_id,
4000 test_name: &test_name1,
4001 },
4002 retry_data: RetryData {
4003 attempt: 1,
4004 total_attempts: 1,
4005 },
4006 state: UnitState::Running {
4007 pid: 12345,
4008 time_taken: Duration::from_millis(400),
4009 slow_after: None,
4010 },
4011 output: make_split_output(None, "abc", "def"),
4012 }),
4013 },
4014 })
4015 .unwrap();
4016
4017 reporter
4019 .write_event(&TestEvent {
4020 timestamp: Local::now().into(),
4021 elapsed: Duration::ZERO,
4022 kind: TestEventKind::InfoResponse {
4023 index: 6,
4024 total: 21,
4025 response: InfoResponse::Test(TestInfoResponse {
4026 stress_index: Some(StressIndex {
4027 current: 0,
4028 total: None,
4029 }),
4030 test_instance: TestInstanceId {
4031 binary_id: &binary_id,
4032 test_name: &test_name2,
4033 },
4034 retry_data: RetryData {
4035 attempt: 2,
4036 total_attempts: 3,
4037 },
4038 state: UnitState::Terminating(UnitTerminatingState {
4039 pid: 12346,
4040 time_taken: Duration::from_millis(99999),
4041 reason: UnitTerminateReason::Timeout,
4042 method: UnitTerminateMethod::Fake,
4043 waiting_duration: Duration::from_millis(6789),
4044 remaining: Duration::from_millis(9786),
4045 }),
4046 output: make_split_output(None, "abc", "def"),
4047 }),
4048 },
4049 })
4050 .unwrap();
4051
4052 reporter
4054 .write_event(&TestEvent {
4055 timestamp: Local::now().into(),
4056 elapsed: Duration::ZERO,
4057 kind: TestEventKind::InfoResponse {
4058 index: 7,
4059 total: 21,
4060 response: InfoResponse::Test(TestInfoResponse {
4061 stress_index: None,
4062 test_instance: TestInstanceId {
4063 binary_id: &binary_id,
4064 test_name: &test_name3,
4065 },
4066 retry_data: RetryData {
4067 attempt: 2,
4068 total_attempts: 3,
4069 },
4070 state: UnitState::Exiting {
4071 pid: 99999,
4072 time_taken: Duration::from_millis(99999),
4073 slow_after: Some(Duration::from_millis(33333)),
4074 tentative_result: None,
4075 waiting_duration: Duration::from_millis(1),
4076 remaining: Duration::from_millis(999),
4077 },
4078 output: make_split_output(None, "abc", "def"),
4079 }),
4080 },
4081 })
4082 .unwrap();
4083
4084 reporter
4086 .write_event(&TestEvent {
4087 timestamp: Local::now().into(),
4088 elapsed: Duration::ZERO,
4089 kind: TestEventKind::InfoResponse {
4090 index: 8,
4091 total: 21,
4092 response: InfoResponse::Test(TestInfoResponse {
4093 stress_index: Some(StressIndex {
4094 current: 1,
4095 total: Some(NonZero::new(3).unwrap()),
4096 }),
4097 test_instance: TestInstanceId {
4098 binary_id: &binary_id,
4099 test_name: &test_name4,
4100 },
4101 retry_data: RetryData {
4102 attempt: 1,
4103 total_attempts: 5,
4104 },
4105 state: UnitState::Exited {
4106 result: ExecutionResultDescription::Pass,
4107 time_taken: Duration::from_millis(99999),
4108 slow_after: Some(Duration::from_millis(33333)),
4109 },
4110 output: make_combined_output_with_errors(
4111 Some(ExecutionResult::Pass),
4112 "abc\ndef\nghi\n",
4113 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
4114 std::io::Error::other("error waiting"),
4115 )))],
4116 ),
4117 }),
4118 },
4119 })
4120 .unwrap();
4121
4122 reporter
4124 .write_event(&TestEvent {
4125 timestamp: Local::now().into(),
4126 elapsed: Duration::ZERO,
4127 kind: TestEventKind::InfoResponse {
4128 index: 9,
4129 total: 21,
4130 response: InfoResponse::Test(TestInfoResponse {
4131 stress_index: None,
4132 test_instance: TestInstanceId {
4133 binary_id: &binary_id,
4134 test_name: &test_name4,
4135 },
4136 retry_data: RetryData {
4137 attempt: 1,
4141 total_attempts: 5,
4142 },
4143 state: UnitState::DelayBeforeNextAttempt {
4144 previous_result: ExecutionResultDescription::ExecFail,
4145 previous_slow: true,
4146 waiting_duration: Duration::from_millis(1234),
4147 remaining: Duration::from_millis(5678),
4148 },
4149 output: make_combined_output_with_errors(
4152 Some(ExecutionResult::Pass),
4153 "*** THIS OUTPUT SHOULD BE IGNORED",
4154 vec![ChildError::Fd(ChildFdError::Wait(Arc::new(
4155 std::io::Error::other(
4156 "*** THIS ERROR SHOULD ALSO BE IGNORED",
4157 ),
4158 )))],
4159 ),
4160 }),
4161 },
4162 })
4163 .unwrap();
4164
4165 reporter
4167 .write_event(&TestEvent {
4168 timestamp: Local::now().into(),
4169 elapsed: Duration::ZERO,
4170 kind: TestEventKind::InfoResponse {
4171 index: 10,
4172 total: 21,
4173 response: InfoResponse::Test(TestInfoResponse {
4174 stress_index: None,
4175 test_instance: TestInstanceId {
4176 binary_id: &binary_id,
4177 test_name: &test_name5,
4178 },
4179 retry_data: RetryData {
4180 attempt: 1,
4181 total_attempts: 1,
4182 },
4183 state: UnitState::Exited {
4184 result: ExecutionResultDescription::Fail {
4185 failure: FailureDescription::Abort {
4186 abort: AbortDescription::UnixSignal {
4187 signal: 11,
4188 name: Some("SEGV".into()),
4189 },
4190 },
4191 leaked: true,
4192 },
4193 time_taken: Duration::from_millis(5678),
4194 slow_after: None,
4195 },
4196 output: make_split_output(None, "segfault output", ""),
4197 }),
4198 },
4199 })
4200 .unwrap();
4201
4202 reporter
4203 .write_event(&TestEvent {
4204 timestamp: Local::now().into(),
4205 elapsed: Duration::ZERO,
4206 kind: TestEventKind::InfoFinished { missing: 2 },
4207 })
4208 .unwrap();
4209 },
4210 &mut out,
4211 );
4212
4213 insta::assert_snapshot!("info_response_output", out,);
4214 }
4215
4216 #[test]
4217 fn verbose_command_line() {
4218 let binary_id = RustBinaryId::new("my-binary-id");
4219 let test_name = TestCaseName::new("test_name");
4220 let test_with_spaces = TestCaseName::new("test_with_spaces");
4221 let test_special_chars = TestCaseName::new("test_special_chars");
4222 let test_retry = TestCaseName::new("test_retry");
4223 let mut out = String::new();
4224
4225 with_verbose_reporter(
4226 |mut reporter| {
4227 let current_stats = RunStats {
4228 initial_run_count: 10,
4229 finished_count: 0,
4230 ..Default::default()
4231 };
4232
4233 reporter
4235 .write_event(&TestEvent {
4236 timestamp: Local::now().into(),
4237 elapsed: Duration::ZERO,
4238 kind: TestEventKind::TestStarted {
4239 stress_index: None,
4240 test_instance: TestInstanceId {
4241 binary_id: &binary_id,
4242 test_name: &test_name,
4243 },
4244 slot_assignment: global_slot_assignment(0),
4245 current_stats,
4246 running: 1,
4247 command_line: vec![
4248 "/path/to/binary".to_string(),
4249 "--exact".to_string(),
4250 "test_name".to_string(),
4251 ],
4252 },
4253 })
4254 .unwrap();
4255
4256 reporter
4258 .write_event(&TestEvent {
4259 timestamp: Local::now().into(),
4260 elapsed: Duration::ZERO,
4261 kind: TestEventKind::TestStarted {
4262 stress_index: None,
4263 test_instance: TestInstanceId {
4264 binary_id: &binary_id,
4265 test_name: &test_with_spaces,
4266 },
4267 slot_assignment: global_slot_assignment(1),
4268 current_stats,
4269 running: 2,
4270 command_line: vec![
4271 "/path/to/binary".to_string(),
4272 "--exact".to_string(),
4273 "test with spaces".to_string(),
4274 "--flag=value".to_string(),
4275 ],
4276 },
4277 })
4278 .unwrap();
4279
4280 reporter
4282 .write_event(&TestEvent {
4283 timestamp: Local::now().into(),
4284 elapsed: Duration::ZERO,
4285 kind: TestEventKind::TestStarted {
4286 stress_index: None,
4287 test_instance: TestInstanceId {
4288 binary_id: &binary_id,
4289 test_name: &test_special_chars,
4290 },
4291 slot_assignment: global_slot_assignment(2),
4292 current_stats,
4293 running: 3,
4294 command_line: vec![
4295 "/path/to/binary".to_string(),
4296 "test\"with\"quotes".to_string(),
4297 "test'with'single".to_string(),
4298 ],
4299 },
4300 })
4301 .unwrap();
4302
4303 reporter
4305 .write_event(&TestEvent {
4306 timestamp: Local::now().into(),
4307 elapsed: Duration::ZERO,
4308 kind: TestEventKind::TestRetryStarted {
4309 stress_index: None,
4310 test_instance: TestInstanceId {
4311 binary_id: &binary_id,
4312 test_name: &test_retry,
4313 },
4314 slot_assignment: global_slot_assignment(0),
4315 retry_data: RetryData {
4316 attempt: 2,
4317 total_attempts: 3,
4318 },
4319 running: 1,
4320 command_line: vec![
4321 "/path/to/binary".to_string(),
4322 "--exact".to_string(),
4323 "test_retry".to_string(),
4324 ],
4325 },
4326 })
4327 .unwrap();
4328
4329 reporter
4331 .write_event(&TestEvent {
4332 timestamp: Local::now().into(),
4333 elapsed: Duration::ZERO,
4334 kind: TestEventKind::TestRetryStarted {
4335 stress_index: None,
4336 test_instance: TestInstanceId {
4337 binary_id: &binary_id,
4338 test_name: &test_retry,
4339 },
4340 slot_assignment: global_slot_assignment(0),
4341 retry_data: RetryData {
4342 attempt: 3,
4343 total_attempts: 3,
4344 },
4345 running: 1,
4346 command_line: vec![
4347 "/path/to/binary".to_string(),
4348 "--exact".to_string(),
4349 "test_retry".to_string(),
4350 ],
4351 },
4352 })
4353 .unwrap();
4354 },
4355 &mut out,
4356 );
4357
4358 insta::assert_snapshot!("verbose_command_line", out);
4359 }
4360
4361 #[test]
4362 fn no_capture_settings() {
4363 let mut out = String::new();
4365
4366 with_reporter(
4367 |reporter| {
4368 assert!(reporter.inner.no_capture, "no_capture is true");
4369 let overrides = reporter.inner.unit_output.overrides();
4370 assert_eq!(
4371 overrides.force_failure_output,
4372 Some(TestOutputDisplay::Never),
4373 "failure output is never, overriding other settings"
4374 );
4375 assert_eq!(
4376 overrides.force_success_output,
4377 Some(TestOutputDisplay::Never),
4378 "success output is never, overriding other settings"
4379 );
4380 assert_eq!(
4381 reporter.inner.status_levels.status_level,
4382 StatusLevel::Pass,
4383 "status level is pass, overriding other settings"
4384 );
4385 },
4386 &mut out,
4387 );
4388 }
4389
4390 fn write_test_slow_events<'a>(
4398 reporter: &mut DisplayReporter<'a>,
4399 binary_id: &'a RustBinaryId,
4400 test_name: &'a TestCaseName,
4401 ) {
4402 reporter
4404 .write_event(&TestEvent {
4405 timestamp: Local::now().into(),
4406 elapsed: Duration::ZERO,
4407 kind: TestEventKind::TestSlow {
4408 stress_index: None,
4409 test_instance: TestInstanceId {
4410 binary_id,
4411 test_name,
4412 },
4413 retry_data: RetryData {
4414 attempt: 1,
4415 total_attempts: 1,
4416 },
4417 elapsed: Duration::from_secs(60),
4418 will_terminate: false,
4419 },
4420 })
4421 .unwrap();
4422
4423 reporter
4425 .write_event(&TestEvent {
4426 timestamp: Local::now().into(),
4427 elapsed: Duration::ZERO,
4428 kind: TestEventKind::TestSlow {
4429 stress_index: None,
4430 test_instance: TestInstanceId {
4431 binary_id,
4432 test_name,
4433 },
4434 retry_data: RetryData {
4435 attempt: 1,
4436 total_attempts: 3,
4437 },
4438 elapsed: Duration::from_secs(60),
4439 will_terminate: false,
4440 },
4441 })
4442 .unwrap();
4443
4444 reporter
4446 .write_event(&TestEvent {
4447 timestamp: Local::now().into(),
4448 elapsed: Duration::ZERO,
4449 kind: TestEventKind::TestSlow {
4450 stress_index: None,
4451 test_instance: TestInstanceId {
4452 binary_id,
4453 test_name,
4454 },
4455 retry_data: RetryData {
4456 attempt: 2,
4457 total_attempts: 3,
4458 },
4459 elapsed: Duration::from_secs(60),
4460 will_terminate: false,
4461 },
4462 })
4463 .unwrap();
4464
4465 reporter
4467 .write_event(&TestEvent {
4468 timestamp: Local::now().into(),
4469 elapsed: Duration::ZERO,
4470 kind: TestEventKind::TestSlow {
4471 stress_index: None,
4472 test_instance: TestInstanceId {
4473 binary_id,
4474 test_name,
4475 },
4476 retry_data: RetryData {
4477 attempt: 3,
4478 total_attempts: 3,
4479 },
4480 elapsed: Duration::from_secs(60),
4481 will_terminate: false,
4482 },
4483 })
4484 .unwrap();
4485
4486 reporter
4492 .write_event(&TestEvent {
4493 timestamp: Local::now().into(),
4494 elapsed: Duration::ZERO,
4495 kind: TestEventKind::TestSlow {
4496 stress_index: None,
4497 test_instance: TestInstanceId {
4498 binary_id,
4499 test_name,
4500 },
4501 retry_data: RetryData {
4502 attempt: 1,
4503 total_attempts: 1,
4504 },
4505 elapsed: Duration::from_secs(120),
4506 will_terminate: true,
4507 },
4508 })
4509 .unwrap();
4510
4511 reporter
4514 .write_event(&TestEvent {
4515 timestamp: Local::now().into(),
4516 elapsed: Duration::ZERO,
4517 kind: TestEventKind::TestSlow {
4518 stress_index: None,
4519 test_instance: TestInstanceId {
4520 binary_id,
4521 test_name,
4522 },
4523 retry_data: RetryData {
4524 attempt: 1,
4525 total_attempts: 3,
4526 },
4527 elapsed: Duration::from_secs(120),
4528 will_terminate: true,
4529 },
4530 })
4531 .unwrap();
4532
4533 reporter
4535 .write_event(&TestEvent {
4536 timestamp: Local::now().into(),
4537 elapsed: Duration::ZERO,
4538 kind: TestEventKind::TestSlow {
4539 stress_index: None,
4540 test_instance: TestInstanceId {
4541 binary_id,
4542 test_name,
4543 },
4544 retry_data: RetryData {
4545 attempt: 2,
4546 total_attempts: 3,
4547 },
4548 elapsed: Duration::from_secs(120),
4549 will_terminate: true,
4550 },
4551 })
4552 .unwrap();
4553
4554 reporter
4556 .write_event(&TestEvent {
4557 timestamp: Local::now().into(),
4558 elapsed: Duration::ZERO,
4559 kind: TestEventKind::TestSlow {
4560 stress_index: None,
4561 test_instance: TestInstanceId {
4562 binary_id,
4563 test_name,
4564 },
4565 retry_data: RetryData {
4566 attempt: 3,
4567 total_attempts: 3,
4568 },
4569 elapsed: Duration::from_secs(120),
4570 will_terminate: true,
4571 },
4572 })
4573 .unwrap();
4574 }
4575
4576 fn write_setup_script_slow_events(reporter: &mut DisplayReporter<'_>) {
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(60),
4593 will_terminate: false,
4594 },
4595 })
4596 .unwrap();
4597
4598 reporter
4600 .write_event(&TestEvent {
4601 timestamp: Local::now().into(),
4602 elapsed: Duration::ZERO,
4603 kind: TestEventKind::SetupScriptSlow {
4604 stress_index: None,
4605 script_id: ScriptId::new(SmolStr::new("my-script")).unwrap(),
4606 program: "my-program".to_owned(),
4607 args: vec!["--arg1".to_owned()],
4608 elapsed: Duration::from_secs(120),
4609 will_terminate: true,
4610 },
4611 })
4612 .unwrap();
4613 }
4614
4615 #[test_case(StatusLevel::None; "none")]
4626 #[test_case(StatusLevel::Fail; "fail")]
4627 #[test_case(StatusLevel::Retry; "retry")]
4628 #[test_case(StatusLevel::Slow; "slow")]
4629 #[test_case(StatusLevel::Pass; "pass")]
4630 fn test_slow_status_levels(status_level: StatusLevel) {
4631 let binary_id = RustBinaryId::new("my-binary-id");
4632 let test_name = TestCaseName::new("test_name");
4633 let mut out = String::new();
4634
4635 with_reporter_at_status_level(
4636 |mut reporter| {
4637 write_test_slow_events(&mut reporter, &binary_id, &test_name);
4638 write_setup_script_slow_events(&mut reporter);
4639 },
4640 &mut out,
4641 status_level,
4642 );
4643
4644 let label = match status_level {
4647 StatusLevel::None => "none",
4648 StatusLevel::Fail => "fail",
4649 StatusLevel::Retry => "retry",
4650 StatusLevel::Slow => "slow",
4651 StatusLevel::Pass => "pass",
4652 _ => unreachable!("test only covers these levels"),
4653 };
4654 insta::assert_snapshot!(format!("test_slow_status_level_{label}"), out);
4655 }
4656
4657 #[test]
4658 fn run_begin_cancel_and_kill_lines() {
4659 let cases = [
4660 (RunStopKind::Cancel, Some(CancelReason::TestFailure), 0, 1),
4661 (RunStopKind::Cancel, Some(CancelReason::GlobalTimeout), 0, 1),
4662 (RunStopKind::Cancel, Some(CancelReason::Interrupt), 0, 3),
4663 (RunStopKind::Cancel, Some(CancelReason::Interrupt), 1, 0),
4664 (RunStopKind::Cancel, Some(CancelReason::Signal), 2, 0),
4665 (
4666 RunStopKind::Cancel,
4667 Some(CancelReason::SetupScriptFailure),
4668 0,
4669 0,
4670 ),
4671 (
4672 RunStopKind::Cancel,
4673 Some(CancelReason::TestFailureImmediate),
4674 0,
4675 1,
4676 ),
4677 (
4678 RunStopKind::Cancel,
4679 Some(CancelReason::TestFailureImmediate),
4680 0,
4681 0,
4682 ),
4683 (RunStopKind::Cancel, None, 0, 1),
4684 (RunStopKind::Cancel, None, 0, 0),
4685 (RunStopKind::Kill, Some(CancelReason::SecondSignal), 0, 1),
4686 (RunStopKind::Kill, Some(CancelReason::SecondSignal), 0, 3),
4687 (RunStopKind::Kill, Some(CancelReason::SecondSignal), 1, 0),
4688 (RunStopKind::Kill, Some(CancelReason::SecondSignal), 2, 0),
4689 (RunStopKind::Kill, Some(CancelReason::SecondSignal), 0, 0),
4690 (
4691 RunStopKind::Kill,
4692 Some(CancelReason::TestFailureImmediate),
4693 0,
4694 1,
4695 ),
4696 (RunStopKind::Kill, None, 0, 1),
4697 (RunStopKind::Kill, None, 0, 0),
4698 ];
4699
4700 let mut snapshot = String::new();
4701 for (stop_kind, cancel_reason, setup_scripts_running, running) in cases {
4702 let current_stats = RunStats {
4703 cancel_reason,
4704 ..RunStats::default()
4705 };
4706 let kind = match stop_kind {
4707 RunStopKind::Cancel => TestEventKind::RunBeginCancel {
4708 setup_scripts_running,
4709 current_stats,
4710 running,
4711 },
4712 RunStopKind::Kill => TestEventKind::RunBeginKill {
4713 setup_scripts_running,
4714 current_stats,
4715 running,
4716 },
4717 };
4718
4719 let mut out = String::new();
4720 with_reporter(
4721 |mut reporter| {
4722 reporter
4723 .write_event(&TestEvent {
4724 timestamp: Local::now().into(),
4725 elapsed: Duration::ZERO,
4726 kind,
4727 })
4728 .unwrap();
4729 },
4730 &mut out,
4731 );
4732
4733 swriteln!(
4736 snapshot,
4737 "{stop_kind:?}, cancel_reason: {cancel_reason:?}, setup_scripts_running: \
4738 {setup_scripts_running}, running: {running}\n {out:?}",
4739 );
4740 }
4741
4742 insta::assert_snapshot!("run_begin_cancel_and_kill_lines", snapshot);
4743 }
4744
4745 #[test]
4746 fn sort_final_outputs_counter_flag() {
4747 let binary_a = RustBinaryId::new("aaa");
4752 let binary_b = RustBinaryId::new("bbb");
4753 let test_x = TestCaseName::new("test_x");
4754 let test_y = TestCaseName::new("test_y");
4755
4756 let mut entries = vec![
4757 FinalOutputEntry {
4758 stress_index: None,
4759 counter: TestInstanceCounter::Counter {
4760 current: 99,
4761 total: 100,
4762 },
4763 instance: TestInstanceId {
4764 binary_id: &binary_b,
4765 test_name: &test_y,
4766 },
4767 output: make_pass_output(),
4768 },
4769 FinalOutputEntry {
4770 stress_index: None,
4771 counter: TestInstanceCounter::Counter {
4772 current: 1,
4773 total: 100,
4774 },
4775 instance: TestInstanceId {
4776 binary_id: &binary_a,
4777 test_name: &test_y,
4778 },
4779 output: make_pass_output(),
4780 },
4781 FinalOutputEntry {
4782 stress_index: None,
4783 counter: TestInstanceCounter::Counter {
4784 current: 50,
4785 total: 100,
4786 },
4787 instance: TestInstanceId {
4788 binary_id: &binary_a,
4789 test_name: &test_x,
4790 },
4791 output: make_pass_output(),
4792 },
4793 FinalOutputEntry {
4794 stress_index: None,
4795 counter: TestInstanceCounter::Counter {
4796 current: 50,
4797 total: 100,
4798 },
4799 instance: TestInstanceId {
4800 binary_id: &binary_b,
4801 test_name: &test_x,
4802 },
4803 output: make_pass_output(),
4804 },
4805 ];
4806
4807 sort_final_outputs(&mut entries, false);
4809 assert_eq!(
4810 extract_ids(&entries),
4811 vec![
4812 ("aaa", "test_x"),
4813 ("aaa", "test_y"),
4814 ("bbb", "test_x"),
4815 ("bbb", "test_y"),
4816 ],
4817 "without counter, sort is purely by instance"
4818 );
4819
4820 sort_final_outputs(&mut entries, true);
4823 assert_eq!(
4824 extract_ids(&entries),
4825 vec![
4826 ("aaa", "test_y"),
4827 ("aaa", "test_x"),
4828 ("bbb", "test_x"),
4829 ("bbb", "test_y"),
4830 ],
4831 "with counter, sort by counter first, then by instance as tiebreaker"
4832 );
4833 }
4834
4835 #[test]
4836 fn sort_final_outputs_mixed_status_levels() {
4837 let binary_a = RustBinaryId::new("aaa");
4841 let binary_b = RustBinaryId::new("bbb");
4842 let binary_c = RustBinaryId::new("ccc");
4843 let test_1 = TestCaseName::new("test_1");
4844
4845 let mut entries = vec![
4846 FinalOutputEntry {
4847 stress_index: None,
4848 counter: TestInstanceCounter::Counter {
4849 current: 1,
4850 total: 100,
4851 },
4852 instance: TestInstanceId {
4853 binary_id: &binary_a,
4854 test_name: &test_1,
4855 },
4856 output: make_fail_output(),
4857 },
4858 FinalOutputEntry {
4859 stress_index: None,
4860 counter: TestInstanceCounter::Counter {
4861 current: 2,
4862 total: 100,
4863 },
4864 instance: TestInstanceId {
4865 binary_id: &binary_b,
4866 test_name: &test_1,
4867 },
4868 output: make_skip_output(),
4869 },
4870 FinalOutputEntry {
4871 stress_index: None,
4872 counter: TestInstanceCounter::Counter {
4873 current: 3,
4874 total: 100,
4875 },
4876 instance: TestInstanceId {
4877 binary_id: &binary_c,
4878 test_name: &test_1,
4879 },
4880 output: make_pass_output(),
4881 },
4882 ];
4883
4884 sort_final_outputs(&mut entries, false);
4886 assert_eq!(
4887 extract_ids(&entries),
4888 vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4889 "pass first, then skip, then fail (reversed status level)"
4890 );
4891
4892 entries.swap(0, 2);
4895 sort_final_outputs(&mut entries, true);
4896 assert_eq!(
4897 extract_ids(&entries),
4898 vec![("ccc", "test_1"), ("bbb", "test_1"), ("aaa", "test_1")],
4899 "with counter, status level still dominates"
4900 );
4901 }
4902
4903 #[test]
4904 fn sort_final_outputs_stress_indexes() {
4905 let binary_a = RustBinaryId::new("aaa");
4907 let test_1 = TestCaseName::new("test_1");
4908 let test_2 = TestCaseName::new("test_2");
4909
4910 let mut entries = vec![
4911 FinalOutputEntry {
4912 stress_index: Some(StressIndex {
4913 current: 2,
4914 total: None,
4915 }),
4916 counter: TestInstanceCounter::Counter {
4917 current: 1,
4918 total: 100,
4919 },
4920 instance: TestInstanceId {
4921 binary_id: &binary_a,
4922 test_name: &test_1,
4923 },
4924 output: make_pass_output(),
4925 },
4926 FinalOutputEntry {
4927 stress_index: Some(StressIndex {
4928 current: 0,
4929 total: None,
4930 }),
4931 counter: TestInstanceCounter::Counter {
4932 current: 3,
4933 total: 100,
4934 },
4935 instance: TestInstanceId {
4936 binary_id: &binary_a,
4937 test_name: &test_2,
4938 },
4939 output: make_pass_output(),
4940 },
4941 FinalOutputEntry {
4942 stress_index: Some(StressIndex {
4943 current: 0,
4944 total: None,
4945 }),
4946 counter: TestInstanceCounter::Counter {
4947 current: 2,
4948 total: 100,
4949 },
4950 instance: TestInstanceId {
4951 binary_id: &binary_a,
4952 test_name: &test_1,
4953 },
4954 output: make_pass_output(),
4955 },
4956 ];
4957
4958 sort_final_outputs(&mut entries, false);
4959 assert_eq!(
4960 extract_ids(&entries),
4961 vec![("aaa", "test_1"), ("aaa", "test_2"), ("aaa", "test_1")],
4962 "stress index 0 entries come before stress index 2"
4963 );
4964 let stress_indexes: Vec<_> = entries
4966 .iter()
4967 .map(|e| e.stress_index.unwrap().current)
4968 .collect();
4969 assert_eq!(
4970 stress_indexes,
4971 vec![0, 0, 2],
4972 "stress indexes are sorted correctly"
4973 );
4974 }
4975}
4976
4977#[cfg(all(windows, test))]
4978mod windows_tests {
4979 use super::*;
4980 use crate::reporter::events::AbortDescription;
4981 use windows_sys::Win32::{
4982 Foundation::{STATUS_CONTROL_C_EXIT, STATUS_CONTROL_STACK_VIOLATION},
4983 Globalization::SetThreadUILanguage,
4984 };
4985
4986 #[test]
4987 fn test_write_windows_abort_line() {
4988 unsafe {
4989 SetThreadUILanguage(0x0409);
4991 }
4992
4993 insta::assert_snapshot!(
4994 "ctrl_c_code",
4995 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_C_EXIT))
4996 );
4997 insta::assert_snapshot!(
4998 "stack_violation_code",
4999 to_abort_line(AbortStatus::WindowsNtStatus(STATUS_CONTROL_STACK_VIOLATION)),
5000 );
5001 insta::assert_snapshot!("job_object", to_abort_line(AbortStatus::JobObject));
5002 }
5003
5004 #[track_caller]
5005 fn to_abort_line(status: AbortStatus) -> String {
5006 let mut buf = String::new();
5007 let description = AbortDescription::from(status);
5008 write_windows_abort_line(&description, &Styles::default(), &mut buf).unwrap();
5009 buf
5010 }
5011}