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 = format!(
209 "{}",
210 DisplayTestName::new(self.instance.test_name, self.styles)
211 );
212
213 if let Some(max_width) = self.max_width {
215 let stress_index_width = text_width(&stress_index_str);
219 let counter_index_width = text_width(&counter_index_str);
220 let binary_id_width = text_width(&binary_id_str);
221 let test_name_width = text_width(&test_name_str);
222
223 let mut stress_index_resolved_width = stress_index_width;
230 let mut counter_index_resolved_width = counter_index_width;
231 let mut binary_id_resolved_width = binary_id_width;
232 let mut test_name_resolved_width = test_name_width;
233
234 if stress_index_resolved_width > max_width {
236 stress_index_resolved_width = max_width;
237 }
238
239 let remaining_width = max_width.saturating_sub(stress_index_resolved_width);
241 if counter_index_resolved_width > remaining_width {
242 counter_index_resolved_width = remaining_width;
243 }
244
245 let remaining_width = max_width
247 .saturating_sub(stress_index_resolved_width)
248 .saturating_sub(counter_index_resolved_width);
249 if binary_id_resolved_width > remaining_width {
250 binary_id_resolved_width = remaining_width;
251 }
252
253 let remaining_width = max_width
255 .saturating_sub(stress_index_resolved_width)
256 .saturating_sub(counter_index_resolved_width)
257 .saturating_sub(binary_id_resolved_width);
258 if test_name_resolved_width > remaining_width {
259 test_name_resolved_width = remaining_width;
260 }
261
262 let test_name_truncated_str = if test_name_resolved_width == test_name_width {
264 test_name_str
265 } else {
266 truncate_ansi_aware(
268 &test_name_str,
269 test_name_width.saturating_sub(test_name_resolved_width),
270 test_name_width,
271 )
272 };
273 let binary_id_truncated_str = if binary_id_resolved_width == binary_id_width {
274 binary_id_str
275 } else {
276 truncate_ansi_aware(&binary_id_str, 0, binary_id_resolved_width)
278 };
279 let counter_index_truncated_str = if counter_index_resolved_width == counter_index_width
280 {
281 counter_index_str
282 } else {
283 truncate_ansi_aware(&counter_index_str, 0, counter_index_resolved_width)
285 };
286 let stress_index_truncated_str = if stress_index_resolved_width == stress_index_width {
287 stress_index_str
288 } else {
289 truncate_ansi_aware(&stress_index_str, 0, stress_index_resolved_width)
291 };
292
293 write!(
294 f,
295 "{}{}{}{}",
296 stress_index_truncated_str,
297 counter_index_truncated_str,
298 binary_id_truncated_str,
299 test_name_truncated_str,
300 )
301 } else {
302 write!(
303 f,
304 "{}{}{}{}",
305 stress_index_str, counter_index_str, binary_id_str, test_name_str
306 )
307 }
308 }
309}
310
311fn text_width(text: &str) -> usize {
312 strip_ansi_escapes::strip_str(text)
320 .chars()
321 .map(|c| c.width().unwrap_or(0))
322 .sum()
323}
324
325fn truncate_ansi_aware(text: &str, start: usize, end: usize) -> String {
326 let mut pos = 0;
327 let mut res = String::new();
328 for (s, is_ansi) in AnsiCodeIterator::new(text) {
329 if is_ansi {
330 res.push_str(s);
331 continue;
332 } else if pos >= end {
333 continue;
336 }
337
338 for c in s.chars() {
339 let c_width = c.width().unwrap_or(0);
340 if start <= pos && pos + c_width <= end {
341 res.push(c);
342 }
343 pos += c_width;
344 if pos > end {
345 break;
347 }
348 }
349 }
350
351 res
352}
353
354pub(crate) struct DisplayScriptInstance {
355 stress_index: Option<StressIndex>,
356 script_id: ScriptId,
357 full_command: String,
358 script_id_style: Style,
359 count_style: Style,
360}
361
362impl DisplayScriptInstance {
363 pub(crate) fn new(
364 stress_index: Option<StressIndex>,
365 script_id: ScriptId,
366 command: &str,
367 args: &[String],
368 script_id_style: Style,
369 count_style: Style,
370 ) -> Self {
371 let full_command =
372 shell_words::join(std::iter::once(command).chain(args.iter().map(|arg| arg.as_ref())));
373
374 Self {
375 stress_index,
376 script_id,
377 full_command,
378 script_id_style,
379 count_style,
380 }
381 }
382}
383
384impl fmt::Display for DisplayScriptInstance {
385 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386 if let Some(stress_index) = self.stress_index {
387 write!(
388 f,
389 "[{}] ",
390 DisplayStressIndex {
391 stress_index,
392 count_style: self.count_style,
393 }
394 )?;
395 }
396 write!(
397 f,
398 "{}: {}",
399 self.script_id.style(self.script_id_style),
400 self.full_command,
401 )
402 }
403}
404
405struct DisplayStressIndex {
406 stress_index: StressIndex,
407 count_style: Style,
408}
409
410impl fmt::Display for DisplayStressIndex {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 match self.stress_index.total {
413 Some(total) => {
414 write!(
415 f,
416 "{:>width$}/{}",
417 (self.stress_index.current + 1).style(self.count_style),
418 total.style(self.count_style),
419 width = u32_decimal_char_width(total.get()),
420 )
421 }
422 None => {
423 write!(
424 f,
425 "{}",
426 (self.stress_index.current + 1).style(self.count_style)
427 )
428 }
429 }
430 }
431}
432
433pub enum DisplayCounterIndex {
435 Counter {
437 current: usize,
439 total: usize,
441 },
442 Padded {
444 character: char,
446 width: usize,
448 },
449}
450
451impl DisplayCounterIndex {
452 pub fn new_counter(current: usize, total: usize) -> Self {
454 Self::Counter { current, total }
455 }
456
457 pub fn new_padded(character: char, width: usize) -> Self {
459 Self::Padded { character, width }
460 }
461}
462
463impl fmt::Display for DisplayCounterIndex {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 match self {
466 Self::Counter { current, total } => {
467 write!(
468 f,
469 "({:>width$}/{})",
470 current,
471 total,
472 width = usize_decimal_char_width(*total)
473 )
474 }
475 Self::Padded { character, width } => {
476 let s: String = std::iter::repeat_n(*character, 2 * *width + 1).collect();
481 write!(f, "({s})")
482 }
483 }
484 }
485}
486
487pub(crate) fn usize_decimal_char_width(n: usize) -> usize {
488 (n.checked_ilog10().unwrap_or(0) + 1).try_into().unwrap()
492}
493
494pub(crate) fn u32_decimal_char_width(n: u32) -> usize {
495 (n.checked_ilog10().unwrap_or(0) + 1).try_into().unwrap()
499}
500
501pub(crate) fn u64_decimal_char_width(n: u64) -> usize {
502 (n.checked_ilog10().unwrap_or(0) + 1).try_into().unwrap()
506}
507
508pub(crate) fn write_test_name(
510 name: &TestCaseName,
511 style: &Styles,
512 writer: &mut dyn WriteStr,
513) -> io::Result<()> {
514 let (module_path, trailing) = name.module_path_and_name();
515 if let Some(module_path) = module_path {
516 write!(
517 writer,
518 "{}{}",
519 module_path.style(style.module_path),
520 "::".style(style.module_path)
521 )?;
522 }
523 write!(writer, "{}", trailing.style(style.test_name))?;
524
525 Ok(())
526}
527
528pub(crate) struct DisplayTestName<'a> {
530 name: &'a TestCaseName,
531 styles: &'a Styles,
532}
533
534impl<'a> DisplayTestName<'a> {
535 pub(crate) fn new(name: &'a TestCaseName, styles: &'a Styles) -> Self {
536 Self { name, styles }
537 }
538}
539
540impl fmt::Display for DisplayTestName<'_> {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 let (module_path, trailing) = self.name.module_path_and_name();
543 if let Some(module_path) = module_path {
544 write!(
545 f,
546 "{}{}",
547 module_path.style(self.styles.module_path),
548 "::".style(self.styles.module_path)
549 )?;
550 }
551 write!(f, "{}", trailing.style(self.styles.test_name))?;
552
553 Ok(())
554 }
555}
556
557pub(crate) fn convert_build_platform(
558 platform: nextest_metadata::BuildPlatform,
559) -> guppy::graph::cargo::BuildPlatform {
560 match platform {
561 nextest_metadata::BuildPlatform::Target => guppy::graph::cargo::BuildPlatform::Target,
562 nextest_metadata::BuildPlatform::Host => guppy::graph::cargo::BuildPlatform::Host,
563 }
564}
565
566pub(crate) fn dylib_path_envvar() -> &'static str {
573 if cfg!(windows) {
574 "PATH"
575 } else if cfg!(target_os = "macos") {
576 "DYLD_FALLBACK_LIBRARY_PATH"
592 } else {
593 "LD_LIBRARY_PATH"
594 }
595}
596
597pub(crate) fn dylib_path() -> Vec<PathBuf> {
602 match std::env::var_os(dylib_path_envvar()) {
603 Some(var) => std::env::split_paths(&var).collect(),
604 None => Vec::new(),
605 }
606}
607
608#[cfg(windows)]
610pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
611 if !rel_path.is_relative() {
612 panic!("path for conversion to forward slash '{rel_path}' is not relative");
613 }
614 rel_path.as_str().replace('\\', "/").into()
615}
616
617#[cfg(not(windows))]
618pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
619 rel_path.to_path_buf()
620}
621
622#[cfg(windows)]
624pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
625 if !rel_path.is_relative() {
626 panic!("path for conversion to backslash '{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_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
633 rel_path.to_path_buf()
634}
635
636pub(crate) fn rel_path_join(rel_path: &Utf8Path, path: &Utf8Path) -> Utf8PathBuf {
638 assert!(rel_path.is_relative(), "rel_path {rel_path} is relative");
639 assert!(path.is_relative(), "path {path} is relative",);
640 format!("{rel_path}/{path}").into()
641}
642
643#[derive(Debug)]
644pub(crate) struct FormattedDuration(pub(crate) Duration);
645
646impl fmt::Display for FormattedDuration {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 let duration = self.0.as_secs_f64();
649 if duration > 60.0 {
650 write!(f, "{}m {:.2}s", duration as u32 / 60, duration % 60.0)
651 } else {
652 write!(f, "{duration:.2}s")
653 }
654 }
655}
656
657#[derive(Debug)]
658pub(crate) struct FormattedRelativeDuration(pub(crate) Duration);
659
660impl fmt::Display for FormattedRelativeDuration {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 fn item(unit: &'static str, value: u64) -> ControlFlow<(&'static str, u64)> {
666 if value > 0 {
667 ControlFlow::Break((unit, value))
668 } else {
669 ControlFlow::Continue(())
670 }
671 }
672
673 fn fmt(f: Duration) -> ControlFlow<(&'static str, u64), ()> {
677 let secs = f.as_secs();
678 let nanos = f.subsec_nanos();
679
680 let years = secs / 31_557_600; let year_days = secs % 31_557_600;
682 let months = year_days / 2_630_016; let month_days = year_days % 2_630_016;
684 let days = month_days / 86400;
685 let day_secs = month_days % 86400;
686 let hours = day_secs / 3600;
687 let minutes = day_secs % 3600 / 60;
688 let seconds = day_secs % 60;
689
690 let millis = nanos / 1_000_000;
691 let micros = nanos / 1_000;
692
693 item("y", years)?;
698 item("mo", months)?;
699 item("d", days)?;
700 item("h", hours)?;
701 item("m", minutes)?;
702 item("s", seconds)?;
703 item("ms", u64::from(millis))?;
704 item("us", u64::from(micros))?;
705 item("ns", u64::from(nanos))?;
706 ControlFlow::Continue(())
707 }
708
709 match fmt(self.0) {
710 ControlFlow::Break((unit, value)) => write!(f, "{value}{unit}"),
711 ControlFlow::Continue(()) => write!(f, "0s"),
712 }
713 }
714}
715
716#[derive(Clone, Debug)]
721pub struct ThemeCharacters {
722 hbar: char,
723 progress_chars: &'static str,
724 use_unicode: bool,
725}
726
727impl Default for ThemeCharacters {
728 fn default() -> Self {
729 Self {
730 hbar: '-',
731 progress_chars: "=> ",
732 use_unicode: false,
733 }
734 }
735}
736
737impl ThemeCharacters {
738 pub fn use_unicode(&mut self) {
740 self.hbar = '─';
741 self.progress_chars = "█▉▊▋▌▍▎▏ ";
743 self.use_unicode = true;
744 }
745
746 pub fn hbar_char(&self) -> char {
748 self.hbar
749 }
750
751 pub fn hbar(&self, width: usize) -> String {
753 std::iter::repeat_n(self.hbar, width).collect()
754 }
755
756 pub fn progress_chars(&self) -> &'static str {
758 self.progress_chars
759 }
760
761 pub fn tree_branch(&self) -> &'static str {
763 if self.use_unicode { "├─" } else { "|-" }
764 }
765
766 pub fn tree_last(&self) -> &'static str {
768 if self.use_unicode { "└─" } else { "\\-" }
769 }
770
771 pub fn tree_continuation(&self) -> &'static str {
773 if self.use_unicode { "│ " } else { "| " }
774 }
775
776 pub fn tree_space(&self) -> &'static str {
778 " "
779 }
780}
781
782pub(crate) fn display_exited_with(exit_status: ExitStatus) -> String {
784 match AbortStatus::extract(exit_status) {
785 Some(abort_status) => display_abort_status(abort_status),
786 None => match exit_status.code() {
787 Some(code) => format!("exited with exit code {code}"),
788 None => "exited with an unknown error".to_owned(),
789 },
790 }
791}
792
793pub(crate) fn display_abort_status(abort_status: AbortStatus) -> String {
795 match abort_status {
796 #[cfg(unix)]
797 AbortStatus::UnixSignal(sig) => match crate::helpers::signal_str(sig) {
798 Some(s) => {
799 format!("aborted with signal {sig} (SIG{s})")
800 }
801 None => {
802 format!("aborted with signal {sig}")
803 }
804 },
805 #[cfg(windows)]
806 AbortStatus::WindowsNtStatus(nt_status) => {
807 format!(
808 "aborted with code {}",
809 crate::helpers::display_nt_status(nt_status, Style::new())
811 )
812 }
813 #[cfg(windows)]
814 AbortStatus::JobObject => "terminated via job object".to_string(),
815 }
816}
817
818#[cfg(unix)]
819pub(crate) fn signal_str(signal: i32) -> Option<&'static str> {
820 match signal {
827 1 => Some("HUP"),
828 2 => Some("INT"),
829 3 => Some("QUIT"),
830 4 => Some("ILL"),
831 5 => Some("TRAP"),
832 6 => Some("ABRT"),
833 8 => Some("FPE"),
834 9 => Some("KILL"),
835 11 => Some("SEGV"),
836 13 => Some("PIPE"),
837 14 => Some("ALRM"),
838 15 => Some("TERM"),
839 _ => None,
840 }
841}
842
843#[cfg(windows)]
844pub(crate) fn display_nt_status(
845 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
846 bold_style: Style,
847) -> String {
848 let bolded_status = format!("{:#010x}", nt_status.style(bold_style));
852
853 match windows_nt_status_message(nt_status) {
854 Some(message) => format!("{bolded_status}: {message}"),
855 None => bolded_status,
856 }
857}
858
859#[cfg(windows)]
861pub(crate) fn windows_nt_status_message(
862 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
863) -> Option<smol_str::SmolStr> {
864 let win32_code = unsafe { windows_sys::Win32::Foundation::RtlNtStatusToDosError(nt_status) };
866
867 if win32_code == windows_sys::Win32::Foundation::ERROR_MR_MID_NOT_FOUND {
868 return None;
870 }
871
872 Some(smol_str::SmolStr::new(
873 io::Error::from_raw_os_error(win32_code as i32).to_string(),
874 ))
875}
876
877#[derive(Copy, Clone, Debug)]
878pub(crate) struct QuotedDisplay<'a, T: ?Sized>(pub(crate) &'a T);
879
880impl<T: ?Sized> fmt::Display for QuotedDisplay<'_, T>
881where
882 T: fmt::Display,
883{
884 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885 write!(f, "'{}'", self.0)
886 }
887}
888
889unsafe extern "C" {
891 fn __nextest_external_symbol_that_does_not_exist();
892}
893
894pub fn format_interceptor_too_many_tests(
896 cli_opt_name: &str,
897 mode: NextestRunMode,
898 test_count: usize,
899 test_instances: &[OwnedTestInstanceId],
900 list_styles: &Styles,
901 count_style: Style,
902) -> String {
903 let mut msg = format!(
904 "--{} requires exactly one {}, but {} {} were selected:",
905 cli_opt_name,
906 plural::tests_plural_if(mode, false),
907 test_count.style(count_style),
908 plural::tests_str(mode, test_count)
909 );
910
911 for test_instance in test_instances {
912 let display = DisplayTestInstance::new(None, None, test_instance.as_ref(), list_styles);
913 swrite!(msg, "\n {}", display);
914 }
915
916 if test_count > test_instances.len() {
917 let remaining = test_count - test_instances.len();
918 swrite!(
919 msg,
920 "\n ... and {} more {}",
921 remaining.style(count_style),
922 plural::tests_str(mode, remaining)
923 );
924 }
925
926 msg
927}
928
929#[inline]
930#[expect(dead_code)]
931pub(crate) fn statically_unreachable() -> ! {
932 unsafe {
933 __nextest_external_symbol_that_does_not_exist();
934 }
935 unreachable!("linker symbol above cannot be resolved")
936}
937
938#[cfg(test)]
939mod test {
940 use super::*;
941
942 #[test]
943 fn test_decimal_char_width() {
944 assert_eq!(1, usize_decimal_char_width(0));
945 assert_eq!(1, usize_decimal_char_width(1));
946 assert_eq!(1, usize_decimal_char_width(5));
947 assert_eq!(1, usize_decimal_char_width(9));
948 assert_eq!(2, usize_decimal_char_width(10));
949 assert_eq!(2, usize_decimal_char_width(11));
950 assert_eq!(2, usize_decimal_char_width(99));
951 assert_eq!(3, usize_decimal_char_width(100));
952 assert_eq!(3, usize_decimal_char_width(999));
953 }
954
955 #[test]
956 fn test_u64_decimal_char_width() {
957 assert_eq!(1, u64_decimal_char_width(0));
958 assert_eq!(1, u64_decimal_char_width(1));
959 assert_eq!(1, u64_decimal_char_width(9));
960 assert_eq!(2, u64_decimal_char_width(10));
961 assert_eq!(2, u64_decimal_char_width(99));
962 assert_eq!(3, u64_decimal_char_width(100));
963 assert_eq!(3, u64_decimal_char_width(999));
964 assert_eq!(6, u64_decimal_char_width(999_999));
965 assert_eq!(7, u64_decimal_char_width(1_000_000));
966 assert_eq!(8, u64_decimal_char_width(10_000_000));
967 assert_eq!(8, u64_decimal_char_width(11_000_000));
968 }
969}