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