1use crate::{
7 config::scripts::ScriptId,
8 list::{OwnedTestInstanceId, Styles, TestInstanceId},
9 reporter::events::{AbortStatus, StressIndex},
10 run_mode::NextestRunMode,
11 write_str::WriteStr,
12};
13use camino::{Utf8Path, Utf8PathBuf};
14use console::AnsiCodeIterator;
15use nextest_metadata::TestCaseName;
16use owo_colors::{OwoColorize, Style};
17use quick_junit::ReportUuid;
18use std::{fmt, io, ops::ControlFlow, path::PathBuf, process::ExitStatus, time::Duration};
19use swrite::{SWrite, swrite};
20use tracing::warn;
21use unicode_width::UnicodeWidthChar;
22
23const FORCE_RUN_ID_ENV: &str = "__NEXTEST_FORCE_RUN_ID";
25
26pub fn force_or_new_run_id() -> ReportUuid {
28 if let Ok(id_str) = std::env::var(FORCE_RUN_ID_ENV) {
29 match id_str.parse::<ReportUuid>() {
30 Ok(uuid) => return uuid,
31 Err(err) => {
32 warn!(
33 "{FORCE_RUN_ID_ENV} is set but invalid (expected UUID): {err}, \
34 generating random ID"
35 );
36 }
37 }
38 }
39 ReportUuid::new_v4()
40}
41
42pub mod plural {
44 use crate::run_mode::NextestRunMode;
45
46 pub fn were_plural_if(plural: bool) -> &'static str {
48 if plural { "were" } else { "was" }
49 }
50
51 pub fn setup_scripts_str(count: usize) -> &'static str {
53 if count == 1 {
54 "setup script"
55 } else {
56 "setup scripts"
57 }
58 }
59
60 pub fn tests_str(mode: NextestRunMode, count: usize) -> &'static str {
65 tests_plural_if(mode, count != 1)
66 }
67
68 pub fn tests_plural_if(mode: NextestRunMode, plural: bool) -> &'static str {
73 match (mode, plural) {
74 (NextestRunMode::Test, true) => "tests",
75 (NextestRunMode::Test, false) => "test",
76 (NextestRunMode::Benchmark, true) => "benchmarks",
77 (NextestRunMode::Benchmark, false) => "benchmark",
78 }
79 }
80
81 pub fn tests_plural(mode: NextestRunMode) -> &'static str {
83 match mode {
84 NextestRunMode::Test => "tests",
85 NextestRunMode::Benchmark => "benchmarks",
86 }
87 }
88
89 pub fn binaries_str(count: usize) -> &'static str {
91 if count == 1 { "binary" } else { "binaries" }
92 }
93
94 pub fn paths_str(count: usize) -> &'static str {
96 if count == 1 { "path" } else { "paths" }
97 }
98
99 pub fn files_str(count: usize) -> &'static str {
101 if count == 1 { "file" } else { "files" }
102 }
103
104 pub fn directories_str(count: usize) -> &'static str {
106 if count == 1 {
107 "directory"
108 } else {
109 "directories"
110 }
111 }
112
113 pub fn this_crate_str(count: usize) -> &'static str {
115 if count == 1 {
116 "this crate"
117 } else {
118 "these crates"
119 }
120 }
121
122 pub fn libraries_str(count: usize) -> &'static str {
124 if count == 1 { "library" } else { "libraries" }
125 }
126
127 pub fn filters_str(count: usize) -> &'static str {
129 if count == 1 { "filter" } else { "filters" }
130 }
131
132 pub fn sections_str(count: usize) -> &'static str {
134 if count == 1 { "section" } else { "sections" }
135 }
136
137 pub fn iterations_str(count: u32) -> &'static str {
139 if count == 1 {
140 "iteration"
141 } else {
142 "iterations"
143 }
144 }
145
146 pub fn runs_str(count: usize) -> &'static str {
148 if count == 1 { "run" } else { "runs" }
149 }
150
151 pub fn orphans_str(count: usize) -> &'static str {
153 if count == 1 { "orphan" } else { "orphans" }
154 }
155
156 pub fn errors_str(count: usize) -> &'static str {
158 if count == 1 { "error" } else { "errors" }
159 }
160
161 pub fn exist_str(count: usize) -> &'static str {
163 if count == 1 { "exists" } else { "exist" }
164 }
165
166 pub fn end_str(count: usize) -> &'static str {
168 if count == 1 { "ends" } else { "end" }
169 }
170
171 pub fn remain_str(count: usize) -> &'static str {
173 if count == 1 { "remains" } else { "remain" }
174 }
175}
176
177pub struct DisplayTestInstance<'a> {
179 stress_index: Option<StressIndex>,
180 display_counter_index: Option<DisplayCounterIndex>,
181 instance: TestInstanceId<'a>,
182 styles: &'a Styles,
183 max_width: Option<usize>,
184}
185
186impl<'a> DisplayTestInstance<'a> {
187 pub fn new(
189 stress_index: Option<StressIndex>,
190 display_counter_index: Option<DisplayCounterIndex>,
191 instance: TestInstanceId<'a>,
192 styles: &'a Styles,
193 ) -> Self {
194 Self {
195 stress_index,
196 display_counter_index,
197 instance,
198 styles,
199 max_width: None,
200 }
201 }
202
203 pub(crate) fn with_max_width(mut self, max_width: usize) -> Self {
204 self.max_width = Some(max_width);
205 self
206 }
207}
208
209impl fmt::Display for DisplayTestInstance<'_> {
210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211 let stress_index_str = if let Some(stress_index) = self.stress_index {
213 format!(
214 "[{}] ",
215 DisplayStressIndex {
216 stress_index,
217 count_style: self.styles.count,
218 }
219 )
220 } else {
221 String::new()
222 };
223 let counter_index_str = if let Some(display_counter_index) = &self.display_counter_index {
224 format!("{display_counter_index} ")
225 } else {
226 String::new()
227 };
228 let binary_id_str = format!("{} ", self.instance.binary_id.style(self.styles.binary_id));
229 let test_name_str = DisplayTestName::new(self.instance.test_name, self.styles).to_string();
230
231 if let Some(max_width) = self.max_width {
233 let stress_index_width = text_width(&stress_index_str);
237 let counter_index_width = text_width(&counter_index_str);
238 let binary_id_width = text_width(&binary_id_str);
239 let test_name_width = text_width(&test_name_str);
240
241 let mut stress_index_resolved_width = stress_index_width;
248 let mut counter_index_resolved_width = counter_index_width;
249 let mut binary_id_resolved_width = binary_id_width;
250 let mut test_name_resolved_width = test_name_width;
251
252 if stress_index_resolved_width > max_width {
254 stress_index_resolved_width = max_width;
255 }
256
257 let remaining_width = max_width.saturating_sub(stress_index_resolved_width);
259 if counter_index_resolved_width > remaining_width {
260 counter_index_resolved_width = remaining_width;
261 }
262
263 let remaining_width = max_width
265 .saturating_sub(stress_index_resolved_width)
266 .saturating_sub(counter_index_resolved_width);
267 if binary_id_resolved_width > remaining_width {
268 binary_id_resolved_width = remaining_width;
269 }
270
271 let remaining_width = max_width
273 .saturating_sub(stress_index_resolved_width)
274 .saturating_sub(counter_index_resolved_width)
275 .saturating_sub(binary_id_resolved_width);
276 if test_name_resolved_width > remaining_width {
277 test_name_resolved_width = remaining_width;
278 }
279
280 let test_name_truncated_str = if test_name_resolved_width == test_name_width {
282 test_name_str
283 } else {
284 truncate_ansi_aware(
286 &test_name_str,
287 test_name_width.saturating_sub(test_name_resolved_width),
288 test_name_width,
289 )
290 };
291 let binary_id_truncated_str = if binary_id_resolved_width == binary_id_width {
292 binary_id_str
293 } else {
294 truncate_ansi_aware(&binary_id_str, 0, binary_id_resolved_width)
296 };
297 let counter_index_truncated_str = if counter_index_resolved_width == counter_index_width
298 {
299 counter_index_str
300 } else {
301 truncate_ansi_aware(&counter_index_str, 0, counter_index_resolved_width)
303 };
304 let stress_index_truncated_str = if stress_index_resolved_width == stress_index_width {
305 stress_index_str
306 } else {
307 truncate_ansi_aware(&stress_index_str, 0, stress_index_resolved_width)
309 };
310
311 write!(
312 f,
313 "{}{}{}{}",
314 stress_index_truncated_str,
315 counter_index_truncated_str,
316 binary_id_truncated_str,
317 test_name_truncated_str,
318 )
319 } else {
320 write!(
321 f,
322 "{}{}{}{}",
323 stress_index_str, counter_index_str, binary_id_str, test_name_str
324 )
325 }
326 }
327}
328
329fn text_width(text: &str) -> usize {
330 strip_ansi_escapes::strip_str(text)
338 .chars()
339 .map(|c| c.width().unwrap_or(0))
340 .sum()
341}
342
343fn truncate_ansi_aware(text: &str, start: usize, end: usize) -> String {
344 let mut pos = 0;
345 let mut res = String::new();
346 for (s, is_ansi) in AnsiCodeIterator::new(text) {
347 if is_ansi {
348 res.push_str(s);
349 continue;
350 } else if pos >= end {
351 continue;
354 }
355
356 for c in s.chars() {
357 let c_width = c.width().unwrap_or(0);
358 if start <= pos && pos + c_width <= end {
359 res.push(c);
360 }
361 pos += c_width;
362 if pos > end {
363 break;
365 }
366 }
367 }
368
369 res
370}
371
372pub(crate) struct DisplayScriptInstance {
373 stress_index: Option<StressIndex>,
374 script_id: ScriptId,
375 full_command: String,
376 script_id_style: Style,
377 count_style: Style,
378}
379
380impl DisplayScriptInstance {
381 pub(crate) fn new(
382 stress_index: Option<StressIndex>,
383 script_id: ScriptId,
384 command: &str,
385 args: &[String],
386 script_id_style: Style,
387 count_style: Style,
388 ) -> Self {
389 let full_command =
390 shell_words::join(std::iter::once(command).chain(args.iter().map(|arg| arg.as_ref())));
391
392 Self {
393 stress_index,
394 script_id,
395 full_command,
396 script_id_style,
397 count_style,
398 }
399 }
400}
401
402impl fmt::Display for DisplayScriptInstance {
403 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
404 if let Some(stress_index) = self.stress_index {
405 write!(
406 f,
407 "[{}] ",
408 DisplayStressIndex {
409 stress_index,
410 count_style: self.count_style,
411 }
412 )?;
413 }
414 write!(
415 f,
416 "{}: {}",
417 self.script_id.style(self.script_id_style),
418 self.full_command,
419 )
420 }
421}
422
423struct DisplayStressIndex {
424 stress_index: StressIndex,
425 count_style: Style,
426}
427
428impl fmt::Display for DisplayStressIndex {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 match self.stress_index.total {
431 Some(total) => {
432 write!(
433 f,
434 "{:>width$}/{}",
435 (self.stress_index.current + 1).style(self.count_style),
436 total.style(self.count_style),
437 width = decimal_char_width(total.get()),
438 )
439 }
440 None => {
441 write!(
442 f,
443 "{}",
444 (self.stress_index.current + 1).style(self.count_style)
445 )
446 }
447 }
448 }
449}
450
451pub enum DisplayCounterIndex {
453 Counter {
455 current: usize,
457 total: usize,
459 },
460 Padded {
462 character: char,
464 width: usize,
466 },
467}
468
469impl DisplayCounterIndex {
470 pub fn new_counter(current: usize, total: usize) -> Self {
472 Self::Counter { current, total }
473 }
474
475 pub fn new_padded(character: char, width: usize) -> Self {
477 Self::Padded { character, width }
478 }
479}
480
481impl fmt::Display for DisplayCounterIndex {
482 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483 match self {
484 Self::Counter { current, total } => {
485 write!(
486 f,
487 "({:>width$}/{})",
488 current,
489 total,
490 width = decimal_char_width(*total)
491 )
492 }
493 Self::Padded { character, width } => {
494 let s: String = std::iter::repeat_n(*character, 2 * *width + 1).collect();
499 write!(f, "({s})")
500 }
501 }
502 }
503}
504
505pub(crate) fn decimal_char_width<T>(n: T) -> usize
509where
510 T: TryInto<u128> + Copy,
511{
512 let n: u128 = n.try_into().ok().expect("converted to u128");
516 (n.checked_ilog10().unwrap_or(0) + 1) as usize
517}
518
519pub(crate) fn write_test_name(
521 name: &TestCaseName,
522 style: &Styles,
523 writer: &mut dyn WriteStr,
524) -> io::Result<()> {
525 let (module_path, trailing) = name.module_path_and_name();
526 if let Some(module_path) = module_path {
527 write!(
528 writer,
529 "{}{}",
530 module_path.style(style.module_path),
531 "::".style(style.module_path)
532 )?;
533 }
534 write!(writer, "{}", trailing.style(style.test_name))?;
535
536 Ok(())
537}
538
539pub(crate) struct DisplayTestName<'a> {
541 name: &'a TestCaseName,
542 styles: &'a Styles,
543}
544
545impl<'a> DisplayTestName<'a> {
546 pub(crate) fn new(name: &'a TestCaseName, styles: &'a Styles) -> Self {
547 Self { name, styles }
548 }
549}
550
551impl fmt::Display for DisplayTestName<'_> {
552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553 let (module_path, trailing) = self.name.module_path_and_name();
554 if let Some(module_path) = module_path {
555 write!(
556 f,
557 "{}{}",
558 module_path.style(self.styles.module_path),
559 "::".style(self.styles.module_path)
560 )?;
561 }
562 write!(f, "{}", trailing.style(self.styles.test_name))?;
563
564 Ok(())
565 }
566}
567
568pub(crate) fn convert_build_platform(
569 platform: nextest_metadata::BuildPlatform,
570) -> guppy::graph::cargo::BuildPlatform {
571 match platform {
572 nextest_metadata::BuildPlatform::Target => guppy::graph::cargo::BuildPlatform::Target,
573 nextest_metadata::BuildPlatform::Host => guppy::graph::cargo::BuildPlatform::Host,
574 }
575}
576
577pub(crate) fn dylib_path_envvar() -> &'static str {
584 if cfg!(windows) {
585 "PATH"
586 } else if cfg!(target_os = "macos") {
587 "DYLD_FALLBACK_LIBRARY_PATH"
603 } else {
604 "LD_LIBRARY_PATH"
605 }
606}
607
608pub(crate) fn dylib_path() -> Vec<PathBuf> {
613 match std::env::var_os(dylib_path_envvar()) {
614 Some(var) => std::env::split_paths(&var).collect(),
615 None => Vec::new(),
616 }
617}
618
619#[cfg(windows)]
621pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
622 if !rel_path.is_relative() {
623 panic!("path for conversion to forward slash '{rel_path}' is not relative");
624 }
625 rel_path.as_str().replace('\\', "/").into()
626}
627
628#[cfg(not(windows))]
629pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
630 rel_path.to_path_buf()
631}
632
633#[cfg(windows)]
635pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
636 if !rel_path.is_relative() {
637 panic!("path for conversion to backslash '{rel_path}' is not relative");
638 }
639 rel_path.as_str().replace('/', "\\").into()
640}
641
642#[cfg(not(windows))]
643pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
644 rel_path.to_path_buf()
645}
646
647pub(crate) fn rel_path_join(rel_path: &Utf8Path, path: &Utf8Path) -> Utf8PathBuf {
649 assert!(rel_path.is_relative(), "rel_path {rel_path} is relative");
650 assert!(path.is_relative(), "path {path} is relative",);
651 format!("{rel_path}/{path}").into()
652}
653
654#[derive(Debug)]
655pub(crate) struct FormattedDuration(pub(crate) Duration);
656
657#[derive(Copy, Clone, Debug)]
659pub(crate) enum DurationRounding {
660 Floor,
662
663 Ceiling,
667}
668
669#[derive(Debug)]
671pub(crate) struct FormattedHhMmSs {
672 pub(crate) duration: Duration,
673 pub(crate) rounding: DurationRounding,
674}
675
676impl fmt::Display for FormattedHhMmSs {
677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678 let total_secs = self.duration.as_secs();
679 let total_secs = match self.rounding {
680 DurationRounding::Ceiling if self.duration.subsec_millis() > 0 => total_secs + 1,
681 _ => total_secs,
682 };
683 let secs = total_secs % 60;
684 let total_mins = total_secs / 60;
685 let mins = total_mins % 60;
686 let hours = total_mins / 60;
687
688 write!(f, "{hours:02}:{mins:02}:{secs:02}")
689 }
690}
691
692impl fmt::Display for FormattedDuration {
693 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694 let duration = self.0.as_secs_f64();
695 if duration > 60.0 {
696 write!(f, "{}m {:.2}s", duration as u32 / 60, duration % 60.0)
697 } else {
698 write!(f, "{duration:.2}s")
699 }
700 }
701}
702
703#[derive(Debug)]
704pub(crate) struct FormattedRelativeDuration(pub(crate) Duration);
705
706impl fmt::Display for FormattedRelativeDuration {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 fn item(unit: &'static str, value: u64) -> ControlFlow<(&'static str, u64)> {
712 if value > 0 {
713 ControlFlow::Break((unit, value))
714 } else {
715 ControlFlow::Continue(())
716 }
717 }
718
719 fn fmt(f: Duration) -> ControlFlow<(&'static str, u64), ()> {
723 let secs = f.as_secs();
724 let nanos = f.subsec_nanos();
725
726 let years = secs / 31_557_600; let year_days = secs % 31_557_600;
728 let months = year_days / 2_630_016; let month_days = year_days % 2_630_016;
730 let days = month_days / 86400;
731 let day_secs = month_days % 86400;
732 let hours = day_secs / 3600;
733 let minutes = day_secs % 3600 / 60;
734 let seconds = day_secs % 60;
735
736 let millis = nanos / 1_000_000;
737 let micros = nanos / 1_000;
738
739 item("y", years)?;
744 item("mo", months)?;
745 item("d", days)?;
746 item("h", hours)?;
747 item("m", minutes)?;
748 item("s", seconds)?;
749 item("ms", u64::from(millis))?;
750 item("us", u64::from(micros))?;
751 item("ns", u64::from(nanos))?;
752 ControlFlow::Continue(())
753 }
754
755 match fmt(self.0) {
756 ControlFlow::Break((unit, value)) => write!(f, "{value}{unit}"),
757 ControlFlow::Continue(()) => write!(f, "0s"),
758 }
759 }
760}
761
762#[derive(Clone, Debug)]
767pub struct ThemeCharacters {
768 hbar: char,
769 progress_chars: &'static str,
770 use_unicode: bool,
771}
772
773impl Default for ThemeCharacters {
774 fn default() -> Self {
775 Self {
776 hbar: '-',
777 progress_chars: "=> ",
778 use_unicode: false,
779 }
780 }
781}
782
783impl ThemeCharacters {
784 pub fn detect(stream: supports_unicode::Stream) -> Self {
787 let mut this = Self::default();
788 if supports_unicode::on(stream) {
789 this.use_unicode();
790 }
791 this
792 }
793
794 pub fn use_unicode(&mut self) {
796 self.hbar = '─';
797 self.progress_chars = "█▉▊▋▌▍▎▏ ";
799 self.use_unicode = true;
800 }
801
802 pub fn hbar_char(&self) -> char {
804 self.hbar
805 }
806
807 pub fn hbar(&self, width: usize) -> String {
809 std::iter::repeat_n(self.hbar, width).collect()
810 }
811
812 pub fn progress_chars(&self) -> &'static str {
814 self.progress_chars
815 }
816
817 pub fn tree_branch(&self) -> &'static str {
819 if self.use_unicode { "├─" } else { "|-" }
820 }
821
822 pub fn tree_last(&self) -> &'static str {
824 if self.use_unicode { "└─" } else { "\\-" }
825 }
826
827 pub fn tree_continuation(&self) -> &'static str {
829 if self.use_unicode { "│ " } else { "| " }
830 }
831
832 pub fn tree_space(&self) -> &'static str {
834 " "
835 }
836}
837
838pub(crate) fn display_exited_with(exit_status: ExitStatus) -> String {
840 match AbortStatus::extract(exit_status) {
841 Some(abort_status) => display_abort_status(abort_status),
842 None => match exit_status.code() {
843 Some(code) => format!("exited with exit code {code}"),
844 None => "exited with an unknown error".to_owned(),
845 },
846 }
847}
848
849pub(crate) fn display_abort_status(abort_status: AbortStatus) -> String {
851 match abort_status {
852 #[cfg(unix)]
853 AbortStatus::UnixSignal(sig) => match crate::helpers::signal_str(sig) {
854 Some(s) => {
855 format!("aborted with signal {sig} (SIG{s})")
856 }
857 None => {
858 format!("aborted with signal {sig}")
859 }
860 },
861 #[cfg(windows)]
862 AbortStatus::WindowsNtStatus(nt_status) => {
863 format!(
864 "aborted with code {}",
865 crate::helpers::display_nt_status(nt_status, Style::new())
867 )
868 }
869 #[cfg(windows)]
870 AbortStatus::JobObject => "terminated via job object".to_string(),
871 }
872}
873
874#[cfg(unix)]
875pub(crate) fn signal_str(signal: i32) -> Option<&'static str> {
876 match signal {
883 1 => Some("HUP"),
884 2 => Some("INT"),
885 3 => Some("QUIT"),
886 4 => Some("ILL"),
887 5 => Some("TRAP"),
888 6 => Some("ABRT"),
889 8 => Some("FPE"),
890 9 => Some("KILL"),
891 11 => Some("SEGV"),
892 13 => Some("PIPE"),
893 14 => Some("ALRM"),
894 15 => Some("TERM"),
895 _ => None,
896 }
897}
898
899#[cfg(windows)]
900pub(crate) fn display_nt_status(
901 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
902 bold_style: Style,
903) -> String {
904 let bolded_status = format!("{:#010x}", nt_status.style(bold_style));
908
909 match windows_nt_status_message(nt_status) {
910 Some(message) => format!("{bolded_status}: {message}"),
911 None => bolded_status,
912 }
913}
914
915#[cfg(windows)]
917pub(crate) fn windows_nt_status_message(
918 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
919) -> Option<smol_str::SmolStr> {
920 let win32_code = unsafe { windows_sys::Win32::Foundation::RtlNtStatusToDosError(nt_status) };
922
923 if win32_code == windows_sys::Win32::Foundation::ERROR_MR_MID_NOT_FOUND {
924 return None;
926 }
927
928 Some(smol_str::SmolStr::new(
929 io::Error::from_raw_os_error(win32_code as i32).to_string(),
930 ))
931}
932
933#[derive(Copy, Clone, Debug)]
934pub(crate) struct QuotedDisplay<'a, T: ?Sized>(pub(crate) &'a T);
935
936impl<T: ?Sized> fmt::Display for QuotedDisplay<'_, T>
937where
938 T: fmt::Display,
939{
940 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
941 write!(f, "'{}'", self.0)
942 }
943}
944
945unsafe extern "C" {
947 fn __nextest_external_symbol_that_does_not_exist();
948}
949
950pub fn format_interceptor_too_many_tests(
952 cli_opt_name: &str,
953 mode: NextestRunMode,
954 test_count: usize,
955 test_instances: &[OwnedTestInstanceId],
956 list_styles: &Styles,
957 count_style: Style,
958) -> String {
959 let mut msg = format!(
960 "--{} requires exactly one {}, but {} {} were selected:",
961 cli_opt_name,
962 plural::tests_plural_if(mode, false),
963 test_count.style(count_style),
964 plural::tests_str(mode, test_count)
965 );
966
967 for test_instance in test_instances {
968 let display = DisplayTestInstance::new(None, None, test_instance.as_ref(), list_styles);
969 swrite!(msg, "\n {}", display);
970 }
971
972 if test_count > test_instances.len() {
973 let remaining = test_count - test_instances.len();
974 swrite!(
975 msg,
976 "\n ... and {} more {}",
977 remaining.style(count_style),
978 plural::tests_str(mode, remaining)
979 );
980 }
981
982 msg
983}
984
985#[inline]
986#[expect(dead_code)]
987pub(crate) fn statically_unreachable() -> ! {
988 unsafe {
989 __nextest_external_symbol_that_does_not_exist();
990 }
991 unreachable!("linker symbol above cannot be resolved")
992}
993
994#[cfg(test)]
995mod test {
996 use super::*;
997
998 #[test]
999 fn test_decimal_char_width() {
1000 assert_eq!(1, decimal_char_width(0_usize));
1002 assert_eq!(1, decimal_char_width(1_usize));
1003 assert_eq!(1, decimal_char_width(5_usize));
1004 assert_eq!(1, decimal_char_width(9_usize));
1005 assert_eq!(2, decimal_char_width(10_usize));
1006 assert_eq!(2, decimal_char_width(11_usize));
1007 assert_eq!(2, decimal_char_width(99_usize));
1008 assert_eq!(3, decimal_char_width(100_usize));
1009 assert_eq!(3, decimal_char_width(999_usize));
1010
1011 assert_eq!(1, decimal_char_width(0_u32));
1013 assert_eq!(3, decimal_char_width(100_u32));
1014
1015 assert_eq!(1, decimal_char_width(0_u64));
1017 assert_eq!(1, decimal_char_width(1_u64));
1018 assert_eq!(1, decimal_char_width(9_u64));
1019 assert_eq!(2, decimal_char_width(10_u64));
1020 assert_eq!(2, decimal_char_width(99_u64));
1021 assert_eq!(3, decimal_char_width(100_u64));
1022 assert_eq!(3, decimal_char_width(999_u64));
1023 assert_eq!(6, decimal_char_width(999_999_u64));
1024 assert_eq!(7, decimal_char_width(1_000_000_u64));
1025 assert_eq!(8, decimal_char_width(10_000_000_u64));
1026 assert_eq!(8, decimal_char_width(11_000_000_u64));
1027 }
1028}