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 std::{fmt, io, ops::ControlFlow, path::PathBuf, process::ExitStatus, time::Duration};
18use swrite::{SWrite, swrite};
19use unicode_width::UnicodeWidthChar;
20
21pub mod plural {
23 use crate::run_mode::NextestRunMode;
24
25 pub fn were_plural_if(plural: bool) -> &'static str {
27 if plural { "were" } else { "was" }
28 }
29
30 pub fn setup_scripts_str(count: usize) -> &'static str {
32 if count == 1 {
33 "setup script"
34 } else {
35 "setup scripts"
36 }
37 }
38
39 pub fn tests_str(mode: NextestRunMode, count: usize) -> &'static str {
44 tests_plural_if(mode, count != 1)
45 }
46
47 pub fn tests_plural_if(mode: NextestRunMode, plural: bool) -> &'static str {
52 match (mode, plural) {
53 (NextestRunMode::Test, true) => "tests",
54 (NextestRunMode::Test, false) => "test",
55 (NextestRunMode::Benchmark, true) => "benchmarks",
56 (NextestRunMode::Benchmark, false) => "benchmark",
57 }
58 }
59
60 pub fn tests_plural(mode: NextestRunMode) -> &'static str {
62 match mode {
63 NextestRunMode::Test => "tests",
64 NextestRunMode::Benchmark => "benchmarks",
65 }
66 }
67
68 pub fn binaries_str(count: usize) -> &'static str {
70 if count == 1 { "binary" } else { "binaries" }
71 }
72
73 pub fn paths_str(count: usize) -> &'static str {
75 if count == 1 { "path" } else { "paths" }
76 }
77
78 pub fn files_str(count: usize) -> &'static str {
80 if count == 1 { "file" } else { "files" }
81 }
82
83 pub fn directories_str(count: usize) -> &'static str {
85 if count == 1 {
86 "directory"
87 } else {
88 "directories"
89 }
90 }
91
92 pub fn this_crate_str(count: usize) -> &'static str {
94 if count == 1 {
95 "this crate"
96 } else {
97 "these crates"
98 }
99 }
100
101 pub fn libraries_str(count: usize) -> &'static str {
103 if count == 1 { "library" } else { "libraries" }
104 }
105
106 pub fn filters_str(count: usize) -> &'static str {
108 if count == 1 { "filter" } else { "filters" }
109 }
110
111 pub fn sections_str(count: usize) -> &'static str {
113 if count == 1 { "section" } else { "sections" }
114 }
115
116 pub fn iterations_str(count: u32) -> &'static str {
118 if count == 1 {
119 "iteration"
120 } else {
121 "iterations"
122 }
123 }
124
125 pub fn runs_str(count: usize) -> &'static str {
127 if count == 1 { "run" } else { "runs" }
128 }
129
130 pub fn orphans_str(count: usize) -> &'static str {
132 if count == 1 { "orphan" } else { "orphans" }
133 }
134
135 pub fn errors_str(count: usize) -> &'static str {
137 if count == 1 { "error" } else { "errors" }
138 }
139
140 pub fn exist_str(count: usize) -> &'static str {
142 if count == 1 { "exists" } else { "exist" }
143 }
144
145 pub fn end_str(count: usize) -> &'static str {
147 if count == 1 { "ends" } else { "end" }
148 }
149
150 pub fn remain_str(count: usize) -> &'static str {
152 if count == 1 { "remains" } else { "remain" }
153 }
154}
155
156pub struct DisplayTestInstance<'a> {
158 stress_index: Option<StressIndex>,
159 display_counter_index: Option<DisplayCounterIndex>,
160 instance: TestInstanceId<'a>,
161 styles: &'a Styles,
162 max_width: Option<usize>,
163}
164
165impl<'a> DisplayTestInstance<'a> {
166 pub fn new(
168 stress_index: Option<StressIndex>,
169 display_counter_index: Option<DisplayCounterIndex>,
170 instance: TestInstanceId<'a>,
171 styles: &'a Styles,
172 ) -> Self {
173 Self {
174 stress_index,
175 display_counter_index,
176 instance,
177 styles,
178 max_width: None,
179 }
180 }
181
182 pub(crate) fn with_max_width(mut self, max_width: usize) -> Self {
183 self.max_width = Some(max_width);
184 self
185 }
186}
187
188impl fmt::Display for DisplayTestInstance<'_> {
189 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190 let stress_index_str = if let Some(stress_index) = self.stress_index {
192 format!(
193 "[{}] ",
194 DisplayStressIndex {
195 stress_index,
196 count_style: self.styles.count,
197 }
198 )
199 } else {
200 String::new()
201 };
202 let counter_index_str = if let Some(display_counter_index) = &self.display_counter_index {
203 format!("{display_counter_index} ")
204 } else {
205 String::new()
206 };
207 let binary_id_str = format!("{} ", self.instance.binary_id.style(self.styles.binary_id));
208 let test_name_str = DisplayTestName::new(self.instance.test_name, self.styles).to_string();
209
210 if let Some(max_width) = self.max_width {
212 let stress_index_width = text_width(&stress_index_str);
216 let counter_index_width = text_width(&counter_index_str);
217 let binary_id_width = text_width(&binary_id_str);
218 let test_name_width = text_width(&test_name_str);
219
220 let mut stress_index_resolved_width = stress_index_width;
227 let mut counter_index_resolved_width = counter_index_width;
228 let mut binary_id_resolved_width = binary_id_width;
229 let mut test_name_resolved_width = test_name_width;
230
231 if stress_index_resolved_width > max_width {
233 stress_index_resolved_width = max_width;
234 }
235
236 let remaining_width = max_width.saturating_sub(stress_index_resolved_width);
238 if counter_index_resolved_width > remaining_width {
239 counter_index_resolved_width = remaining_width;
240 }
241
242 let remaining_width = max_width
244 .saturating_sub(stress_index_resolved_width)
245 .saturating_sub(counter_index_resolved_width);
246 if binary_id_resolved_width > remaining_width {
247 binary_id_resolved_width = remaining_width;
248 }
249
250 let remaining_width = max_width
252 .saturating_sub(stress_index_resolved_width)
253 .saturating_sub(counter_index_resolved_width)
254 .saturating_sub(binary_id_resolved_width);
255 if test_name_resolved_width > remaining_width {
256 test_name_resolved_width = remaining_width;
257 }
258
259 let test_name_truncated_str = if test_name_resolved_width == test_name_width {
261 test_name_str
262 } else {
263 truncate_ansi_aware(
265 &test_name_str,
266 test_name_width.saturating_sub(test_name_resolved_width),
267 test_name_width,
268 )
269 };
270 let binary_id_truncated_str = if binary_id_resolved_width == binary_id_width {
271 binary_id_str
272 } else {
273 truncate_ansi_aware(&binary_id_str, 0, binary_id_resolved_width)
275 };
276 let counter_index_truncated_str = if counter_index_resolved_width == counter_index_width
277 {
278 counter_index_str
279 } else {
280 truncate_ansi_aware(&counter_index_str, 0, counter_index_resolved_width)
282 };
283 let stress_index_truncated_str = if stress_index_resolved_width == stress_index_width {
284 stress_index_str
285 } else {
286 truncate_ansi_aware(&stress_index_str, 0, stress_index_resolved_width)
288 };
289
290 write!(
291 f,
292 "{}{}{}{}",
293 stress_index_truncated_str,
294 counter_index_truncated_str,
295 binary_id_truncated_str,
296 test_name_truncated_str,
297 )
298 } else {
299 write!(
300 f,
301 "{}{}{}{}",
302 stress_index_str, counter_index_str, binary_id_str, test_name_str
303 )
304 }
305 }
306}
307
308fn text_width(text: &str) -> usize {
309 strip_ansi_escapes::strip_str(text)
317 .chars()
318 .map(|c| c.width().unwrap_or(0))
319 .sum()
320}
321
322fn truncate_ansi_aware(text: &str, start: usize, end: usize) -> String {
323 let mut pos = 0;
324 let mut res = String::new();
325 for (s, is_ansi) in AnsiCodeIterator::new(text) {
326 if is_ansi {
327 res.push_str(s);
328 continue;
329 } else if pos >= end {
330 continue;
333 }
334
335 for c in s.chars() {
336 let c_width = c.width().unwrap_or(0);
337 if start <= pos && pos + c_width <= end {
338 res.push(c);
339 }
340 pos += c_width;
341 if pos > end {
342 break;
344 }
345 }
346 }
347
348 res
349}
350
351pub(crate) struct DisplayScriptInstance {
352 stress_index: Option<StressIndex>,
353 script_id: ScriptId,
354 full_command: String,
355 script_id_style: Style,
356 count_style: Style,
357}
358
359impl DisplayScriptInstance {
360 pub(crate) fn new(
361 stress_index: Option<StressIndex>,
362 script_id: ScriptId,
363 command: &str,
364 args: &[String],
365 script_id_style: Style,
366 count_style: Style,
367 ) -> Self {
368 let full_command =
369 shell_words::join(std::iter::once(command).chain(args.iter().map(|arg| arg.as_ref())));
370
371 Self {
372 stress_index,
373 script_id,
374 full_command,
375 script_id_style,
376 count_style,
377 }
378 }
379}
380
381impl fmt::Display for DisplayScriptInstance {
382 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
383 if let Some(stress_index) = self.stress_index {
384 write!(
385 f,
386 "[{}] ",
387 DisplayStressIndex {
388 stress_index,
389 count_style: self.count_style,
390 }
391 )?;
392 }
393 write!(
394 f,
395 "{}: {}",
396 self.script_id.style(self.script_id_style),
397 self.full_command,
398 )
399 }
400}
401
402struct DisplayStressIndex {
403 stress_index: StressIndex,
404 count_style: Style,
405}
406
407impl fmt::Display for DisplayStressIndex {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 match self.stress_index.total {
410 Some(total) => {
411 write!(
412 f,
413 "{:>width$}/{}",
414 (self.stress_index.current + 1).style(self.count_style),
415 total.style(self.count_style),
416 width = decimal_char_width(total.get()),
417 )
418 }
419 None => {
420 write!(
421 f,
422 "{}",
423 (self.stress_index.current + 1).style(self.count_style)
424 )
425 }
426 }
427 }
428}
429
430pub enum DisplayCounterIndex {
432 Counter {
434 current: usize,
436 total: usize,
438 },
439 Padded {
441 character: char,
443 width: usize,
445 },
446}
447
448impl DisplayCounterIndex {
449 pub fn new_counter(current: usize, total: usize) -> Self {
451 Self::Counter { current, total }
452 }
453
454 pub fn new_padded(character: char, width: usize) -> Self {
456 Self::Padded { character, width }
457 }
458}
459
460impl fmt::Display for DisplayCounterIndex {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 match self {
463 Self::Counter { current, total } => {
464 write!(
465 f,
466 "({:>width$}/{})",
467 current,
468 total,
469 width = decimal_char_width(*total)
470 )
471 }
472 Self::Padded { character, width } => {
473 let s: String = std::iter::repeat_n(*character, 2 * *width + 1).collect();
478 write!(f, "({s})")
479 }
480 }
481 }
482}
483
484pub(crate) fn decimal_char_width<T>(n: T) -> usize
488where
489 T: TryInto<u128> + Copy,
490{
491 let n: u128 = n.try_into().ok().expect("converted to u128");
495 (n.checked_ilog10().unwrap_or(0) + 1) as usize
496}
497
498pub(crate) fn write_test_name(
500 name: &TestCaseName,
501 style: &Styles,
502 writer: &mut dyn WriteStr,
503) -> io::Result<()> {
504 let (module_path, trailing) = name.module_path_and_name();
505 if let Some(module_path) = module_path {
506 write!(
507 writer,
508 "{}{}",
509 module_path.style(style.module_path),
510 "::".style(style.module_path)
511 )?;
512 }
513 write!(writer, "{}", trailing.style(style.test_name))?;
514
515 Ok(())
516}
517
518pub(crate) struct DisplayTestName<'a> {
520 name: &'a TestCaseName,
521 styles: &'a Styles,
522}
523
524impl<'a> DisplayTestName<'a> {
525 pub(crate) fn new(name: &'a TestCaseName, styles: &'a Styles) -> Self {
526 Self { name, styles }
527 }
528}
529
530impl fmt::Display for DisplayTestName<'_> {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 let (module_path, trailing) = self.name.module_path_and_name();
533 if let Some(module_path) = module_path {
534 write!(
535 f,
536 "{}{}",
537 module_path.style(self.styles.module_path),
538 "::".style(self.styles.module_path)
539 )?;
540 }
541 write!(f, "{}", trailing.style(self.styles.test_name))?;
542
543 Ok(())
544 }
545}
546
547pub(crate) fn convert_build_platform(
548 platform: nextest_metadata::BuildPlatform,
549) -> guppy::graph::cargo::BuildPlatform {
550 match platform {
551 nextest_metadata::BuildPlatform::Target => guppy::graph::cargo::BuildPlatform::Target,
552 nextest_metadata::BuildPlatform::Host => guppy::graph::cargo::BuildPlatform::Host,
553 }
554}
555
556pub(crate) fn dylib_path_envvar() -> &'static str {
563 if cfg!(windows) {
564 "PATH"
565 } else if cfg!(target_os = "macos") {
566 "DYLD_FALLBACK_LIBRARY_PATH"
582 } else {
583 "LD_LIBRARY_PATH"
584 }
585}
586
587pub(crate) fn dylib_path() -> Vec<PathBuf> {
592 match std::env::var_os(dylib_path_envvar()) {
593 Some(var) => std::env::split_paths(&var).collect(),
594 None => Vec::new(),
595 }
596}
597
598#[cfg(windows)]
600pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
601 if !rel_path.is_relative() {
602 panic!("path for conversion to forward slash '{rel_path}' is not relative");
603 }
604 rel_path.as_str().replace('\\', "/").into()
605}
606
607#[cfg(not(windows))]
608pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
609 rel_path.to_path_buf()
610}
611
612#[cfg(windows)]
614pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
615 if !rel_path.is_relative() {
616 panic!("path for conversion to backslash '{rel_path}' is not relative");
617 }
618 rel_path.as_str().replace('/', "\\").into()
619}
620
621#[cfg(not(windows))]
622pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
623 rel_path.to_path_buf()
624}
625
626pub(crate) fn rel_path_join(rel_path: &Utf8Path, path: &Utf8Path) -> Utf8PathBuf {
628 assert!(rel_path.is_relative(), "rel_path {rel_path} is relative");
629 assert!(path.is_relative(), "path {path} is relative",);
630 format!("{rel_path}/{path}").into()
631}
632
633#[derive(Debug)]
634pub(crate) struct FormattedDuration(pub(crate) Duration);
635
636#[derive(Copy, Clone, Debug)]
638pub(crate) enum DurationRounding {
639 Floor,
641
642 Ceiling,
646}
647
648#[derive(Debug)]
650pub(crate) struct FormattedHhMmSs {
651 pub(crate) duration: Duration,
652 pub(crate) rounding: DurationRounding,
653}
654
655impl fmt::Display for FormattedHhMmSs {
656 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
657 let total_secs = self.duration.as_secs();
658 let total_secs = match self.rounding {
659 DurationRounding::Ceiling if self.duration.subsec_millis() > 0 => total_secs + 1,
660 _ => total_secs,
661 };
662 let secs = total_secs % 60;
663 let total_mins = total_secs / 60;
664 let mins = total_mins % 60;
665 let hours = total_mins / 60;
666
667 write!(f, "{hours:02}:{mins:02}:{secs:02}")
668 }
669}
670
671impl fmt::Display for FormattedDuration {
672 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
673 let duration = self.0.as_secs_f64();
674 if duration > 60.0 {
675 write!(f, "{}m {:.2}s", duration as u32 / 60, duration % 60.0)
676 } else {
677 write!(f, "{duration:.2}s")
678 }
679 }
680}
681
682#[derive(Debug)]
683pub(crate) struct FormattedRelativeDuration(pub(crate) Duration);
684
685impl fmt::Display for FormattedRelativeDuration {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687 fn item(unit: &'static str, value: u64) -> ControlFlow<(&'static str, u64)> {
691 if value > 0 {
692 ControlFlow::Break((unit, value))
693 } else {
694 ControlFlow::Continue(())
695 }
696 }
697
698 fn fmt(f: Duration) -> ControlFlow<(&'static str, u64), ()> {
702 let secs = f.as_secs();
703 let nanos = f.subsec_nanos();
704
705 let years = secs / 31_557_600; let year_days = secs % 31_557_600;
707 let months = year_days / 2_630_016; let month_days = year_days % 2_630_016;
709 let days = month_days / 86400;
710 let day_secs = month_days % 86400;
711 let hours = day_secs / 3600;
712 let minutes = day_secs % 3600 / 60;
713 let seconds = day_secs % 60;
714
715 let millis = nanos / 1_000_000;
716 let micros = nanos / 1_000;
717
718 item("y", years)?;
723 item("mo", months)?;
724 item("d", days)?;
725 item("h", hours)?;
726 item("m", minutes)?;
727 item("s", seconds)?;
728 item("ms", u64::from(millis))?;
729 item("us", u64::from(micros))?;
730 item("ns", u64::from(nanos))?;
731 ControlFlow::Continue(())
732 }
733
734 match fmt(self.0) {
735 ControlFlow::Break((unit, value)) => write!(f, "{value}{unit}"),
736 ControlFlow::Continue(()) => write!(f, "0s"),
737 }
738 }
739}
740
741#[derive(Clone, Debug)]
746pub struct ThemeCharacters {
747 hbar: char,
748 progress_chars: &'static str,
749 use_unicode: bool,
750}
751
752impl Default for ThemeCharacters {
753 fn default() -> Self {
754 Self {
755 hbar: '-',
756 progress_chars: "=> ",
757 use_unicode: false,
758 }
759 }
760}
761
762impl ThemeCharacters {
763 pub fn detect(stream: supports_unicode::Stream) -> Self {
766 let mut this = Self::default();
767 if supports_unicode::on(stream) {
768 this.use_unicode();
769 }
770 this
771 }
772
773 pub fn use_unicode(&mut self) {
775 self.hbar = '─';
776 self.progress_chars = "█▉▊▋▌▍▎▏ ";
778 self.use_unicode = true;
779 }
780
781 pub fn hbar_char(&self) -> char {
783 self.hbar
784 }
785
786 pub fn hbar(&self, width: usize) -> String {
788 std::iter::repeat_n(self.hbar, width).collect()
789 }
790
791 pub fn progress_chars(&self) -> &'static str {
793 self.progress_chars
794 }
795
796 pub fn tree_branch(&self) -> &'static str {
798 if self.use_unicode { "├─" } else { "|-" }
799 }
800
801 pub fn tree_last(&self) -> &'static str {
803 if self.use_unicode { "└─" } else { "\\-" }
804 }
805
806 pub fn tree_continuation(&self) -> &'static str {
808 if self.use_unicode { "│ " } else { "| " }
809 }
810
811 pub fn tree_space(&self) -> &'static str {
813 " "
814 }
815}
816
817pub(crate) fn display_exited_with(exit_status: ExitStatus) -> String {
819 match AbortStatus::extract(exit_status) {
820 Some(abort_status) => display_abort_status(abort_status),
821 None => match exit_status.code() {
822 Some(code) => format!("exited with exit code {code}"),
823 None => "exited with an unknown error".to_owned(),
824 },
825 }
826}
827
828pub(crate) fn display_abort_status(abort_status: AbortStatus) -> String {
830 match abort_status {
831 #[cfg(unix)]
832 AbortStatus::UnixSignal(sig) => match crate::helpers::signal_str(sig) {
833 Some(s) => {
834 format!("aborted with signal {sig} (SIG{s})")
835 }
836 None => {
837 format!("aborted with signal {sig}")
838 }
839 },
840 #[cfg(windows)]
841 AbortStatus::WindowsNtStatus(nt_status) => {
842 format!(
843 "aborted with code {}",
844 crate::helpers::display_nt_status(nt_status, Style::new())
846 )
847 }
848 #[cfg(windows)]
849 AbortStatus::JobObject => "terminated via job object".to_string(),
850 }
851}
852
853#[cfg(unix)]
854pub(crate) fn signal_str(signal: i32) -> Option<&'static str> {
855 match signal {
862 1 => Some("HUP"),
863 2 => Some("INT"),
864 3 => Some("QUIT"),
865 4 => Some("ILL"),
866 5 => Some("TRAP"),
867 6 => Some("ABRT"),
868 8 => Some("FPE"),
869 9 => Some("KILL"),
870 11 => Some("SEGV"),
871 13 => Some("PIPE"),
872 14 => Some("ALRM"),
873 15 => Some("TERM"),
874 _ => None,
875 }
876}
877
878#[cfg(windows)]
879pub(crate) fn display_nt_status(
880 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
881 bold_style: Style,
882) -> String {
883 let bolded_status = format!("{:#010x}", nt_status.style(bold_style));
887
888 match windows_nt_status_message(nt_status) {
889 Some(message) => format!("{bolded_status}: {message}"),
890 None => bolded_status,
891 }
892}
893
894#[cfg(windows)]
896pub(crate) fn windows_nt_status_message(
897 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
898) -> Option<smol_str::SmolStr> {
899 let win32_code = unsafe { windows_sys::Win32::Foundation::RtlNtStatusToDosError(nt_status) };
901
902 if win32_code == windows_sys::Win32::Foundation::ERROR_MR_MID_NOT_FOUND {
903 return None;
905 }
906
907 Some(smol_str::SmolStr::new(
908 io::Error::from_raw_os_error(win32_code as i32).to_string(),
909 ))
910}
911
912#[derive(Copy, Clone, Debug)]
913pub(crate) struct QuotedDisplay<'a, T: ?Sized>(pub(crate) &'a T);
914
915impl<T: ?Sized> fmt::Display for QuotedDisplay<'_, T>
916where
917 T: fmt::Display,
918{
919 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
920 write!(f, "'{}'", self.0)
921 }
922}
923
924unsafe extern "C" {
926 fn __nextest_external_symbol_that_does_not_exist();
927}
928
929pub fn format_interceptor_too_many_tests(
931 cli_opt_name: &str,
932 mode: NextestRunMode,
933 test_count: usize,
934 test_instances: &[OwnedTestInstanceId],
935 list_styles: &Styles,
936 count_style: Style,
937) -> String {
938 let mut msg = format!(
939 "--{} requires exactly one {}, but {} {} were selected:",
940 cli_opt_name,
941 plural::tests_plural_if(mode, false),
942 test_count.style(count_style),
943 plural::tests_str(mode, test_count)
944 );
945
946 for test_instance in test_instances {
947 let display = DisplayTestInstance::new(None, None, test_instance.as_ref(), list_styles);
948 swrite!(msg, "\n {}", display);
949 }
950
951 if test_count > test_instances.len() {
952 let remaining = test_count - test_instances.len();
953 swrite!(
954 msg,
955 "\n ... and {} more {}",
956 remaining.style(count_style),
957 plural::tests_str(mode, remaining)
958 );
959 }
960
961 msg
962}
963
964#[inline]
965#[expect(dead_code)]
966pub(crate) fn statically_unreachable() -> ! {
967 unsafe {
968 __nextest_external_symbol_that_does_not_exist();
969 }
970 unreachable!("linker symbol above cannot be resolved")
971}
972
973#[cfg(test)]
974mod test {
975 use super::*;
976
977 #[test]
978 fn test_decimal_char_width() {
979 assert_eq!(1, decimal_char_width(0_usize));
981 assert_eq!(1, decimal_char_width(1_usize));
982 assert_eq!(1, decimal_char_width(5_usize));
983 assert_eq!(1, decimal_char_width(9_usize));
984 assert_eq!(2, decimal_char_width(10_usize));
985 assert_eq!(2, decimal_char_width(11_usize));
986 assert_eq!(2, decimal_char_width(99_usize));
987 assert_eq!(3, decimal_char_width(100_usize));
988 assert_eq!(3, decimal_char_width(999_usize));
989
990 assert_eq!(1, decimal_char_width(0_u32));
992 assert_eq!(3, decimal_char_width(100_u32));
993
994 assert_eq!(1, decimal_char_width(0_u64));
996 assert_eq!(1, decimal_char_width(1_u64));
997 assert_eq!(1, decimal_char_width(9_u64));
998 assert_eq!(2, decimal_char_width(10_u64));
999 assert_eq!(2, decimal_char_width(99_u64));
1000 assert_eq!(3, decimal_char_width(100_u64));
1001 assert_eq!(3, decimal_char_width(999_u64));
1002 assert_eq!(6, decimal_char_width(999_999_u64));
1003 assert_eq!(7, decimal_char_width(1_000_000_u64));
1004 assert_eq!(8, decimal_char_width(10_000_000_u64));
1005 assert_eq!(8, decimal_char_width(11_000_000_u64));
1006 }
1007}