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