Fix test progress bar for small screens.

This commit is contained in:
Jean-Christophe Amiel 2024-06-24 12:07:06 +02:00
parent 6546d11cf8
commit 8aba7c2bef
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
5 changed files with 83 additions and 15 deletions

1
Cargo.lock generated
View File

@ -527,6 +527,7 @@ dependencies = [
"serde_json", "serde_json",
"sha2", "sha2",
"similar", "similar",
"terminal_size",
"termion", "termion",
"url", "url",
"uuid", "uuid",

View File

@ -44,6 +44,7 @@ lazy_static = "1.5.0"
# uuid features: lets you generate random UUIDs and use a faster (but still sufficiently random) RNG # uuid features: lets you generate random UUIDs and use a faster (but still sufficiently random) RNG
uuid = { version = "1.8.0", features = ["v4" , "fast-rng"] } uuid = { version = "1.8.0", features = ["v4" , "fast-rng"] }
similar = "2.5.0" similar = "2.5.0"
terminal_size = "0.3.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
termion = "4.0.2" termion = "4.0.2"

View File

@ -31,6 +31,8 @@ pub struct ParProgress {
mode: Mode, mode: Mode,
/// The standard error format for message: ANSI or plain. /// The standard error format for message: ANSI or plain.
format: Format, format: Format,
/// The maximum width of the progress string, in chars.
max_width: Option<usize>,
/// Save last progress bar refresh to limits flickering. /// Save last progress bar refresh to limits flickering.
throttle: Throttle, throttle: Throttle,
} }
@ -52,12 +54,18 @@ const FIRST_THROTTLE: Duration = Duration::from_millis(16);
impl ParProgress { impl ParProgress {
/// Creates a new instance. /// Creates a new instance.
pub fn new(max_running_displayed: usize, mode: Mode, color: bool) -> Self { pub fn new(
max_running_displayed: usize,
mode: Mode,
color: bool,
max_width: Option<usize>,
) -> Self {
let format = if color { Format::Ansi } else { Format::Plain }; let format = if color { Format::Ansi } else { Format::Plain };
ParProgress { ParProgress {
max_running_displayed, max_running_displayed,
mode, mode,
format, format,
max_width,
throttle: Throttle::new(UPDATE_INTERVAL, FIRST_THROTTLE), throttle: Throttle::new(UPDATE_INTERVAL, FIRST_THROTTLE),
} }
} }
@ -91,6 +99,7 @@ impl ParProgress {
count, count,
self.max_running_displayed, self.max_running_displayed,
self.format, self.format,
self.max_width,
) else { ) else {
return; return;
}; };
@ -199,13 +208,15 @@ impl Throttle {
/// ///
/// `max_running_displayed` is used to limit the number of running progress bar. If more jobs are /// `max_running_displayed` is used to limit the number of running progress bar. If more jobs are
/// running, a label "...x more" is displayed. /// running, a label "...x more" is displayed.
/// `color` is `true` when the returned progress string uses color. /// `format` is the format of the progress string (ANSI or plain).
/// The progress string is wrapped with new lines at width `max_width`.
fn build_progress( fn build_progress(
workers: &[(Worker, WorkerState)], workers: &[(Worker, WorkerState)],
completed: usize, completed: usize,
count: Option<usize>, count: Option<usize>,
max_running_displayed: usize, max_running_displayed: usize,
format: Format, format: Format,
max_width: Option<usize>,
) -> Option<String> { ) -> Option<String> {
// Select the running workers to be displayed // Select the running workers to be displayed
let mut workers = workers let mut workers = workers
@ -237,7 +248,7 @@ fn build_progress(
}) })
.max() .max()
.unwrap(); .unwrap();
let max_width = 2 * (((max as f64).log10() as usize) + 1) + 1; let max_completed_width = 2 * (((max as f64).log10() as usize) + 1) + 1;
// Construct all the progress strings // Construct all the progress strings
let mut all_progress = String::new(); let mut all_progress = String::new();
@ -248,6 +259,8 @@ fn build_progress(
} }
None => format!("Executed files: {completed}\n"), None => format!("Executed files: {completed}\n"),
}; };
// We don't wrap this string for the moment, there is low chance to overlap the maximum width
// of the terminal.
all_progress.push_str(&progress); all_progress.push_str(&progress);
for (_, state) in &workers { for (_, state) in &workers {
@ -259,7 +272,7 @@ fn build_progress(
{ {
let entry_index = entry_index + 1; // entry index display is 1-based let entry_index = entry_index + 1; // entry index display is 1-based
let requests = format!("{entry_index}/{entry_count}"); let requests = format!("{entry_index}/{entry_count}");
let padding = " ".repeat(max_width - requests.len()); let padding = " ".repeat(max_completed_width - requests.len());
let bar = progress_bar(entry_index, *entry_count); let bar = progress_bar(entry_index, *entry_count);
let mut progress = StyledString::new(); let mut progress = StyledString::new();
@ -271,6 +284,13 @@ fn build_progress(
progress.push_with("Running", Style::new().cyan().bold()); progress.push_with("Running", Style::new().cyan().bold());
progress.push("\n"); progress.push("\n");
// We wrap the progress string with new lines if necessary
if let Some(max_width) = max_width {
if progress.len() >= max_width {
progress = progress.wrap(max_width);
}
}
let progress = progress.to_string(format); let progress = progress.to_string(format);
all_progress.push_str(&progress); all_progress.push_str(&progress);
} }
@ -370,7 +390,14 @@ mod tests {
(w4, WorkerState::Idle), (w4, WorkerState::Idle),
]; ];
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert!(progress.is_none()); assert!(progress.is_none());
workers[0].1 = new_running_state(&jobs[0], 0, 10); workers[0].1 = new_running_state(&jobs[0], 0, 10);
@ -379,7 +406,14 @@ mod tests {
workers[3].1 = new_running_state(&jobs[3], 0, 7); workers[3].1 = new_running_state(&jobs[3], 0, 7);
workers[4].1 = new_running_state(&jobs[4], 0, 4); workers[4].1 = new_running_state(&jobs[4], 0, 4);
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert_eq!( assert_eq!(
progress.unwrap(), progress.unwrap(),
"\ "\
@ -397,7 +431,14 @@ Executed files: 75/100 (75%)\n\
workers[3].1 = new_running_state(&jobs[3], 3, 7); workers[3].1 = new_running_state(&jobs[3], 3, 7);
workers[4].1 = new_running_state(&jobs[4], 1, 4); workers[4].1 = new_running_state(&jobs[4], 1, 4);
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert_eq!( assert_eq!(
progress.unwrap(), progress.unwrap(),
"\ "\
@ -415,7 +456,14 @@ Executed files: 75/100 (75%)\n\
workers[3].1 = new_running_state(&jobs[3], 5, 7); workers[3].1 = new_running_state(&jobs[3], 5, 7);
workers[4].1 = new_running_state(&jobs[4], 2, 4); workers[4].1 = new_running_state(&jobs[4], 2, 4);
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert_eq!( assert_eq!(
progress.unwrap(), progress.unwrap(),
"\ "\
@ -433,7 +481,14 @@ Executed files: 75/100 (75%)\n\
workers[3].1 = WorkerState::Idle; workers[3].1 = WorkerState::Idle;
workers[4].1 = new_running_state(&jobs[4], 3, 4); workers[4].1 = new_running_state(&jobs[4], 3, 4);
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert_eq!( assert_eq!(
progress.unwrap(), progress.unwrap(),
"\ "\
@ -449,7 +504,14 @@ Executed files: 75/100 (75%)\n\
workers[3].1 = WorkerState::Idle; workers[3].1 = WorkerState::Idle;
workers[4].1 = WorkerState::Idle; workers[4].1 = WorkerState::Idle;
let progress = build_progress(&workers, completed, total, max_displayed, Format::Plain); let progress = build_progress(
&workers,
completed,
total,
max_displayed,
Format::Plain,
None,
);
assert_eq!( assert_eq!(
progress.unwrap(), progress.unwrap(),
"\ "\
@ -475,7 +537,7 @@ Executed files: 75/100 (75%)\n\
assert_eq!(progress_bar(2, 3), "[========> ] 2/3"); assert_eq!(progress_bar(2, 3), "[========> ] 2/3");
assert_eq!(progress_bar(3, 3), "[================> ] 3/3"); assert_eq!(progress_bar(3, 3), "[================> ] 3/3");
// Progress strings with 1 entries: // Progress strings with 1 entry:
assert_eq!(progress_bar(1, 1), "[> ] 1/1"); assert_eq!(progress_bar(1, 1), "[> ] 1/1");
} }
} }

View File

@ -95,11 +95,12 @@ impl ParallelRunner {
/// whether as a raw response body bytes, or in a structured JSON output. /// whether as a raw response body bytes, or in a structured JSON output.
/// ///
/// The runner can repeat running a list of jobs. For instance, when repeating two times the job /// The runner can repeat running a list of jobs. For instance, when repeating two times the job
/// sequence (`a`, `b`, `c`), runner will act as if it runs (`a`, `b`, `c`). /// sequence (`a`, `b`, `c`), runner will act as if it runs (`a`, `b`, `c`, `a`, `b`, `c`).
/// ///
/// If `test` mode is `true` the runner is run in "test" mode, reporting the success or failure /// If `test` mode is `true` the runner is run in "test" mode, reporting the success or failure
/// of each file on standard error. Additionally to the test mode, a `progress_bar` designed for /// of each file on standard error. In addition to the test mode, a `progress_bar` designed for
/// parallel run progression can be used. /// parallel run progression can be used. When the progress bar is displayed, it's wrapped with
/// new lines at width `max_width`.
/// ///
/// `color` determines if color if used in standard error. /// `color` determines if color if used in standard error.
pub fn new( pub fn new(
@ -109,6 +110,7 @@ impl ParallelRunner {
test: bool, test: bool,
progress_bar: bool, progress_bar: bool,
color: bool, color: bool,
max_width: Option<usize>,
) -> Self { ) -> Self {
// Worker are running on theirs own thread, while parallel runner is running in the main // Worker are running on theirs own thread, while parallel runner is running in the main
// thread. // thread.
@ -128,7 +130,7 @@ impl ParallelRunner {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mode = Mode::new(test, progress_bar); let mode = Mode::new(test, progress_bar);
let progress = ParProgress::new(MAX_RUNNING_DISPLAYED, mode, color); let progress = ParProgress::new(MAX_RUNNING_DISPLAYED, mode, color, max_width);
ParallelRunner { ParallelRunner {
workers, workers,

View File

@ -174,6 +174,7 @@ pub fn run_par(
let output_type = options let output_type = options
.output_type .output_type
.to_output_type(options.include, options.color); .to_output_type(options.include, options.color);
let max_width = terminal_size::terminal_size().map(|(w, _)| w.0 as usize);
let jobs = files let jobs = files
.iter() .iter()
@ -192,6 +193,7 @@ pub fn run_par(
options.test, options.test,
options.progress_bar, options.progress_bar,
options.color, options.color,
max_width,
); );
let results = runner.run(&jobs)?; let results = runner.run(&jobs)?;
let results = results.into_iter().map(HurlRun::from).collect(); let results = results.into_iter().map(HurlRun::from).collect();