Skip to main content

forge/
progress.rs

1//! Progress bars for the test run.
2
3use alloy_primitives::map::HashMap;
4use chrono::Utc;
5use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
6use parking_lot::Mutex;
7use std::{sync::Arc, time::Duration};
8
9const TICK_CHARS: &str = "⠁⠂⠄⡀⢀⠠⠐⠈ ";
10
11/// State of [ProgressBar]s displayed for the given test run.
12/// Shows progress of all test suites matching filter.
13/// For each test within the test suite an individual progress bar is displayed.
14/// When a test suite completes, their progress is removed from overall progress and result summary
15/// is displayed.
16#[derive(Debug)]
17pub struct TestsProgressState {
18    /// Main [MultiProgress] instance showing progress for all test suites.
19    multi: MultiProgress,
20    /// Progress bar counting completed / remaining test suites.
21    overall_progress: ProgressBar,
22    /// Individual test suites progress.
23    suites_progress: HashMap<String, ProgressBar>,
24}
25
26impl TestsProgressState {
27    /// Creates overall tests progress state.
28    pub fn new(suites_len: usize, threads_no: usize) -> Self {
29        let multi = MultiProgress::new();
30        let overall_progress = multi.add(ProgressBar::new(suites_len as u64));
31        overall_progress.set_style(
32            ProgressStyle::with_template("{bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
33                .unwrap()
34                .progress_chars("##-"),
35        );
36        overall_progress.set_message(format!("completed (with {threads_no} threads)"));
37        Self { multi, overall_progress, suites_progress: HashMap::default() }
38    }
39
40    /// Creates new test suite progress and add it to overall progress.
41    pub fn start_suite_progress(&mut self, suite_name: &str) {
42        let suite_progress = self.multi.add(ProgressBar::new_spinner());
43        suite_progress.set_style(
44            ProgressStyle::with_template("{spinner} {wide_msg:.bold.dim}")
45                .unwrap()
46                .tick_chars(TICK_CHARS),
47        );
48        suite_progress.set_message(format!("{suite_name} "));
49        suite_progress.enable_steady_tick(Duration::from_millis(100));
50        self.suites_progress.insert(suite_name.to_owned(), suite_progress);
51    }
52
53    /// Prints suite result summary and removes it from overall progress.
54    pub fn end_suite_progress(&mut self, suite_name: &str, result_summary: String) {
55        let Some(suite_progress) = self.suites_progress.remove(suite_name) else { return };
56        self.multi.suspend(|| {
57            let _ = sh_println!("{suite_name}\n  ↪ {result_summary}");
58        });
59        suite_progress.finish_and_clear();
60        self.overall_progress.inc(1);
61    }
62
63    /// Creates progress entry for fuzz tests.
64    /// Set the prefix and total number of runs. Message is updated during execution with current
65    /// phase. Test progress is placed under test suite progress entry so all tests within suite
66    /// are grouped.
67    pub fn start_fuzz_progress(
68        &mut self,
69        suite_name: &str,
70        test_name: &str,
71        timeout: Option<u32>,
72        runs: u32,
73    ) -> Option<ProgressBar> {
74        let suite_progress = self.suites_progress.get(suite_name)?;
75        let fuzz_progress = self.multi.insert_after(suite_progress, ProgressBar::new(runs as u64));
76        let template = if let Some(timeout) = timeout {
77            let ends_at = (Utc::now() + chrono::Duration::seconds(timeout.into()))
78                .format("%H:%M:%S %Y-%m-%d");
79            format!("    ↪ {{prefix:.bold.dim}}: [{{pos}}] Runs, ends at {ends_at} UTC {{msg}}")
80        } else {
81            "    ↪ {prefix:.bold.dim}: [{pos}/{len}] Runs {msg}".to_string()
82        };
83        fuzz_progress
84            .set_style(ProgressStyle::with_template(&template).unwrap().tick_chars(TICK_CHARS));
85        fuzz_progress.set_prefix(test_name.to_owned());
86        fuzz_progress.enable_steady_tick(Duration::from_millis(100));
87        Some(fuzz_progress)
88    }
89
90    /// Removes overall test progress.
91    pub fn clear(&mut self) {
92        self.multi.clear().unwrap();
93    }
94}
95
96/// Cloneable wrapper around [TestsProgressState].
97#[derive(Debug, Clone)]
98pub struct TestsProgress {
99    pub inner: Arc<Mutex<TestsProgressState>>,
100}
101
102impl TestsProgress {
103    pub fn new(suites_len: usize, threads_no: usize) -> Self {
104        Self { inner: Arc::new(Mutex::new(TestsProgressState::new(suites_len, threads_no))) }
105    }
106}