1use crate::{
5 cargo_config::{CargoConfigs, DiscoveredConfig},
6 helpers::{DisplayTestInstance, plural},
7 list::TestInstanceId,
8 reporter::{
9 displayer::formatters::DisplayBracketedHhMmSs,
10 events::*,
11 helpers::{Styles, print_lines_in_chunks},
12 },
13};
14use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
15use nextest_metadata::RustBinaryId;
16use owo_colors::OwoColorize;
17use std::{
18 cmp::{max, min},
19 env, fmt,
20 io::IsTerminal,
21 str::FromStr,
22 time::{Duration, Instant},
23};
24use swrite::{SWrite, swrite};
25use tracing::debug;
26
27const PROGRESS_REFRESH_RATE_HZ: u8 = 1;
39
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum MaxProgressRunning {
44 Count(usize),
47
48 Infinite,
50}
51
52impl MaxProgressRunning {
53 pub const DEFAULT_VALUE: Self = Self::Count(8);
55}
56
57impl Default for MaxProgressRunning {
58 fn default() -> Self {
59 Self::DEFAULT_VALUE
60 }
61}
62
63impl FromStr for MaxProgressRunning {
64 type Err = String;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 if s.eq_ignore_ascii_case("infinite") {
68 return Ok(Self::Infinite);
69 }
70
71 match s.parse::<usize>() {
72 Err(e) => Err(format!("Error: {e} parsing {s}")),
73 Ok(n) => Ok(Self::Count(n)),
74 }
75 }
76}
77
78impl fmt::Display for MaxProgressRunning {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::Infinite => write!(f, "infinite"),
82 Self::Count(n) => write!(f, "{n}"),
83 }
84 }
85}
86
87#[derive(Default, Clone, Copy, Debug)]
89pub enum ShowProgress {
90 #[default]
92 Auto,
93
94 None,
96
97 Counter,
99
100 Running,
102}
103
104#[derive(Debug)]
105pub(super) enum RunningTestStatus {
106 Running,
107 Slow,
108 Delay(Duration),
109 Retry,
110}
111
112#[derive(Debug)]
113pub(super) struct RunningTest {
114 binary_id: RustBinaryId,
115 test_name: String,
116 status: RunningTestStatus,
117 start_time: Instant,
118 paused_for: Duration,
119}
120
121impl RunningTest {
122 fn message(&self, now: Instant, width: usize, styles: &Styles) -> String {
123 let mut elapsed = (now - self.start_time).saturating_sub(self.paused_for);
124 let status = match self.status {
125 RunningTestStatus::Running => " ".to_owned(),
126 RunningTestStatus::Slow => " SLOW".style(styles.skip).to_string(),
127 RunningTestStatus::Delay(d) => {
128 elapsed = d.saturating_sub(elapsed);
132 "DELAY".style(styles.retry).to_string()
133 }
134 RunningTestStatus::Retry => "RETRY".style(styles.retry).to_string(),
135 };
136 let elapsed = format!(
137 "{:0>2}:{:0>2}:{:0>2}",
138 elapsed.as_secs() / 3600,
139 elapsed.as_secs() / 60,
140 elapsed.as_secs() % 60,
141 );
142 let max_width = width.saturating_sub(25);
143 let test = DisplayTestInstance::new(
144 None,
145 None,
146 TestInstanceId {
147 binary_id: &self.binary_id,
148
149 test_name: &self.test_name,
150 },
151 &styles.list_styles,
152 )
153 .with_max_width(max_width);
154 format!(" {} [{:>9}] {}", status, elapsed, test)
155 }
156}
157
158#[derive(Debug)]
159pub(super) struct ProgressBarState {
160 bar: ProgressBar,
161 stats: RunStats,
162 running: usize,
163 max_progress_running: MaxProgressRunning,
164 max_running_displayed: usize,
168 running_tests: Option<Vec<RunningTest>>,
170 buffer: String,
171 println_chunk_size: usize,
174 hidden_no_capture: bool,
184 hidden_run_paused: bool,
185 hidden_info_response: bool,
186}
187
188impl ProgressBarState {
189 pub(super) fn new(
190 test_count: usize,
191 progress_chars: &str,
192 max_progress_running: MaxProgressRunning,
193 ) -> Self {
194 let bar = ProgressBar::new(test_count as u64);
195 let test_count_width = format!("{test_count}").len();
196 let template = format!(
201 "{{prefix:>12}} [{{elapsed_precise:>9}}] {{wide_bar}} \
202 {{pos:>{test_count_width}}}/{{len:{test_count_width}}}: {{msg}}"
203 );
204 bar.set_style(
205 ProgressStyle::default_bar()
206 .progress_chars(progress_chars)
207 .template(&template)
208 .expect("template is known to be valid"),
209 );
210
211 let running_tests =
212 (!matches!(max_progress_running, MaxProgressRunning::Count(0))).then(Vec::new);
213
214 let println_chunk_size = env::var("__NEXTEST_PROGRESS_PRINTLN_CHUNK_SIZE")
218 .ok()
219 .and_then(|s| s.parse::<usize>().ok())
220 .unwrap_or(4096);
221
222 Self {
223 bar,
224 stats: RunStats::default(),
225 running: 0,
226 max_progress_running,
227 max_running_displayed: 0,
228 running_tests,
229 buffer: String::new(),
230 println_chunk_size,
231 hidden_no_capture: false,
232 hidden_run_paused: false,
233 hidden_info_response: false,
234 }
235 }
236
237 pub(super) fn tick(&mut self, styles: &Styles) {
238 self.update_message(styles);
239 self.print_and_clear_buffer();
240 }
241
242 fn print_and_clear_buffer(&mut self) {
243 self.print_and_force_redraw();
244 self.buffer.clear();
245 }
246
247 fn print_and_force_redraw(&self) {
249 if self.buffer.is_empty() {
250 self.bar.force_draw();
253 return;
254 }
255
256 print_lines_in_chunks(&self.buffer, self.println_chunk_size, |chunk| {
270 self.bar.println(chunk);
271 });
272 }
273
274 fn update_message(&mut self, styles: &Styles) {
275 let mut msg = progress_bar_msg(&self.stats, self.running, styles);
276 msg += " ";
277
278 if let Some(running_tests) = &self.running_tests {
279 let (_, width) = console::Term::stderr().size();
280 let width = max(width as usize, 40);
281 let now = Instant::now();
282 let mut count = match self.max_progress_running {
283 MaxProgressRunning::Count(count) => min(running_tests.len(), count),
284 MaxProgressRunning::Infinite => running_tests.len(),
285 };
286 for running_test in &running_tests[..count] {
287 msg.push('\n');
288 msg.push_str(&running_test.message(now, width, styles));
289 }
290 if count < running_tests.len() {
291 let overflow_count = running_tests.len() - count;
292 msg.push_str(&format!(
293 "\n ... and {} more {} running",
294 overflow_count.style(styles.count),
295 plural::tests_str(overflow_count),
296 ));
297 count += 1;
298 }
299 self.max_running_displayed = max(self.max_running_displayed, count);
300 msg.push_str(&"\n".to_string().repeat(self.max_running_displayed - count));
301 }
302 self.bar.set_message(msg);
303 }
304
305 pub(super) fn update_progress_bar(&mut self, event: &TestEvent<'_>, styles: &Styles) {
306 let before_should_hide = self.should_hide();
307
308 match &event.kind {
309 TestEventKind::StressSubRunStarted { .. } => {
310 self.bar.reset();
311 }
312 TestEventKind::StressSubRunFinished { .. } => {
313 self.bar.finish_and_clear();
316 }
317 TestEventKind::SetupScriptStarted { no_capture, .. } => {
318 if *no_capture {
320 self.hidden_no_capture = true;
321 }
322 }
323 TestEventKind::SetupScriptFinished { no_capture, .. } => {
324 if *no_capture {
326 self.hidden_no_capture = false;
327 }
328 }
329 TestEventKind::TestStarted {
330 current_stats,
331 running,
332 test_instance,
333 ..
334 } => {
335 self.running = *running;
336
337 self.bar.set_prefix(progress_bar_prefix(
338 current_stats,
339 current_stats.cancel_reason,
340 styles,
341 ));
342 self.bar.set_length(current_stats.initial_run_count as u64);
345 self.bar.set_position(current_stats.finished_count as u64);
346
347 if let Some(running_tests) = &mut self.running_tests {
348 running_tests.push(RunningTest {
349 binary_id: test_instance.id().binary_id.clone(),
350 test_name: test_instance.id().test_name.to_owned(),
351 status: RunningTestStatus::Running,
352 start_time: Instant::now(),
353 paused_for: Duration::ZERO,
354 });
355 }
356 }
357 TestEventKind::TestFinished {
358 current_stats,
359 running,
360 test_instance,
361 ..
362 } => {
363 self.running = *running;
364 self.remove_test(&test_instance.id());
365
366 self.bar.set_prefix(progress_bar_prefix(
367 current_stats,
368 current_stats.cancel_reason,
369 styles,
370 ));
371 self.bar.set_length(current_stats.initial_run_count as u64);
374 self.bar.set_position(current_stats.finished_count as u64);
375 }
376 TestEventKind::TestAttemptFailedWillRetry {
377 test_instance,
378 delay_before_next_attempt,
379 ..
380 } => {
381 self.remove_test(&test_instance.id());
382 if let Some(running_tests) = &mut self.running_tests {
383 running_tests.push(RunningTest {
384 binary_id: test_instance.id().binary_id.clone(),
385 test_name: test_instance.id().test_name.to_owned(),
386 status: RunningTestStatus::Delay(*delay_before_next_attempt),
387 start_time: Instant::now(),
388 paused_for: Duration::ZERO,
389 });
390 }
391 }
392 TestEventKind::TestRetryStarted { test_instance, .. } => {
393 self.remove_test(&test_instance.id());
394 if let Some(running_tests) = &mut self.running_tests {
395 running_tests.push(RunningTest {
396 binary_id: test_instance.id().binary_id.clone(),
397 test_name: test_instance.id().test_name.to_owned(),
398 status: RunningTestStatus::Retry,
399 start_time: Instant::now(),
400 paused_for: Duration::ZERO,
401 });
402 }
403 }
404 TestEventKind::TestSlow { test_instance, .. } => {
405 if let Some(running_tests) = &mut self.running_tests {
406 running_tests
407 .iter_mut()
408 .find(|rt| {
409 &rt.binary_id == test_instance.id().binary_id
410 && rt.test_name == test_instance.id().test_name
411 })
412 .expect("a slow test to be already running")
413 .status = RunningTestStatus::Slow;
414 }
415 }
416 TestEventKind::InfoStarted { .. } => {
417 self.hidden_info_response = true;
420 }
421 TestEventKind::InfoFinished { .. } => {
422 self.hidden_info_response = false;
424 }
425 TestEventKind::RunPaused { .. } => {
426 self.hidden_run_paused = true;
429 }
430 TestEventKind::RunContinued { .. } => {
431 self.hidden_run_paused = false;
434 let current_global_elapsed = self.bar.elapsed();
435 self.bar.set_elapsed(event.elapsed);
436
437 if let Some(running_tests) = &mut self.running_tests {
438 let delta = current_global_elapsed.saturating_sub(event.elapsed);
439 for running_test in running_tests {
440 running_test.paused_for += delta;
441 }
442 }
443 }
444 TestEventKind::RunBeginCancel { current_stats, .. }
445 | TestEventKind::RunBeginKill { current_stats, .. } => {
446 self.bar.set_prefix(progress_bar_cancel_prefix(
447 current_stats.cancel_reason,
448 styles,
449 ));
450 }
451 _ => {}
452 }
453
454 let after_should_hide = self.should_hide();
455
456 match (before_should_hide, after_should_hide) {
457 (false, true) => self.bar.set_draw_target(Self::hidden_target()),
458 (true, false) => self.bar.set_draw_target(Self::stderr_target()),
459 _ => {}
460 }
461 }
462
463 fn remove_test(&mut self, test_instance: &TestInstanceId) {
464 if let Some(running_tests) = &mut self.running_tests {
465 running_tests.remove(
466 running_tests
467 .iter()
468 .position(|e| {
469 &e.binary_id == test_instance.binary_id
470 && e.test_name == test_instance.test_name
471 })
472 .expect("finished test to have started"),
473 );
474 }
475 }
476
477 pub(super) fn write_buf(&mut self, buf: &str) {
478 self.buffer.push_str(buf);
479 }
480
481 #[inline]
482 pub(super) fn finish_and_clear(&self) {
483 self.print_and_force_redraw();
484 self.bar.finish_and_clear();
485 }
486
487 fn stderr_target() -> ProgressDrawTarget {
488 ProgressDrawTarget::stderr_with_hz(PROGRESS_REFRESH_RATE_HZ)
489 }
490
491 fn hidden_target() -> ProgressDrawTarget {
492 ProgressDrawTarget::hidden()
493 }
494
495 fn should_hide(&self) -> bool {
496 self.hidden_no_capture || self.hidden_run_paused || self.hidden_info_response
497 }
498
499 pub(super) fn is_hidden(&self) -> bool {
500 self.bar.is_hidden()
501 }
502}
503
504#[derive(Default)]
506pub(super) struct TerminalProgress {
507 last_value: TerminalProgressValue,
508}
509
510impl TerminalProgress {
511 const ENV: &str = "CARGO_TERM_PROGRESS_TERM_INTEGRATION";
512
513 pub(super) fn new(configs: &CargoConfigs, stream: &dyn IsTerminal) -> Option<Self> {
514 for config in configs.discovered_configs() {
516 match config {
517 DiscoveredConfig::CliOption { config, source } => {
518 if let Some(v) = config.term.progress.term_integration {
519 if v {
520 debug!("enabling terminal progress reporting based on {source:?}");
521 return Some(Self::default());
522 } else {
523 debug!("disabling terminal progress reporting based on {source:?}");
524 return None;
525 }
526 }
527 }
528 DiscoveredConfig::Env => {
529 if let Some(v) = env::var_os(Self::ENV) {
530 if v == "true" {
531 debug!(
532 "enabling terminal progress reporting based on \
533 CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
534 );
535 return Some(Self::default());
536 } else if v == "false" {
537 debug!(
538 "disabling terminal progress reporting based on \
539 CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
540 );
541 return None;
542 } else {
543 debug!(
544 "invalid value for CARGO_TERM_PROGRESS_TERM_INTEGRATION \
545 environment variable: {v:?}, ignoring"
546 );
547 }
548 }
549 }
550 DiscoveredConfig::File { config, source } => {
551 if let Some(v) = config.term.progress.term_integration {
552 if v {
553 debug!("enabling terminal progress reporting based on {source:?}");
554 return Some(Self::default());
555 } else {
556 debug!("disabling terminal progress reporting based on {source:?}");
557 return None;
558 }
559 }
560 }
561 }
562 }
563
564 supports_osc_9_4(stream).then(Self::default)
565 }
566
567 pub(super) fn update_progress(&mut self, event: &TestEvent<'_>) {
568 let value = match &event.kind {
569 TestEventKind::RunStarted { .. }
570 | TestEventKind::StressSubRunStarted { .. }
571 | TestEventKind::StressSubRunFinished { .. }
572 | TestEventKind::SetupScriptStarted { .. }
573 | TestEventKind::SetupScriptSlow { .. }
574 | TestEventKind::SetupScriptFinished { .. } => TerminalProgressValue::None,
575 TestEventKind::TestStarted { current_stats, .. }
576 | TestEventKind::TestFinished { current_stats, .. } => {
577 let percentage = (current_stats.finished_count as f64
578 / current_stats.initial_run_count as f64)
579 * 100.0;
580 if current_stats.has_failures() || current_stats.cancel_reason.is_some() {
581 TerminalProgressValue::Error(percentage)
582 } else {
583 TerminalProgressValue::Value(percentage)
584 }
585 }
586 TestEventKind::TestSlow { .. }
587 | TestEventKind::TestAttemptFailedWillRetry { .. }
588 | TestEventKind::TestRetryStarted { .. }
589 | TestEventKind::TestSkipped { .. }
590 | TestEventKind::InfoStarted { .. }
591 | TestEventKind::InfoResponse { .. }
592 | TestEventKind::InfoFinished { .. }
593 | TestEventKind::InputEnter { .. } => TerminalProgressValue::None,
594 TestEventKind::RunBeginCancel { current_stats, .. }
595 | TestEventKind::RunBeginKill { current_stats, .. } => {
596 let percentage = (current_stats.finished_count as f64
598 / current_stats.initial_run_count as f64)
599 * 100.0;
600 TerminalProgressValue::Error(percentage)
601 }
602 TestEventKind::RunPaused { .. }
603 | TestEventKind::RunContinued { .. }
604 | TestEventKind::RunFinished { .. } => {
605 TerminalProgressValue::Remove
608 }
609 };
610
611 self.last_value = value;
612 }
613
614 pub(super) fn last_value(&self) -> &TerminalProgressValue {
615 &self.last_value
616 }
617}
618
619fn supports_osc_9_4(stream: &dyn IsTerminal) -> bool {
621 if !stream.is_terminal() {
622 debug!(
623 "autodetect terminal progress reporting: disabling since \
624 passed-in stream (usually stderr) is not a terminal"
625 );
626 return false;
627 }
628 if std::env::var_os("WT_SESSION").is_some() {
629 debug!("autodetect terminal progress reporting: enabling since WT_SESSION is set");
630 return true;
631 };
632 if std::env::var_os("ConEmuANSI").is_some_and(|term| term == "ON") {
633 debug!("autodetect terminal progress reporting: enabling since ConEmuANSI is ON");
634 return true;
635 }
636 if let Ok(term) = std::env::var("TERM_PROGRAM")
637 && (term == "WezTerm" || term == "ghostty")
638 {
639 debug!("autodetect terminal progress reporting: enabling since TERM_PROGRAM is {term}");
640 return true;
641 }
642
643 false
644}
645
646#[derive(PartialEq, Debug, Default)]
650pub(super) enum TerminalProgressValue {
651 #[default]
653 None,
654 Remove,
656 Value(f64),
658 #[expect(dead_code)]
662 Indeterminate,
663 Error(f64),
665}
666
667impl fmt::Display for TerminalProgressValue {
668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669 let (state, progress) = match self {
677 Self::None => return Ok(()), Self::Remove => (0, 0.0),
679 Self::Value(v) => (1, *v),
680 Self::Indeterminate => (3, 0.0),
681 Self::Error(v) => (2, *v),
682 };
683 write!(f, "\x1b]9;4;{state};{progress:.0}\x1b\\")
684 }
685}
686
687pub(super) fn progress_str(
689 elapsed: Duration,
690 current_stats: &RunStats,
691 running: usize,
692 styles: &Styles,
693) -> String {
694 let mut s = progress_bar_prefix(current_stats, current_stats.cancel_reason, styles);
696
697 swrite!(
699 s,
700 " {}{}/{}: {}",
701 DisplayBracketedHhMmSs(elapsed),
702 current_stats.finished_count,
703 current_stats.initial_run_count,
704 progress_bar_msg(current_stats, running, styles)
705 );
706
707 s
708}
709
710pub(super) fn write_summary_str(run_stats: &RunStats, styles: &Styles, out: &mut String) {
711 let &RunStats {
713 initial_run_count: _,
714 finished_count: _,
715 setup_scripts_initial_count: _,
716 setup_scripts_finished_count: _,
717 setup_scripts_passed: _,
718 setup_scripts_failed: _,
719 setup_scripts_exec_failed: _,
720 setup_scripts_timed_out: _,
721 passed,
722 passed_slow,
723 flaky,
724 failed,
725 failed_slow: _,
726 timed_out,
727 leaky,
728 leaky_failed,
729 exec_failed,
730 skipped,
731 cancel_reason: _,
732 } = run_stats;
733
734 swrite!(
735 out,
736 "{} {}",
737 passed.style(styles.count),
738 "passed".style(styles.pass)
739 );
740
741 if passed_slow > 0 || flaky > 0 || leaky > 0 {
742 let mut text = Vec::with_capacity(3);
743 if passed_slow > 0 {
744 text.push(format!(
745 "{} {}",
746 passed_slow.style(styles.count),
747 "slow".style(styles.skip),
748 ));
749 }
750 if flaky > 0 {
751 text.push(format!(
752 "{} {}",
753 flaky.style(styles.count),
754 "flaky".style(styles.skip),
755 ));
756 }
757 if leaky > 0 {
758 text.push(format!(
759 "{} {}",
760 leaky.style(styles.count),
761 "leaky".style(styles.skip),
762 ));
763 }
764 swrite!(out, " ({})", text.join(", "));
765 }
766 swrite!(out, ", ");
767
768 if failed > 0 {
769 swrite!(
770 out,
771 "{} {}",
772 failed.style(styles.count),
773 "failed".style(styles.fail),
774 );
775 if leaky_failed > 0 {
776 swrite!(
777 out,
778 " ({} due to being {})",
779 leaky_failed.style(styles.count),
780 "leaky".style(styles.fail),
781 );
782 }
783 swrite!(out, ", ");
784 }
785
786 if exec_failed > 0 {
787 swrite!(
788 out,
789 "{} {}, ",
790 exec_failed.style(styles.count),
791 "exec failed".style(styles.fail),
792 );
793 }
794
795 if timed_out > 0 {
796 swrite!(
797 out,
798 "{} {}, ",
799 timed_out.style(styles.count),
800 "timed out".style(styles.fail),
801 );
802 }
803
804 swrite!(
805 out,
806 "{} {}",
807 skipped.style(styles.count),
808 "skipped".style(styles.skip),
809 );
810}
811
812fn progress_bar_cancel_prefix(reason: Option<CancelReason>, styles: &Styles) -> String {
813 let status = match reason {
814 Some(CancelReason::SetupScriptFailure)
815 | Some(CancelReason::TestFailure)
816 | Some(CancelReason::ReportError)
817 | Some(CancelReason::GlobalTimeout)
818 | Some(CancelReason::TestFailureImmediate)
819 | Some(CancelReason::Signal)
820 | Some(CancelReason::Interrupt)
821 | None => "Cancelling",
822 Some(CancelReason::SecondSignal) => "Killing",
823 };
824 format!("{:>12}", status.style(styles.fail))
825}
826
827fn progress_bar_prefix(
828 run_stats: &RunStats,
829 cancel_reason: Option<CancelReason>,
830 styles: &Styles,
831) -> String {
832 if let Some(reason) = cancel_reason {
833 return progress_bar_cancel_prefix(Some(reason), styles);
834 }
835
836 let style = if run_stats.has_failures() {
837 styles.fail
838 } else {
839 styles.pass
840 };
841
842 format!("{:>12}", "Running".style(style))
843}
844
845pub(super) fn progress_bar_msg(
846 current_stats: &RunStats,
847 running: usize,
848 styles: &Styles,
849) -> String {
850 let mut s = format!("{} running, ", running.style(styles.count));
851 write_summary_str(current_stats, styles, &mut s);
852 s
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858
859 #[test]
860 fn test_progress_bar_prefix() {
861 let mut styles = Styles::default();
862 styles.colorize();
863
864 for (name, stats) in run_stats_test_failure_examples() {
865 let prefix = progress_bar_prefix(&stats, Some(CancelReason::TestFailure), &styles);
866 assert_eq!(
867 prefix,
868 " Cancelling".style(styles.fail).to_string(),
869 "{name} matches"
870 );
871 }
872 for (name, stats) in run_stats_setup_script_failure_examples() {
873 let prefix =
874 progress_bar_prefix(&stats, Some(CancelReason::SetupScriptFailure), &styles);
875 assert_eq!(
876 prefix,
877 " Cancelling".style(styles.fail).to_string(),
878 "{name} matches"
879 );
880 }
881
882 let prefix = progress_bar_prefix(&RunStats::default(), Some(CancelReason::Signal), &styles);
883 assert_eq!(prefix, " Cancelling".style(styles.fail).to_string());
884
885 let prefix = progress_bar_prefix(&RunStats::default(), None, &styles);
886 assert_eq!(prefix, " Running".style(styles.pass).to_string());
887
888 for (name, stats) in run_stats_test_failure_examples() {
889 let prefix = progress_bar_prefix(&stats, None, &styles);
890 assert_eq!(
891 prefix,
892 " Running".style(styles.fail).to_string(),
893 "{name} matches"
894 );
895 }
896 for (name, stats) in run_stats_setup_script_failure_examples() {
897 let prefix = progress_bar_prefix(&stats, None, &styles);
898 assert_eq!(
899 prefix,
900 " Running".style(styles.fail).to_string(),
901 "{name} matches"
902 );
903 }
904 }
905
906 #[test]
907 fn progress_str_snapshots() {
908 let mut styles = Styles::default();
909 styles.colorize();
910
911 let elapsed = Duration::from_secs(123456);
913 let running = 10;
914
915 for (name, stats) in run_stats_test_failure_examples() {
916 let s = progress_str(elapsed, &stats, running, &styles);
917 insta::assert_snapshot!(format!("{name}_with_cancel_reason"), s);
918
919 let mut stats = stats;
920 stats.cancel_reason = None;
921 let s = progress_str(elapsed, &stats, running, &styles);
922 insta::assert_snapshot!(format!("{name}_without_cancel_reason"), s);
923 }
924
925 for (name, stats) in run_stats_setup_script_failure_examples() {
926 let s = progress_str(elapsed, &stats, running, &styles);
927 insta::assert_snapshot!(format!("{name}_with_cancel_reason"), s);
928
929 let mut stats = stats;
930 stats.cancel_reason = None;
931 let s = progress_str(elapsed, &stats, running, &styles);
932 insta::assert_snapshot!(format!("{name}_without_cancel_reason"), s);
933 }
934 }
935
936 #[test]
937 fn running_test_snapshots() {
938 let styles = Styles::default();
939 let now = Instant::now();
940
941 for (name, running_test) in running_test_examples(now) {
942 let msg = running_test.message(now, 80, &styles);
943 insta::assert_snapshot!(name, msg);
944 }
945 }
946
947 fn running_test_examples(now: Instant) -> Vec<(&'static str, RunningTest)> {
948 let binary_id = RustBinaryId::new("my-binary");
949 let test_name = "test::my_test".to_string();
950 let start_time = now - Duration::from_secs(125); vec![
953 (
954 "running_status",
955 RunningTest {
956 binary_id: binary_id.clone(),
957 test_name: test_name.clone(),
958 status: RunningTestStatus::Running,
959 start_time,
960 paused_for: Duration::ZERO,
961 },
962 ),
963 (
964 "slow_status",
965 RunningTest {
966 binary_id: binary_id.clone(),
967 test_name: test_name.clone(),
968 status: RunningTestStatus::Slow,
969 start_time,
970 paused_for: Duration::ZERO,
971 },
972 ),
973 (
974 "delay_status",
975 RunningTest {
976 binary_id: binary_id.clone(),
977 test_name: test_name.clone(),
978 status: RunningTestStatus::Delay(Duration::from_secs(130)),
979 start_time,
980 paused_for: Duration::ZERO,
981 },
982 ),
983 (
984 "delay_status_underflow",
985 RunningTest {
986 binary_id: binary_id.clone(),
987 test_name: test_name.clone(),
988 status: RunningTestStatus::Delay(Duration::from_secs(124)),
989 start_time,
990 paused_for: Duration::ZERO,
991 },
992 ),
993 (
994 "retry_status",
995 RunningTest {
996 binary_id: binary_id.clone(),
997 test_name: test_name.clone(),
998 status: RunningTestStatus::Retry,
999 start_time,
1000 paused_for: Duration::ZERO,
1001 },
1002 ),
1003 (
1004 "with_paused_duration",
1005 RunningTest {
1006 binary_id: binary_id.clone(),
1007 test_name: test_name.clone(),
1008 status: RunningTestStatus::Running,
1009 start_time,
1010 paused_for: Duration::from_secs(30),
1011 },
1012 ),
1013 ]
1014 }
1015
1016 fn run_stats_test_failure_examples() -> Vec<(&'static str, RunStats)> {
1017 vec![
1018 (
1019 "one_failed",
1020 RunStats {
1021 initial_run_count: 20,
1022 finished_count: 1,
1023 failed: 1,
1024 cancel_reason: Some(CancelReason::TestFailure),
1025 ..RunStats::default()
1026 },
1027 ),
1028 (
1029 "one_failed_one_passed",
1030 RunStats {
1031 initial_run_count: 20,
1032 finished_count: 2,
1033 failed: 1,
1034 passed: 1,
1035 cancel_reason: Some(CancelReason::TestFailure),
1036 ..RunStats::default()
1037 },
1038 ),
1039 (
1040 "one_exec_failed",
1041 RunStats {
1042 initial_run_count: 20,
1043 finished_count: 10,
1044 exec_failed: 1,
1045 cancel_reason: Some(CancelReason::TestFailure),
1046 ..RunStats::default()
1047 },
1048 ),
1049 (
1050 "one_timed_out",
1051 RunStats {
1052 initial_run_count: 20,
1053 finished_count: 10,
1054 timed_out: 1,
1055 cancel_reason: Some(CancelReason::TestFailure),
1056 ..RunStats::default()
1057 },
1058 ),
1059 ]
1060 }
1061
1062 fn run_stats_setup_script_failure_examples() -> Vec<(&'static str, RunStats)> {
1063 vec![
1064 (
1065 "one_setup_script_failed",
1066 RunStats {
1067 initial_run_count: 30,
1068 setup_scripts_failed: 1,
1069 cancel_reason: Some(CancelReason::SetupScriptFailure),
1070 ..RunStats::default()
1071 },
1072 ),
1073 (
1074 "one_setup_script_exec_failed",
1075 RunStats {
1076 initial_run_count: 35,
1077 setup_scripts_exec_failed: 1,
1078 cancel_reason: Some(CancelReason::SetupScriptFailure),
1079 ..RunStats::default()
1080 },
1081 ),
1082 (
1083 "one_setup_script_timed_out",
1084 RunStats {
1085 initial_run_count: 40,
1086 setup_scripts_timed_out: 1,
1087 cancel_reason: Some(CancelReason::SetupScriptFailure),
1088 ..RunStats::default()
1089 },
1090 ),
1091 ]
1092 }
1093}