@@ -6,25 +6,38 @@ use crate::{
66 helpers:: { DisplayTestInstance , plural} ,
77 list:: TestInstanceId ,
88 reporter:: {
9- PROGRESS_REFRESH_RATE_HZ , displayer:: formatters:: DisplayBracketedHhMmSs , events:: * ,
10- helpers:: Styles ,
9+ displayer:: formatters:: DisplayBracketedHhMmSs ,
10+ events:: * ,
11+ helpers:: { Styles , print_lines_in_chunks} ,
1112 } ,
12- write_str:: WriteStr ,
1313} ;
1414use indicatif:: { ProgressBar , ProgressDrawTarget , ProgressStyle } ;
1515use nextest_metadata:: RustBinaryId ;
1616use owo_colors:: OwoColorize ;
1717use std:: {
1818 cmp:: { max, min} ,
1919 env, fmt,
20- io:: { self , IsTerminal , Write } ,
20+ io:: IsTerminal ,
2121 num:: NonZero ,
2222 str:: FromStr ,
2323 time:: { Duration , Instant } ,
2424} ;
2525use swrite:: { SWrite , swrite} ;
2626use tracing:: debug;
2727
28+ /// The refresh rate for the progress bar, set to a minimal value.
29+ ///
30+ /// For progress, during each tick, two things happen:
31+ ///
32+ /// - We update the message, calling self.bar.set_message.
33+ /// - We print any buffered output.
34+ ///
35+ /// We want both of these updates to be combined into one terminal flush, so we
36+ /// set *this* to a minimal value (so self.bar.set_message doesn't do a redraw),
37+ /// and rely on ProgressBar::print_and_flush_buffer to always flush the
38+ /// terminal.
39+ const PROGRESS_REFRESH_RATE_HZ : u8 = 1 ;
40+
2841/// The maximum number of running tests to display with
2942/// `--show-progress=running` or `only`.
3043#[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
@@ -159,7 +172,10 @@ pub(super) struct ProgressBarState {
159172 max_running_displayed : usize ,
160173 // None when the running tests are not displayed
161174 running_tests : Option < Vec < RunningTest > > ,
162- buffer : Vec < u8 > ,
175+ buffer : String ,
176+ // Size in bytes for chunking println calls. Configurable via the
177+ // undocumented __NEXTEST_PROGRESS_PRINTLN_CHUNK_SIZE env var.
178+ println_chunk_size : usize ,
163179 // Reasons for hiding the progress bar. We show the progress bar if none of
164180 // these are set and hide it if any of them are set.
165181 //
@@ -201,14 +217,23 @@ impl ProgressBarState {
201217
202218 let running_tests = show_running. then ( Vec :: new) ;
203219
220+ // The println chunk size defaults to a value chosen by experimentation,
221+ // locally and over SSH. This controls how often the progress bar
222+ // refreshes during large output bursts.
223+ let println_chunk_size = env:: var ( "__NEXTEST_PROGRESS_PRINTLN_CHUNK_SIZE" )
224+ . ok ( )
225+ . and_then ( |s| s. parse :: < usize > ( ) . ok ( ) )
226+ . unwrap_or ( 4096 ) ;
227+
204228 Self {
205229 bar,
206230 stats : RunStats :: default ( ) ,
207231 running : 0 ,
208232 max_progress_running,
209233 max_running_displayed : 0 ,
210234 running_tests,
211- buffer : Vec :: new ( ) ,
235+ buffer : String :: new ( ) ,
236+ println_chunk_size,
212237 hidden_no_capture : false ,
213238 hidden_run_paused : false ,
214239 hidden_info_response : false ,
@@ -222,25 +247,38 @@ impl ProgressBarState {
222247 }
223248
224249 fn print_and_clear_buffer ( & mut self ) {
225- self . print_buffer ( ) ;
250+ self . print_and_force_redraw ( ) ;
226251 self . buffer . clear ( ) ;
227252 }
228253
229- fn print_buffer ( & self ) {
230- // ProgressBar::println doesn't print status lines if the bar is
231- // hidden. The suspend method prints it in all cases.
232- // suspend forces a full redraw, so we call it only if there is
233- // something in the buffer
234- if !self . buffer . is_empty ( ) {
235- self . bar . suspend ( || {
236- std:: io:: stderr ( )
237- . write_all ( & self . buffer )
238- . expect ( "write to succeed" )
239- } ) ;
254+ /// Prints the contents of the buffer, and always forces a redraw.
255+ fn print_and_force_redraw ( & self ) {
256+ if self . buffer . is_empty ( ) {
257+ // Force a redraw as part of our contract. See the documentation for
258+ // `PROGRESS_REFRESH_RATE_HZ`.
259+ self . bar . force_draw ( ) ;
260+ return ;
240261 }
262+
263+ // println below also forces a redraw, so we don't need to call
264+ // force_draw in this case.
265+
266+ // ProgressBar::println is only called if there's something in the
267+ // buffer, for two reasons:
268+ //
269+ // 1. If passed in nothing at all, it prints an empty line.
270+ // 2. It forces a full redraw.
271+ //
272+ // But if self.buffer is too large, we can overwhelm the terminal with
273+ // large amounts of non-progress-bar output, causing the progress bar to
274+ // flicker in and out. To avoid those issues, we chunk the output to
275+ // maintain progress bar visibility by redrawing it regularly.
276+ print_lines_in_chunks ( & self . buffer , self . println_chunk_size , |chunk| {
277+ self . bar . println ( chunk) ;
278+ } ) ;
241279 }
242280
243- pub ( super ) fn update_message ( & mut self , styles : & Styles ) {
281+ fn update_message ( & mut self , styles : & Styles ) {
244282 let mut msg = progress_bar_msg ( & self . stats , self . running , styles) ;
245283 msg += " " ;
246284
@@ -457,12 +495,12 @@ impl ProgressBarState {
457495 }
458496
459497 pub ( super ) fn write_buf ( & mut self , buf : & str ) {
460- self . buffer . extend_from_slice ( buf. as_bytes ( ) ) ;
498+ self . buffer . push_str ( buf) ;
461499 }
462500
463501 #[ inline]
464502 pub ( super ) fn finish_and_clear ( & self ) {
465- self . print_buffer ( ) ;
503+ self . print_and_force_redraw ( ) ;
466504 self . bar . finish_and_clear ( ) ;
467505 }
468506
@@ -487,7 +525,10 @@ impl ProgressBarState {
487525}
488526
489527/// OSC 9 terminal progress reporting.
490- pub ( super ) struct TerminalProgress { }
528+ #[ derive( Default ) ]
529+ pub ( super ) struct TerminalProgress {
530+ last_value : TerminalProgressValue ,
531+ }
491532
492533impl TerminalProgress {
493534 const ENV : & str = "CARGO_TERM_PROGRESS_TERM_INTEGRATION" ;
@@ -500,7 +541,7 @@ impl TerminalProgress {
500541 if let Some ( v) = config. term . progress . term_integration {
501542 if v {
502543 debug ! ( "enabling terminal progress reporting based on {source:?}" ) ;
503- return Some ( Self { } ) ;
544+ return Some ( Self :: default ( ) ) ;
504545 } else {
505546 debug ! ( "disabling terminal progress reporting based on {source:?}" ) ;
506547 return None ;
@@ -514,7 +555,7 @@ impl TerminalProgress {
514555 "enabling terminal progress reporting based on \
515556 CARGO_TERM_PROGRESS_TERM_INTEGRATION environment variable"
516557 ) ;
517- return Some ( Self { } ) ;
558+ return Some ( Self :: default ( ) ) ;
518559 } else if v == "false" {
519560 debug ! (
520561 "disabling terminal progress reporting based on \
@@ -533,7 +574,7 @@ impl TerminalProgress {
533574 if let Some ( v) = config. term . progress . term_integration {
534575 if v {
535576 debug ! ( "enabling terminal progress reporting based on {source:?}" ) ;
536- return Some ( Self { } ) ;
577+ return Some ( Self :: default ( ) ) ;
537578 } else {
538579 debug ! ( "disabling terminal progress reporting based on {source:?}" ) ;
539580 return None ;
@@ -543,14 +584,10 @@ impl TerminalProgress {
543584 }
544585 }
545586
546- supports_osc_9_4 ( stream) . then_some ( TerminalProgress { } )
587+ supports_osc_9_4 ( stream) . then ( Self :: default )
547588 }
548589
549- pub ( super ) fn update_progress (
550- & self ,
551- event : & TestEvent < ' _ > ,
552- writer : & mut dyn WriteStr ,
553- ) -> Result < ( ) , io:: Error > {
590+ pub ( super ) fn update_progress ( & mut self , event : & TestEvent < ' _ > ) {
554591 let value = match & event. kind {
555592 TestEventKind :: RunStarted { .. }
556593 | TestEventKind :: StressSubRunStarted { .. }
@@ -594,7 +631,11 @@ impl TerminalProgress {
594631 }
595632 } ;
596633
597- write ! ( writer, "{value}" )
634+ self . last_value = value;
635+ }
636+
637+ pub ( super ) fn last_value ( & self ) -> & TerminalProgressValue {
638+ & self . last_value
598639 }
599640}
600641
@@ -628,9 +669,10 @@ fn supports_osc_9_4(stream: &dyn IsTerminal) -> bool {
628669/// A progress status value printable as an ANSI OSC 9;4 escape code.
629670///
630671/// Adapted from Cargo 1.87.
631- #[ derive( PartialEq , Debug ) ]
632- enum TerminalProgressValue {
672+ #[ derive( PartialEq , Debug , Default ) ]
673+ pub ( super ) enum TerminalProgressValue {
633674 /// No output.
675+ #[ default]
634676 None ,
635677 /// Remove progress.
636678 Remove ,
0 commit comments