nextest_runner/helpers/
progress.rs1use crate::cargo_config::{CargoConfigs, DiscoveredConfig};
5use anstyle_progress::{TermProgress, supports_term_progress};
6use indicatif::ProgressStyle;
7use std::{
8 env,
9 io::{self, Write},
10};
11use tracing::debug;
12
13pub(crate) const PROGRESS_REFRESH_RATE_HZ: u8 = 1;
25
26pub(crate) fn progress_bar_style(progress_chars: &str, suffix: &str) -> ProgressStyle {
27 let template = format!("{{prefix:>12}} [{{elapsed_precise:>9}}] {{wide_bar}} {suffix}");
31 ProgressStyle::default_bar()
32 .progress_chars(progress_chars)
33 .template(&template)
34 .expect("template is known to be valid")
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum ShowTerminalProgress {
40 Yes,
42
43 No,
45}
46
47impl ShowTerminalProgress {
48 const ENV: &str = "CARGO_TERM_PROGRESS_TERM_INTEGRATION";
49
50 pub fn from_cargo_configs(configs: &CargoConfigs, is_terminal: bool) -> Self {
53 for config in configs.discovered_configs() {
55 match config {
56 DiscoveredConfig::CliOption { config, source } => {
57 if let Some(v) = config.term.progress.term_integration {
58 if v {
59 debug!("enabling terminal progress reporting based on {source:?}");
60 return Self::Yes;
61 } else {
62 debug!("disabling terminal progress reporting based on {source:?}");
63 return Self::No;
64 }
65 }
66 }
67 DiscoveredConfig::Env => {
68 if let Some(v) = env::var_os(Self::ENV) {
69 if v == "true" {
70 debug!(
71 "enabling terminal progress reporting based on \
72 CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
73 );
74 return Self::Yes;
75 } else if v == "false" {
76 debug!(
77 "disabling terminal progress reporting based on \
78 CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
79 );
80 return Self::No;
81 } else {
82 debug!(
83 "invalid value for CARGO_TERM_PROGRESS_TERM_INTEGRATION \
84 environment variable: {v:?}, ignoring"
85 );
86 }
87 }
88 }
89 DiscoveredConfig::File { config, source } => {
90 if let Some(v) = config.term.progress.term_integration {
91 if v {
92 debug!("enabling terminal progress reporting based on {source:?}");
93 return Self::Yes;
94 } else {
95 debug!("disabling terminal progress reporting based on {source:?}");
96 return Self::No;
97 }
98 }
99 }
100 }
101 }
102
103 let show = supports_term_progress(is_terminal);
104 debug!(is_terminal, show, "autodetected terminal progress support");
105 if show { Self::Yes } else { Self::No }
106 }
107}
108
109#[derive(Default)]
111pub(crate) struct TerminalProgress {
112 last_value: TermProgress,
113}
114
115impl TerminalProgress {
116 pub(crate) fn new(show: ShowTerminalProgress) -> Option<Self> {
117 match show {
118 ShowTerminalProgress::Yes => Some(Self::default()),
119 ShowTerminalProgress::No => None,
120 }
121 }
122
123 pub(crate) fn set(&mut self, value: TermProgress) {
124 self.last_value = value;
125 }
126
127 pub(crate) fn emit(&self) {
128 let _ = write!(io::stderr(), "{}", self.last_value);
132 }
133}
134
135pub(crate) fn term_progress_percent(done: usize, total: usize) -> u8 {
136 if total == 0 {
137 return 100;
138 }
139 ((done as f64 / total as f64) * 100.0)
140 .round()
141 .clamp(0.0, 100.0) as u8
142}
143
144#[cfg(test)]
145mod tests {
146 use super::term_progress_percent;
147
148 #[test]
149 fn term_progress_percent_boundaries() {
150 assert_eq!(term_progress_percent(0, 0), 100);
152 assert_eq!(term_progress_percent(0, 10), 0);
153 assert_eq!(term_progress_percent(1, 2), 50);
154 assert_eq!(term_progress_percent(1, 4), 25);
155 assert_eq!(term_progress_percent(10, 10), 100);
156
157 assert_eq!(term_progress_percent(1, 8), 13);
159 assert_eq!(term_progress_percent(3, 8), 38);
160
161 assert_eq!(term_progress_percent(11, 10), 100);
163 }
164}