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