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