1use crate::{
5 helpers::{
6 DisplayTestInstance, plural,
7 progress::{PROGRESS_REFRESH_RATE_HZ, progress_bar_style, term_progress_percent},
8 },
9 list::TestInstanceId,
10 reporter::{
11 displayer::formatters::DisplayBracketedHhMmSs,
12 events::*,
13 helpers::{Styles, print_lines_in_chunks},
14 },
15 run_mode::NextestRunMode,
16};
17use anstyle_progress::TermProgress;
18use indicatif::{ProgressBar, ProgressDrawTarget};
19use nextest_metadata::{RustBinaryId, TestCaseName};
20use owo_colors::OwoColorize;
21use std::{
22 cmp::{max, min},
23 env, fmt,
24 str::FromStr,
25 time::{Duration, Instant},
26};
27use swrite::{SWrite, swrite};
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum MaxProgressRunning {
33 Count(usize),
36
37 Infinite,
39}
40
41#[cfg(feature = "config-schema")]
42impl schemars::JsonSchema for MaxProgressRunning {
43 fn schema_name() -> std::borrow::Cow<'static, str> {
44 "MaxProgressRunning".into()
45 }
46
47 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
48 schemars::json_schema!({
49 "title": "MaxProgressRunning",
50 "description":
51 "Maximum number of running tests to display: a non-negative integer, \
52 or \"infinite\" for no limit.",
53 "oneOf": [
54 { "type": "integer", "minimum": 0 },
55 { "type": "string", "enum": ["infinite"] }
56 ]
57 })
58 }
59}
60
61impl MaxProgressRunning {
62 pub const DEFAULT_VALUE: Self = Self::Count(8);
64}
65
66impl Default for MaxProgressRunning {
67 fn default() -> Self {
68 Self::DEFAULT_VALUE
69 }
70}
71
72impl FromStr for MaxProgressRunning {
73 type Err = String;
74
75 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 if s.eq_ignore_ascii_case("infinite") {
77 return Ok(Self::Infinite);
78 }
79
80 match s.parse::<usize>() {
81 Err(e) => Err(format!("Error: {e} parsing {s}")),
82 Ok(n) => Ok(Self::Count(n)),
83 }
84 }
85}
86
87impl fmt::Display for MaxProgressRunning {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 Self::Infinite => write!(f, "infinite"),
91 Self::Count(n) => write!(f, "{n}"),
92 }
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum ShowProgress {
102 Auto {
110 suppress_success: bool,
113 },
114
115 None,
117
118 Counter,
120
121 Running,
123}
124
125impl Default for ShowProgress {
126 fn default() -> Self {
127 ShowProgress::Auto {
128 suppress_success: false,
129 }
130 }
131}
132
133#[derive(Debug)]
134pub(super) enum RunningTestStatus {
135 Running,
136 Slow,
137 Delay(Duration),
138 Retry,
139}
140
141#[derive(Debug)]
142pub(super) struct RunningTest {
143 binary_id: RustBinaryId,
144 test_name: TestCaseName,
145 status: RunningTestStatus,
146 start_time: Instant,
147 paused_for: Duration,
148}
149
150impl RunningTest {
151 fn message(&self, now: Instant, width: usize, styles: &Styles) -> String {
152 let mut elapsed = (now - self.start_time).saturating_sub(self.paused_for);
153 let status = match self.status {
154 RunningTestStatus::Running => " ".to_owned(),
155 RunningTestStatus::Slow => " SLOW".style(styles.skip).to_string(),
156 RunningTestStatus::Delay(d) => {
157 elapsed = d.saturating_sub(elapsed);
161 "DELAY".style(styles.retry).to_string()
162 }
163 RunningTestStatus::Retry => "RETRY".style(styles.retry).to_string(),
164 };
165 let max_width = width.saturating_sub(25);
166 let test = DisplayTestInstance::new(
167 None,
168 None,
169 TestInstanceId {
170 binary_id: &self.binary_id,
171
172 test_name: &self.test_name,
173 },
174 &styles.list_styles,
175 )
176 .with_max_width(max_width);
177 format!(
178 " {} {}{}",
179 status,
180 DisplayBracketedHhMmSs(elapsed),
181 test
182 )
183 }
184}
185
186#[derive(Debug)]
187pub(super) struct ProgressBarState {
188 bar: ProgressBar,
189 mode: NextestRunMode,
190 stats: RunStats,
191 running: usize,
192 max_progress_running: MaxProgressRunning,
193 max_running_displayed: usize,
197 running_tests: Option<Vec<RunningTest>>,
199 buffer: String,
200 println_chunk_size: usize,
203 hidden_no_capture: bool,
213 hidden_run_paused: bool,
214 hidden_info_response: bool,
215}
216
217impl ProgressBarState {
218 pub(super) fn new(
219 mode: NextestRunMode,
220 run_count: usize,
221 progress_chars: &str,
222 max_progress_running: MaxProgressRunning,
223 ) -> Self {
224 let bar = ProgressBar::new(run_count as u64);
225 let run_count_width = format!("{run_count}").len();
226 let suffix = format!("{{pos:>{run_count_width}}}/{{len:{run_count_width}}}: {{msg}}");
227 bar.set_style(progress_bar_style(progress_chars, &suffix));
228
229 let running_tests =
230 (!matches!(max_progress_running, MaxProgressRunning::Count(0))).then(Vec::new);
231
232 let println_chunk_size = env::var("__NEXTEST_PROGRESS_PRINTLN_CHUNK_SIZE")
236 .ok()
237 .and_then(|s| s.parse::<usize>().ok())
238 .unwrap_or(4096);
239
240 Self {
241 bar,
242 mode,
243 stats: RunStats::default(),
244 running: 0,
245 max_progress_running,
246 max_running_displayed: 0,
247 running_tests,
248 buffer: String::new(),
249 println_chunk_size,
250 hidden_no_capture: false,
251 hidden_run_paused: false,
252 hidden_info_response: false,
253 }
254 }
255
256 pub(super) fn tick(&mut self, styles: &Styles) {
257 self.update_message(styles);
258 self.print_and_clear_buffer();
259 }
260
261 fn print_and_clear_buffer(&mut self) {
262 self.print_and_force_redraw();
263 self.buffer.clear();
264 }
265
266 fn print_and_force_redraw(&self) {
268 if self.buffer.is_empty() {
269 self.bar.force_draw();
272 return;
273 }
274
275 print_lines_in_chunks(&self.buffer, self.println_chunk_size, |chunk| {
289 self.bar.println(chunk);
290 });
291 }
292
293 fn update_message(&mut self, styles: &Styles) {
294 let mut msg = self.progress_bar_msg(styles);
295 msg += " ";
296
297 if let Some(running_tests) = &self.running_tests {
298 let (_, width) = console::Term::stderr().size();
299 let width = max(width as usize, 40);
300 let now = Instant::now();
301 let mut count = match self.max_progress_running {
302 MaxProgressRunning::Count(count) => min(running_tests.len(), count),
303 MaxProgressRunning::Infinite => running_tests.len(),
304 };
305 for running_test in &running_tests[..count] {
306 msg.push('\n');
307 msg.push_str(&running_test.message(now, width, styles));
308 }
309 if count < running_tests.len() {
310 let overflow_count = running_tests.len() - count;
311 swrite!(
312 msg,
313 "\n ... and {} more {} running",
314 overflow_count.style(styles.count),
315 plural::tests_str(self.mode, overflow_count),
316 );
317 count += 1;
318 }
319 self.max_running_displayed = max(self.max_running_displayed, count);
320 msg.push_str(&"\n".to_string().repeat(self.max_running_displayed - count));
321 }
322 self.bar.set_message(msg);
323 }
324
325 fn progress_bar_msg(&self, styles: &Styles) -> String {
326 progress_bar_msg(&self.stats, self.running, styles)
327 }
328
329 pub(super) fn update_progress_bar(&mut self, event: &TestEvent<'_>, styles: &Styles) {
330 let before_should_hide = self.should_hide();
331
332 match &event.kind {
333 TestEventKind::StressSubRunStarted { .. } => {
334 self.bar.reset();
335 }
336 TestEventKind::StressSubRunFinished { .. } => {
337 self.bar.finish_and_clear();
340 }
341 TestEventKind::SetupScriptStarted { no_capture, .. } if *no_capture => {
343 self.hidden_no_capture = true;
344 }
345 TestEventKind::SetupScriptFinished { no_capture, .. } if *no_capture => {
347 self.hidden_no_capture = false;
348 }
349 TestEventKind::TestStarted {
350 current_stats,
351 running,
352 test_instance,
353 ..
354 } => {
355 self.running = *running;
356 self.stats = *current_stats;
357
358 self.bar.set_prefix(progress_bar_prefix(
359 current_stats,
360 current_stats.cancel_reason,
361 styles,
362 ));
363 self.bar.set_length(current_stats.initial_run_count as u64);
364 self.bar.set_position(current_stats.finished_count as u64);
365
366 if let Some(running_tests) = &mut self.running_tests {
367 running_tests.push(RunningTest {
368 binary_id: test_instance.binary_id.clone(),
369 test_name: test_instance.test_name.to_owned(),
370 status: RunningTestStatus::Running,
371 start_time: Instant::now(),
372 paused_for: Duration::ZERO,
373 });
374 }
375 }
376 TestEventKind::TestFinished {
377 current_stats,
378 running,
379 test_instance,
380 ..
381 } => {
382 self.running = *running;
383 self.stats = *current_stats;
384 self.remove_test(test_instance);
385
386 self.bar.set_prefix(progress_bar_prefix(
387 current_stats,
388 current_stats.cancel_reason,
389 styles,
390 ));
391 self.bar.set_length(current_stats.initial_run_count as u64);
392 self.bar.set_position(current_stats.finished_count as u64);
393 }
394 TestEventKind::TestAttemptFailedWillRetry {
395 test_instance,
396 delay_before_next_attempt,
397 ..
398 } => {
399 self.remove_test(test_instance);
400 if let Some(running_tests) = &mut self.running_tests {
401 running_tests.push(RunningTest {
402 binary_id: test_instance.binary_id.clone(),
403 test_name: test_instance.test_name.to_owned(),
404 status: RunningTestStatus::Delay(*delay_before_next_attempt),
405 start_time: Instant::now(),
406 paused_for: Duration::ZERO,
407 });
408 }
409 }
410 TestEventKind::TestRetryStarted { test_instance, .. } => {
411 self.remove_test(test_instance);
412 if let Some(running_tests) = &mut self.running_tests {
413 running_tests.push(RunningTest {
414 binary_id: test_instance.binary_id.clone(),
415 test_name: test_instance.test_name.to_owned(),
416 status: RunningTestStatus::Retry,
417 start_time: Instant::now(),
418 paused_for: Duration::ZERO,
419 });
420 }
421 }
422 TestEventKind::TestSlow { test_instance, .. } => {
423 if let Some(running_tests) = &mut self.running_tests {
424 running_tests
425 .iter_mut()
426 .find(|rt| {
427 &rt.binary_id == test_instance.binary_id
428 && &rt.test_name == test_instance.test_name
429 })
430 .expect("a slow test to be already running")
431 .status = RunningTestStatus::Slow;
432 }
433 }
434 TestEventKind::InfoStarted { .. } => {
435 self.hidden_info_response = true;
438 }
439 TestEventKind::InfoFinished { .. } => {
440 self.hidden_info_response = false;
442 }
443 TestEventKind::RunPaused { .. } => {
444 self.hidden_run_paused = true;
447 }
448 TestEventKind::RunContinued { .. } => {
449 self.hidden_run_paused = false;
452 let current_global_elapsed = self.bar.elapsed();
453 self.bar.set_elapsed(event.elapsed);
454
455 if let Some(running_tests) = &mut self.running_tests {
456 let delta = current_global_elapsed.saturating_sub(event.elapsed);
457 for running_test in running_tests {
458 running_test.paused_for += delta;
459 }
460 }
461 }
462 TestEventKind::RunBeginCancel {
463 current_stats,
464 running,
465 ..
466 }
467 | TestEventKind::RunBeginKill {
468 current_stats,
469 running,
470 ..
471 } => {
472 self.running = *running;
473 self.stats = *current_stats;
474 self.bar.set_prefix(progress_bar_cancel_prefix(
475 current_stats.cancel_reason,
476 styles,
477 ));
478 }
479 _ => {}
480 }
481
482 let after_should_hide = self.should_hide();
483
484 match (before_should_hide, after_should_hide) {
485 (false, true) => self.bar.set_draw_target(Self::hidden_target()),
486 (true, false) => self.bar.set_draw_target(Self::stderr_target()),
487 _ => {}
488 }
489 }
490
491 fn remove_test(&mut self, test_instance: &TestInstanceId) {
492 if let Some(running_tests) = &mut self.running_tests {
493 running_tests.remove(
494 running_tests
495 .iter()
496 .position(|e| {
497 &e.binary_id == test_instance.binary_id
498 && &e.test_name == test_instance.test_name
499 })
500 .expect("finished test to have started"),
501 );
502 }
503 }
504
505 pub(super) fn write_buf(&mut self, buf: &str) {
506 self.buffer.push_str(buf);
507 }
508
509 #[inline]
510 pub(super) fn finish_and_clear(&self) {
511 self.print_and_force_redraw();
512 self.bar.finish_and_clear();
513 }
514
515 fn stderr_target() -> ProgressDrawTarget {
516 ProgressDrawTarget::stderr_with_hz(PROGRESS_REFRESH_RATE_HZ)
517 }
518
519 fn hidden_target() -> ProgressDrawTarget {
520 ProgressDrawTarget::hidden()
521 }
522
523 fn should_hide(&self) -> bool {
524 self.hidden_no_capture || self.hidden_run_paused || self.hidden_info_response
525 }
526}
527
528pub(super) fn terminal_progress_value(event: &TestEvent<'_>) -> TermProgress {
529 match &event.kind {
530 TestEventKind::RunStarted { .. }
531 | TestEventKind::StressSubRunStarted { .. }
532 | TestEventKind::StressSubRunFinished { .. }
533 | TestEventKind::SetupScriptStarted { .. }
534 | TestEventKind::SetupScriptSlow { .. }
535 | TestEventKind::SetupScriptFinished { .. } => TermProgress::none(),
536 TestEventKind::TestStarted { current_stats, .. }
537 | TestEventKind::TestFinished { current_stats, .. } => {
538 if current_stats.has_failures() || current_stats.cancel_reason.is_some() {
539 term_progress_errored(current_stats)
540 } else {
541 term_progress_running(current_stats)
542 }
543 }
544 TestEventKind::TestSlow { .. }
545 | TestEventKind::TestAttemptFailedWillRetry { .. }
546 | TestEventKind::TestRetryStarted { .. }
547 | TestEventKind::TestSkipped { .. }
548 | TestEventKind::InfoStarted { .. }
549 | TestEventKind::InfoResponse { .. }
550 | TestEventKind::InfoFinished { .. }
551 | TestEventKind::InputEnter { .. } => TermProgress::none(),
552 TestEventKind::RunBeginCancel { current_stats, .. }
553 | TestEventKind::RunBeginKill { current_stats, .. } => term_progress_errored(current_stats),
554 TestEventKind::RunPaused { .. }
555 | TestEventKind::RunContinued { .. }
556 | TestEventKind::RunFinished { .. } => {
557 TermProgress::remove()
562 }
563 }
564}
565
566fn term_progress_running(current_stats: &RunStats) -> TermProgress {
567 let percent = term_progress_percent(
568 current_stats.finished_count,
569 current_stats.initial_run_count,
570 );
571 TermProgress::start().percent(percent)
572}
573
574fn term_progress_errored(current_stats: &RunStats) -> TermProgress {
575 let percent = term_progress_percent(
576 current_stats.finished_count,
577 current_stats.initial_run_count,
578 );
579 TermProgress::error().percent(percent)
580}
581
582pub(super) fn progress_str(
584 elapsed: Duration,
585 current_stats: &RunStats,
586 running: usize,
587 styles: &Styles,
588) -> String {
589 let mut s = progress_bar_prefix(current_stats, current_stats.cancel_reason, styles);
591
592 swrite!(
594 s,
595 " {}{}/{}: {}",
596 DisplayBracketedHhMmSs(elapsed),
597 current_stats.finished_count,
598 current_stats.initial_run_count,
599 progress_bar_msg(current_stats, running, styles)
600 );
601
602 s
603}
604
605pub(super) fn write_summary_str(run_stats: &RunStats, styles: &Styles, out: &mut String) {
606 let &RunStats {
608 initial_run_count: _,
609 finished_count: _,
610 setup_scripts_initial_count: _,
611 setup_scripts_finished_count: _,
612 setup_scripts_passed: _,
613 setup_scripts_failed: _,
614 setup_scripts_exec_failed: _,
615 setup_scripts_timed_out: _,
616 passed,
617 passed_slow,
618 passed_timed_out: _,
619 flaky,
620 failed,
621 failed_slow: _,
622 failed_timed_out,
623 leaky,
624 leaky_failed,
625 exec_failed,
626 skipped,
627 cancel_reason: _,
628 } = run_stats;
629
630 swrite!(
631 out,
632 "{} {}",
633 passed.style(styles.count),
634 "passed".style(styles.pass)
635 );
636
637 if passed_slow > 0 || flaky > 0 || leaky > 0 {
638 let mut text = Vec::with_capacity(3);
639 if passed_slow > 0 {
640 text.push(format!(
641 "{} {}",
642 passed_slow.style(styles.count),
643 "slow".style(styles.skip),
644 ));
645 }
646 if flaky > 0 {
647 text.push(format!(
648 "{} {}",
649 flaky.style(styles.count),
650 "flaky".style(styles.skip),
651 ));
652 }
653 if leaky > 0 {
654 text.push(format!(
655 "{} {}",
656 leaky.style(styles.count),
657 "leaky".style(styles.skip),
658 ));
659 }
660 swrite!(out, " ({})", text.join(", "));
661 }
662 swrite!(out, ", ");
663
664 if failed > 0 {
665 swrite!(
666 out,
667 "{} {}",
668 failed.style(styles.count),
669 "failed".style(styles.fail),
670 );
671 if leaky_failed > 0 {
672 swrite!(
673 out,
674 " ({} due to being {})",
675 leaky_failed.style(styles.count),
676 "leaky".style(styles.fail),
677 );
678 }
679 swrite!(out, ", ");
680 }
681
682 if exec_failed > 0 {
683 swrite!(
684 out,
685 "{} {}, ",
686 exec_failed.style(styles.count),
687 "exec failed".style(styles.fail),
688 );
689 }
690
691 if failed_timed_out > 0 {
692 swrite!(
693 out,
694 "{} {}, ",
695 failed_timed_out.style(styles.count),
696 "timed out".style(styles.fail),
697 );
698 }
699
700 swrite!(
701 out,
702 "{} {}",
703 skipped.style(styles.count),
704 "skipped".style(styles.skip),
705 );
706}
707
708fn progress_bar_cancel_prefix(reason: Option<CancelReason>, styles: &Styles) -> String {
709 let status = match reason {
710 Some(CancelReason::SetupScriptFailure)
711 | Some(CancelReason::TestFailure)
712 | Some(CancelReason::ReportError)
713 | Some(CancelReason::GlobalTimeout)
714 | Some(CancelReason::TestFailureImmediate)
715 | Some(CancelReason::Signal)
716 | Some(CancelReason::Interrupt)
717 | None => "Cancelling",
718 Some(CancelReason::SecondSignal) => "Killing",
719 };
720 format!("{:>12}", status.style(styles.fail))
721}
722
723fn progress_bar_prefix(
724 run_stats: &RunStats,
725 cancel_reason: Option<CancelReason>,
726 styles: &Styles,
727) -> String {
728 if let Some(reason) = cancel_reason {
729 return progress_bar_cancel_prefix(Some(reason), styles);
730 }
731
732 let style = if run_stats.has_failures() {
733 styles.fail
734 } else {
735 styles.pass
736 };
737
738 format!("{:>12}", "Running".style(style))
739}
740
741pub(super) fn progress_bar_msg(
742 current_stats: &RunStats,
743 running: usize,
744 styles: &Styles,
745) -> String {
746 let mut s = format!("{} running, ", running.style(styles.count));
747 write_summary_str(current_stats, styles, &mut s);
748 s
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754 use crate::{
755 config::elements::{FlakyResult, JunitFlakyFailStatus},
756 output_spec::LiveSpec,
757 reporter::{TestOutputDisplay, test_helpers::global_slot_assignment},
758 test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
759 };
760 use bytes::Bytes;
761 use chrono::Local;
762
763 #[test]
764 fn terminal_progress_value_escape_codes() {
765 let binary_id = RustBinaryId::new("test-binary");
766 let test_name = TestCaseName::new("test_name");
767
768 let normal = TestEvent {
769 timestamp: Local::now().fixed_offset(),
770 elapsed: Duration::ZERO,
771 kind: TestEventKind::TestStarted {
772 stress_index: None,
773 test_instance: TestInstanceId {
774 binary_id: &binary_id,
775 test_name: &test_name,
776 },
777 slot_assignment: global_slot_assignment(0),
778 current_stats: RunStats {
779 initial_run_count: 10,
780 finished_count: 3,
781 ..RunStats::default()
782 },
783 running: 1,
784 command_line: vec![],
785 },
786 };
787 assert_eq!(
788 terminal_progress_value(&normal).to_string(),
789 "\x1b]9;4;1;30\x1b\\"
790 );
791
792 let failing = TestEvent {
793 timestamp: Local::now().fixed_offset(),
794 elapsed: Duration::ZERO,
795 kind: TestEventKind::TestStarted {
796 stress_index: None,
797 test_instance: TestInstanceId {
798 binary_id: &binary_id,
799 test_name: &test_name,
800 },
801 slot_assignment: global_slot_assignment(0),
802 current_stats: RunStats {
803 initial_run_count: 10,
804 finished_count: 3,
805 failed: 1,
806 ..RunStats::default()
807 },
808 running: 1,
809 command_line: vec![],
810 },
811 };
812 assert_eq!(
813 terminal_progress_value(&failing).to_string(),
814 "\x1b]9;4;2;30\x1b\\"
815 );
816
817 let cancelling = TestEvent {
818 timestamp: Local::now().fixed_offset(),
819 elapsed: Duration::ZERO,
820 kind: TestEventKind::RunBeginCancel {
821 setup_scripts_running: 0,
822 current_stats: RunStats {
823 initial_run_count: 4,
824 finished_count: 1,
825 cancel_reason: Some(CancelReason::Signal),
826 ..RunStats::default()
827 },
828 running: 3,
829 },
830 };
831 assert_eq!(
832 terminal_progress_value(&cancelling).to_string(),
833 "\x1b]9;4;2;25\x1b\\"
834 );
835
836 let paused = TestEvent {
837 timestamp: Local::now().fixed_offset(),
838 elapsed: Duration::ZERO,
839 kind: TestEventKind::RunPaused {
840 setup_scripts_running: 0,
841 running: 2,
842 },
843 };
844 assert_eq!(
845 terminal_progress_value(&paused).to_string(),
846 "\x1b]9;4;0;\x1b\\"
847 );
848 }
849
850 #[test]
851 fn test_progress_bar_prefix() {
852 let mut styles = Styles::default();
853 styles.colorize();
854
855 for (name, stats) in run_stats_test_failure_examples() {
856 let prefix = progress_bar_prefix(&stats, Some(CancelReason::TestFailure), &styles);
857 assert_eq!(
858 prefix,
859 " Cancelling".style(styles.fail).to_string(),
860 "{name} matches"
861 );
862 }
863 for (name, stats) in run_stats_setup_script_failure_examples() {
864 let prefix =
865 progress_bar_prefix(&stats, Some(CancelReason::SetupScriptFailure), &styles);
866 assert_eq!(
867 prefix,
868 " Cancelling".style(styles.fail).to_string(),
869 "{name} matches"
870 );
871 }
872
873 let prefix = progress_bar_prefix(&RunStats::default(), Some(CancelReason::Signal), &styles);
874 assert_eq!(prefix, " Cancelling".style(styles.fail).to_string());
875
876 let prefix = progress_bar_prefix(&RunStats::default(), None, &styles);
877 assert_eq!(prefix, " Running".style(styles.pass).to_string());
878
879 for (name, stats) in run_stats_test_failure_examples() {
880 let prefix = progress_bar_prefix(&stats, None, &styles);
881 assert_eq!(
882 prefix,
883 " Running".style(styles.fail).to_string(),
884 "{name} matches"
885 );
886 }
887 for (name, stats) in run_stats_setup_script_failure_examples() {
888 let prefix = progress_bar_prefix(&stats, None, &styles);
889 assert_eq!(
890 prefix,
891 " Running".style(styles.fail).to_string(),
892 "{name} matches"
893 );
894 }
895 }
896
897 #[test]
898 fn progress_str_snapshots() {
899 let mut styles = Styles::default();
900 styles.colorize();
901
902 let elapsed = Duration::from_secs(123456);
904 let running = 10;
905
906 for (name, stats) in run_stats_test_failure_examples() {
907 let s = progress_str(elapsed, &stats, running, &styles);
908 insta::assert_snapshot!(format!("{name}_with_cancel_reason"), s);
909
910 let mut stats = stats;
911 stats.cancel_reason = None;
912 let s = progress_str(elapsed, &stats, running, &styles);
913 insta::assert_snapshot!(format!("{name}_without_cancel_reason"), s);
914 }
915
916 for (name, stats) in run_stats_setup_script_failure_examples() {
917 let s = progress_str(elapsed, &stats, running, &styles);
918 insta::assert_snapshot!(format!("{name}_with_cancel_reason"), s);
919
920 let mut stats = stats;
921 stats.cancel_reason = None;
922 let s = progress_str(elapsed, &stats, running, &styles);
923 insta::assert_snapshot!(format!("{name}_without_cancel_reason"), s);
924 }
925 }
926
927 #[test]
928 fn running_test_snapshots() {
929 let styles = Styles::default();
930 let now = Instant::now();
931
932 for (name, running_test) in running_test_examples(now) {
933 let msg = running_test.message(now, 80, &styles);
934 insta::assert_snapshot!(name, msg);
935 }
936 }
937
938 #[test]
939 fn running_test_message_multi_hour() {
940 let styles = Styles::default();
941 let start_time = Instant::now();
942 let now = start_time + Duration::from_secs(5 * 3600 + 13 * 60 + 30);
943
944 let running_test = RunningTest {
945 binary_id: RustBinaryId::new("my-binary"),
946 test_name: TestCaseName::new("test::my_test"),
947 status: RunningTestStatus::Running,
948 start_time,
949 paused_for: Duration::ZERO,
950 };
951 let msg = running_test.message(now, 80, &styles);
952 insta::assert_snapshot!(msg, @" [ 05:13:30] my-binary test::my_test");
953 }
954
955 fn running_test_examples(now: Instant) -> Vec<(&'static str, RunningTest)> {
956 let binary_id = RustBinaryId::new("my-binary");
957 let test_name = TestCaseName::new("test::my_test");
958 let start_time = now - Duration::from_secs(125); vec![
961 (
962 "running_status",
963 RunningTest {
964 binary_id: binary_id.clone(),
965 test_name: test_name.clone(),
966 status: RunningTestStatus::Running,
967 start_time,
968 paused_for: Duration::ZERO,
969 },
970 ),
971 (
972 "slow_status",
973 RunningTest {
974 binary_id: binary_id.clone(),
975 test_name: test_name.clone(),
976 status: RunningTestStatus::Slow,
977 start_time,
978 paused_for: Duration::ZERO,
979 },
980 ),
981 (
982 "delay_status",
983 RunningTest {
984 binary_id: binary_id.clone(),
985 test_name: test_name.clone(),
986 status: RunningTestStatus::Delay(Duration::from_secs(130)),
987 start_time,
988 paused_for: Duration::ZERO,
989 },
990 ),
991 (
992 "delay_status_underflow",
993 RunningTest {
994 binary_id: binary_id.clone(),
995 test_name: test_name.clone(),
996 status: RunningTestStatus::Delay(Duration::from_secs(124)),
997 start_time,
998 paused_for: Duration::ZERO,
999 },
1000 ),
1001 (
1002 "retry_status",
1003 RunningTest {
1004 binary_id: binary_id.clone(),
1005 test_name: test_name.clone(),
1006 status: RunningTestStatus::Retry,
1007 start_time,
1008 paused_for: Duration::ZERO,
1009 },
1010 ),
1011 (
1012 "with_paused_duration",
1013 RunningTest {
1014 binary_id: binary_id.clone(),
1015 test_name: test_name.clone(),
1016 status: RunningTestStatus::Running,
1017 start_time,
1018 paused_for: Duration::from_secs(30),
1019 },
1020 ),
1021 ]
1022 }
1023
1024 fn run_stats_test_failure_examples() -> Vec<(&'static str, RunStats)> {
1025 vec![
1026 (
1027 "one_failed",
1028 RunStats {
1029 initial_run_count: 20,
1030 finished_count: 1,
1031 failed: 1,
1032 cancel_reason: Some(CancelReason::TestFailure),
1033 ..RunStats::default()
1034 },
1035 ),
1036 (
1037 "one_failed_one_passed",
1038 RunStats {
1039 initial_run_count: 20,
1040 finished_count: 2,
1041 failed: 1,
1042 passed: 1,
1043 cancel_reason: Some(CancelReason::TestFailure),
1044 ..RunStats::default()
1045 },
1046 ),
1047 (
1048 "one_exec_failed",
1049 RunStats {
1050 initial_run_count: 20,
1051 finished_count: 10,
1052 exec_failed: 1,
1053 cancel_reason: Some(CancelReason::TestFailure),
1054 ..RunStats::default()
1055 },
1056 ),
1057 (
1058 "one_timed_out",
1059 RunStats {
1060 initial_run_count: 20,
1061 finished_count: 10,
1062 failed_timed_out: 1,
1063 cancel_reason: Some(CancelReason::TestFailure),
1064 ..RunStats::default()
1065 },
1066 ),
1067 ]
1068 }
1069
1070 fn run_stats_setup_script_failure_examples() -> Vec<(&'static str, RunStats)> {
1071 vec![
1072 (
1073 "one_setup_script_failed",
1074 RunStats {
1075 initial_run_count: 30,
1076 setup_scripts_failed: 1,
1077 cancel_reason: Some(CancelReason::SetupScriptFailure),
1078 ..RunStats::default()
1079 },
1080 ),
1081 (
1082 "one_setup_script_exec_failed",
1083 RunStats {
1084 initial_run_count: 35,
1085 setup_scripts_exec_failed: 1,
1086 cancel_reason: Some(CancelReason::SetupScriptFailure),
1087 ..RunStats::default()
1088 },
1089 ),
1090 (
1091 "one_setup_script_timed_out",
1092 RunStats {
1093 initial_run_count: 40,
1094 setup_scripts_timed_out: 1,
1095 cancel_reason: Some(CancelReason::SetupScriptFailure),
1096 ..RunStats::default()
1097 },
1098 ),
1099 ]
1100 }
1101
1102 #[test]
1110 fn update_progress_bar_updates_stats() {
1111 let styles = Styles::default();
1112 let binary_id = RustBinaryId::new("test-binary");
1113 let test_name = TestCaseName::new("test_name");
1114
1115 let mut state = ProgressBarState::new(
1117 NextestRunMode::Test,
1118 10,
1119 "=> ",
1120 MaxProgressRunning::default(),
1121 );
1122
1123 assert_eq!(state.stats, RunStats::default());
1125 assert_eq!(state.running, 0);
1126
1127 let started_stats = RunStats {
1129 initial_run_count: 10,
1130 passed: 5,
1131 ..RunStats::default()
1132 };
1133 let started_event = TestEvent {
1134 timestamp: Local::now().fixed_offset(),
1135 elapsed: Duration::ZERO,
1136 kind: TestEventKind::TestStarted {
1137 stress_index: None,
1138 test_instance: TestInstanceId {
1139 binary_id: &binary_id,
1140 test_name: &test_name,
1141 },
1142 slot_assignment: global_slot_assignment(0),
1143 current_stats: started_stats,
1144 running: 3,
1145 command_line: vec![],
1146 },
1147 };
1148
1149 state.update_progress_bar(&started_event, &styles);
1150
1151 assert_eq!(
1153 state.stats, started_stats,
1154 "stats should be updated on TestStarted"
1155 );
1156 assert_eq!(state.running, 3, "running should be updated on TestStarted");
1157
1158 let msg = state.progress_bar_msg(&styles);
1160 insta::assert_snapshot!(msg, @"3 running, 5 passed, 0 skipped");
1161
1162 let finished_stats = RunStats {
1164 initial_run_count: 10,
1165 finished_count: 1,
1166 passed: 8,
1167 ..RunStats::default()
1168 };
1169 let finished_event = TestEvent {
1170 timestamp: Local::now().fixed_offset(),
1171 elapsed: Duration::ZERO,
1172 kind: TestEventKind::TestFinished {
1173 stress_index: None,
1174 test_instance: TestInstanceId {
1175 binary_id: &binary_id,
1176 test_name: &test_name,
1177 },
1178 success_output: TestOutputDisplay::Never,
1179 failure_output: TestOutputDisplay::Never,
1180 junit_store_success_output: false,
1181 junit_store_failure_output: false,
1182 junit_flaky_fail_status: JunitFlakyFailStatus::default(),
1183 run_statuses: ExecutionStatuses::new(
1184 vec![ExecuteStatus {
1185 retry_data: RetryData {
1186 attempt: 1,
1187 total_attempts: 1,
1188 },
1189 output: make_test_output(),
1190 result: ExecutionResultDescription::Pass,
1191 start_time: Local::now().fixed_offset(),
1192 time_taken: Duration::from_secs(1),
1193 is_slow: false,
1194 delay_before_start: Duration::ZERO,
1195 error_summary: None,
1196 output_error_slice: None,
1197 }],
1198 FlakyResult::default(),
1199 ),
1200 current_stats: finished_stats,
1201 running: 2,
1202 },
1203 };
1204
1205 state.update_progress_bar(&finished_event, &styles);
1206
1207 assert_eq!(
1209 state.stats, finished_stats,
1210 "stats should be updated on TestFinished"
1211 );
1212 assert_eq!(
1213 state.running, 2,
1214 "running should be updated on TestFinished"
1215 );
1216
1217 let msg = state.progress_bar_msg(&styles);
1219 insta::assert_snapshot!(msg, @"2 running, 8 passed, 0 skipped");
1220
1221 let cancel_stats = RunStats {
1223 initial_run_count: 10,
1224 finished_count: 3,
1225 passed: 2,
1226 failed: 1,
1227 cancel_reason: Some(CancelReason::TestFailure),
1228 ..RunStats::default()
1229 };
1230 let cancel_event = TestEvent {
1231 timestamp: Local::now().fixed_offset(),
1232 elapsed: Duration::ZERO,
1233 kind: TestEventKind::RunBeginCancel {
1234 setup_scripts_running: 0,
1235 current_stats: cancel_stats,
1236 running: 4,
1237 },
1238 };
1239
1240 state.update_progress_bar(&cancel_event, &styles);
1241
1242 assert_eq!(
1244 state.stats, cancel_stats,
1245 "stats should be updated on RunBeginCancel"
1246 );
1247 assert_eq!(
1248 state.running, 4,
1249 "running should be updated on RunBeginCancel"
1250 );
1251
1252 let msg = state.progress_bar_msg(&styles);
1254 insta::assert_snapshot!(msg, @"4 running, 2 passed, 1 failed, 0 skipped");
1255
1256 let kill_stats = RunStats {
1258 initial_run_count: 10,
1259 finished_count: 5,
1260 passed: 3,
1261 failed: 2,
1262 cancel_reason: Some(CancelReason::Signal),
1263 ..RunStats::default()
1264 };
1265 let kill_event = TestEvent {
1266 timestamp: Local::now().fixed_offset(),
1267 elapsed: Duration::ZERO,
1268 kind: TestEventKind::RunBeginKill {
1269 setup_scripts_running: 0,
1270 current_stats: kill_stats,
1271 running: 2,
1272 },
1273 };
1274
1275 state.update_progress_bar(&kill_event, &styles);
1276
1277 assert_eq!(
1279 state.stats, kill_stats,
1280 "stats should be updated on RunBeginKill"
1281 );
1282 assert_eq!(
1283 state.running, 2,
1284 "running should be updated on RunBeginKill"
1285 );
1286
1287 let msg = state.progress_bar_msg(&styles);
1289 insta::assert_snapshot!(msg, @"2 running, 3 passed, 2 failed, 0 skipped");
1290 }
1291
1292 fn make_test_output() -> ChildExecutionOutputDescription<LiveSpec> {
1294 ChildExecutionOutput::Output {
1295 result: Some(ExecutionResult::Pass),
1296 output: ChildOutput::Split(ChildSplitOutput {
1297 stdout: Some(Bytes::new().into()),
1298 stderr: Some(Bytes::new().into()),
1299 }),
1300 errors: None,
1301 }
1302 .into()
1303 }
1304}