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