@@ -10,8 +10,10 @@ use crate::{
1010 write_str:: WriteStr ,
1111} ;
1212use camino:: { Utf8Path , Utf8PathBuf } ;
13+ use console:: AnsiCodeIterator ;
1314use owo_colors:: { OwoColorize , Style } ;
1415use std:: { fmt, io, path:: PathBuf , process:: ExitStatus , time:: Duration } ;
16+ use unicode_width:: UnicodeWidthChar ;
1517
1618/// Utilities for pluralizing various words based on count or plurality.
1719pub mod plural {
@@ -102,6 +104,7 @@ pub(crate) struct DisplayTestInstance<'a> {
102104 display_counter_index : Option < DisplayCounterIndex > ,
103105 instance : TestInstanceId < ' a > ,
104106 styles : & ' a Styles ,
107+ max_width : Option < usize > ,
105108}
106109
107110impl < ' a > DisplayTestInstance < ' a > {
@@ -116,34 +119,180 @@ impl<'a> DisplayTestInstance<'a> {
116119 display_counter_index,
117120 instance,
118121 styles,
122+ max_width : None ,
119123 }
120124 }
125+
126+ pub ( crate ) fn with_max_width ( mut self , max_width : usize ) -> Self {
127+ self . max_width = Some ( max_width) ;
128+ self
129+ }
121130}
122131
123132impl fmt:: Display for DisplayTestInstance < ' _ > {
124133 fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
125- if let Some ( stress_index ) = self . stress_index {
126- write ! (
127- f ,
134+ // Figure out the widths for each component.
135+ let stress_index_str = if let Some ( stress_index ) = self . stress_index {
136+ format ! (
128137 "[{}] " ,
129138 DisplayStressIndex {
130139 stress_index,
131140 count_style: self . styles. count,
132141 }
133- ) ?;
142+ )
143+ } else {
144+ String :: new ( )
145+ } ;
146+ let counter_index_str = if let Some ( display_counter_index) = & self . display_counter_index {
147+ format ! ( "{display_counter_index} " )
148+ } else {
149+ String :: new ( )
150+ } ;
151+ let binary_id_str = format ! ( "{} " , self . instance. binary_id. style( self . styles. binary_id) ) ;
152+ let test_name_str = format ! (
153+ "{}" ,
154+ DisplayTestName :: new( self . instance. test_name, self . styles)
155+ ) ;
156+
157+ // If a max width is defined, trim strings until they fit into it.
158+ if let Some ( max_width) = self . max_width {
159+ // We have to be careful while computing string width -- the strings
160+ // above include ANSI escape codes which have a display width of
161+ // zero.
162+ let stress_index_width = text_width ( & stress_index_str) ;
163+ let counter_index_width = text_width ( & counter_index_str) ;
164+ let binary_id_width = text_width ( & binary_id_str) ;
165+ let test_name_width = text_width ( & test_name_str) ;
166+
167+ // Truncate components in order, from most important to keep to least:
168+ //
169+ // * stress-index (left-aligned)
170+ // * counter index (left-aligned)
171+ // * binary ID (left-aligned)
172+ // * test name (right-aligned)
173+ let mut stress_index_resolved_width = stress_index_width;
174+ let mut counter_index_resolved_width = counter_index_width;
175+ let mut binary_id_resolved_width = binary_id_width;
176+ let mut test_name_resolved_width = test_name_width;
177+
178+ // Truncate stress-index first.
179+ if stress_index_resolved_width > max_width {
180+ stress_index_resolved_width = max_width;
181+ }
182+
183+ // Truncate counter index next.
184+ let remaining_width = max_width. saturating_sub ( stress_index_resolved_width) ;
185+ if counter_index_resolved_width > remaining_width {
186+ counter_index_resolved_width = remaining_width;
187+ }
188+
189+ // Truncate binary ID next.
190+ let remaining_width = max_width
191+ . saturating_sub ( stress_index_resolved_width)
192+ . saturating_sub ( counter_index_resolved_width) ;
193+ if binary_id_resolved_width > remaining_width {
194+ binary_id_resolved_width = remaining_width;
195+ }
196+
197+ // Truncate test name last.
198+ let remaining_width = max_width
199+ . saturating_sub ( stress_index_resolved_width)
200+ . saturating_sub ( counter_index_resolved_width)
201+ . saturating_sub ( binary_id_resolved_width) ;
202+ if test_name_resolved_width > remaining_width {
203+ test_name_resolved_width = remaining_width;
204+ }
205+
206+ // Now truncate the strings if applicable.
207+ let test_name_truncated_str = if test_name_resolved_width == test_name_width {
208+ test_name_str
209+ } else {
210+ // Right-align the test name.
211+ truncate_ansi_aware (
212+ & test_name_str,
213+ test_name_width. saturating_sub ( test_name_resolved_width) ,
214+ test_name_width,
215+ )
216+ } ;
217+ let binary_id_truncated_str = if binary_id_resolved_width == binary_id_width {
218+ binary_id_str
219+ } else {
220+ // Left-align the binary ID.
221+ truncate_ansi_aware ( & binary_id_str, 0 , binary_id_resolved_width)
222+ } ;
223+ let counter_index_truncated_str = if counter_index_resolved_width == counter_index_width
224+ {
225+ counter_index_str
226+ } else {
227+ // Left-align the counter index.
228+ truncate_ansi_aware ( & counter_index_str, 0 , counter_index_resolved_width)
229+ } ;
230+ let stress_index_truncated_str = if stress_index_resolved_width == stress_index_width {
231+ stress_index_str
232+ } else {
233+ // Left-align the stress index.
234+ truncate_ansi_aware ( & stress_index_str, 0 , stress_index_resolved_width)
235+ } ;
236+
237+ write ! (
238+ f,
239+ "{}{}{}{}" ,
240+ stress_index_truncated_str,
241+ counter_index_truncated_str,
242+ binary_id_truncated_str,
243+ test_name_truncated_str,
244+ )
245+ } else {
246+ write ! (
247+ f,
248+ "{}{}{}{}" ,
249+ stress_index_str, counter_index_str, binary_id_str, test_name_str
250+ )
134251 }
252+ }
253+ }
135254
136- if let Some ( display_counter_index) = & self . display_counter_index {
137- write ! ( f, "{display_counter_index} " ) ?
255+ fn text_width ( text : & str ) -> usize {
256+ // Technically, the width of a string may not be the same as the sum of the
257+ // widths of its characters. But managing truncation is pretty difficult. See
258+ // https://docs.rs/unicode-width/latest/unicode_width/#rules-for-determining-width.
259+ //
260+ // This is quite difficult to manage truncation for, so we just use the sum
261+ // of the widths of the string's characters (both here and in
262+ // truncate_ansi_aware below).
263+ strip_ansi_escapes:: strip_str ( text)
264+ . chars ( )
265+ . map ( |c| c. width ( ) . unwrap_or ( 0 ) )
266+ . sum ( )
267+ }
268+
269+ fn truncate_ansi_aware ( text : & str , start : usize , end : usize ) -> String {
270+ let mut pos = 0 ;
271+ let mut res = String :: new ( ) ;
272+ for ( s, is_ansi) in AnsiCodeIterator :: new ( text) {
273+ if is_ansi {
274+ res. push_str ( s) ;
275+ continue ;
276+ } else if pos >= end {
277+ // We retain ANSI escape codes, so this is `continue` rather than
278+ // `break`.
279+ continue ;
138280 }
139281
140- write ! (
141- f,
142- "{} " ,
143- self . instance. binary_id. style( self . styles. binary_id) ,
144- ) ?;
145- fmt_write_test_name ( self . instance . test_name , self . styles , f)
282+ for c in s. chars ( ) {
283+ let c_width = c. width ( ) . unwrap_or ( 0 ) ;
284+ if start <= pos && pos + c_width <= end {
285+ res. push ( c) ;
286+ }
287+ pos += c_width;
288+ if pos > end {
289+ // no need to iterate over the rest of s
290+ break ;
291+ }
292+ }
146293 }
294+
295+ res
147296}
148297
149298pub ( crate ) struct DisplayScriptInstance {
@@ -300,26 +449,35 @@ pub(crate) fn write_test_name(
300449 Ok ( ( ) )
301450}
302451
303- /// Write out a test name, `std::fmt::Write` version.
304- pub ( crate ) fn fmt_write_test_name (
305- name : & str ,
306- style : & Styles ,
307- writer : & mut dyn fmt:: Write ,
308- ) -> fmt:: Result {
309- // Look for the part of the test after the last ::, if any.
310- let mut splits = name. rsplitn ( 2 , "::" ) ;
311- let trailing = splits. next ( ) . expect ( "test should have at least 1 element" ) ;
312- if let Some ( rest) = splits. next ( ) {
313- write ! (
314- writer,
315- "{}{}" ,
316- rest. style( style. module_path) ,
317- "::" . style( style. module_path)
318- ) ?;
452+ /// Wrapper for displaying a test name with styling.
453+ pub ( crate ) struct DisplayTestName < ' a > {
454+ name : & ' a str ,
455+ styles : & ' a Styles ,
456+ }
457+
458+ impl < ' a > DisplayTestName < ' a > {
459+ pub ( crate ) fn new ( name : & ' a str , styles : & ' a Styles ) -> Self {
460+ Self { name, styles }
319461 }
320- write ! ( writer , "{}" , trailing . style ( style . test_name ) ) ? ;
462+ }
321463
322- Ok ( ( ) )
464+ impl fmt:: Display for DisplayTestName < ' _ > {
465+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
466+ // Look for the part of the test after the last ::, if any.
467+ let mut splits = self . name . rsplitn ( 2 , "::" ) ;
468+ let trailing = splits. next ( ) . expect ( "test should have at least 1 element" ) ;
469+ if let Some ( rest) = splits. next ( ) {
470+ write ! (
471+ f,
472+ "{}{}" ,
473+ rest. style( self . styles. module_path) ,
474+ "::" . style( self . styles. module_path)
475+ ) ?;
476+ }
477+ write ! ( f, "{}" , trailing. style( self . styles. test_name) ) ?;
478+
479+ Ok ( ( ) )
480+ }
323481}
324482
325483pub ( crate ) fn convert_build_platform (
0 commit comments