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