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