forge/
progress.rs

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