1use 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#[derive(Debug)]
17pub struct TestsProgressState {
18 multi: MultiProgress,
20 overall_progress: ProgressBar,
22 suites_progress: HashMap<String, ProgressBar>,
24}
25
26impl TestsProgressState {
27 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 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 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 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 pub fn clear(&mut self) {
92 self.multi.clear().unwrap();
93 }
94}
95
96#[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}