1use crate::{
7 config::ScriptId,
8 list::{Styles, TestInstanceId},
9 reporter::events::AbortStatus,
10 write_str::WriteStr,
11};
12use camino::{Utf8Path, Utf8PathBuf};
13use owo_colors::{OwoColorize, Style};
14use std::{fmt, io, path::PathBuf, process::ExitStatus, time::Duration};
15
16pub mod plural {
18 pub fn were_plural_if(plural: bool) -> &'static str {
20 if plural { "were" } else { "was" }
21 }
22
23 pub fn setup_scripts_str(count: usize) -> &'static str {
25 if count == 1 {
26 "setup script"
27 } else {
28 "setup scripts"
29 }
30 }
31
32 pub fn tests_str(count: usize) -> &'static str {
34 tests_plural_if(count != 1)
35 }
36
37 pub fn tests_plural_if(plural: bool) -> &'static str {
39 if plural { "tests" } else { "test" }
40 }
41
42 pub fn binaries_str(count: usize) -> &'static str {
44 if count == 1 { "binary" } else { "binaries" }
45 }
46
47 pub fn paths_str(count: usize) -> &'static str {
49 if count == 1 { "path" } else { "paths" }
50 }
51
52 pub fn files_str(count: usize) -> &'static str {
54 if count == 1 { "file" } else { "files" }
55 }
56
57 pub fn directories_str(count: usize) -> &'static str {
59 if count == 1 {
60 "directory"
61 } else {
62 "directories"
63 }
64 }
65
66 pub fn this_crate_str(count: usize) -> &'static str {
68 if count == 1 {
69 "this crate"
70 } else {
71 "these crates"
72 }
73 }
74
75 pub fn libraries_str(count: usize) -> &'static str {
77 if count == 1 { "library" } else { "libraries" }
78 }
79
80 pub fn filters_str(count: usize) -> &'static str {
82 if count == 1 { "filter" } else { "filters" }
83 }
84
85 pub fn sections_str(count: usize) -> &'static str {
87 if count == 1 { "section" } else { "sections" }
88 }
89}
90
91pub(crate) struct DisplayTestInstance<'a> {
92 instance: TestInstanceId<'a>,
93 styles: &'a Styles,
94}
95
96impl<'a> DisplayTestInstance<'a> {
97 pub(crate) fn new(instance: TestInstanceId<'a>, styles: &'a Styles) -> Self {
98 Self { instance, styles }
99 }
100}
101
102impl fmt::Display for DisplayTestInstance<'_> {
103 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104 write!(
105 f,
106 "{} ",
107 self.instance.binary_id.style(self.styles.binary_id),
108 )?;
109 fmt_write_test_name(self.instance.test_name, self.styles, f)
110 }
111}
112
113pub(crate) struct DisplayScriptInstance {
114 script_id: ScriptId,
115 full_command: String,
116 script_id_style: Style,
117}
118
119impl DisplayScriptInstance {
120 pub(crate) fn new(
121 script_id: ScriptId,
122 command: &str,
123 args: &[String],
124 script_id_style: Style,
125 ) -> Self {
126 let full_command =
127 shell_words::join(std::iter::once(command).chain(args.iter().map(|arg| arg.as_ref())));
128
129 Self {
130 script_id,
131 full_command,
132 script_id_style,
133 }
134 }
135}
136
137impl fmt::Display for DisplayScriptInstance {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 write!(
140 f,
141 "{}: {}",
142 self.script_id.style(self.script_id_style),
143 self.full_command,
144 )
145 }
146}
147
148pub(crate) fn write_test_name(
150 name: &str,
151 style: &Styles,
152 writer: &mut dyn WriteStr,
153) -> io::Result<()> {
154 let mut splits = name.rsplitn(2, "::");
156 let trailing = splits.next().expect("test should have at least 1 element");
157 if let Some(rest) = splits.next() {
158 write!(
159 writer,
160 "{}{}",
161 rest.style(style.module_path),
162 "::".style(style.module_path)
163 )?;
164 }
165 write!(writer, "{}", trailing.style(style.test_name))?;
166
167 Ok(())
168}
169
170pub(crate) fn fmt_write_test_name(
172 name: &str,
173 style: &Styles,
174 writer: &mut dyn fmt::Write,
175) -> fmt::Result {
176 let mut splits = name.rsplitn(2, "::");
178 let trailing = splits.next().expect("test should have at least 1 element");
179 if let Some(rest) = splits.next() {
180 write!(
181 writer,
182 "{}{}",
183 rest.style(style.module_path),
184 "::".style(style.module_path)
185 )?;
186 }
187 write!(writer, "{}", trailing.style(style.test_name))?;
188
189 Ok(())
190}
191
192pub(crate) fn convert_build_platform(
193 platform: nextest_metadata::BuildPlatform,
194) -> guppy::graph::cargo::BuildPlatform {
195 match platform {
196 nextest_metadata::BuildPlatform::Target => guppy::graph::cargo::BuildPlatform::Target,
197 nextest_metadata::BuildPlatform::Host => guppy::graph::cargo::BuildPlatform::Host,
198 }
199}
200
201pub(crate) fn dylib_path_envvar() -> &'static str {
208 if cfg!(windows) {
209 "PATH"
210 } else if cfg!(target_os = "macos") {
211 "DYLD_FALLBACK_LIBRARY_PATH"
227 } else {
228 "LD_LIBRARY_PATH"
229 }
230}
231
232pub(crate) fn dylib_path() -> Vec<PathBuf> {
237 match std::env::var_os(dylib_path_envvar()) {
238 Some(var) => std::env::split_paths(&var).collect(),
239 None => Vec::new(),
240 }
241}
242
243#[cfg(windows)]
245pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
246 if !rel_path.is_relative() {
247 panic!(
248 "path for conversion to forward slash '{}' is not relative",
249 rel_path
250 );
251 }
252 rel_path.as_str().replace('\\', "/").into()
253}
254
255#[cfg(not(windows))]
256pub(crate) fn convert_rel_path_to_forward_slash(rel_path: &Utf8Path) -> Utf8PathBuf {
257 rel_path.to_path_buf()
258}
259
260#[cfg(windows)]
262pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
263 if !rel_path.is_relative() {
264 panic!(
265 "path for conversion to backslash '{}' is not relative",
266 rel_path
267 );
268 }
269 rel_path.as_str().replace('/', "\\").into()
270}
271
272#[cfg(not(windows))]
273pub(crate) fn convert_rel_path_to_main_sep(rel_path: &Utf8Path) -> Utf8PathBuf {
274 rel_path.to_path_buf()
275}
276
277pub(crate) fn rel_path_join(rel_path: &Utf8Path, path: &Utf8Path) -> Utf8PathBuf {
279 assert!(rel_path.is_relative(), "rel_path {rel_path} is relative");
280 assert!(path.is_relative(), "path {path} is relative",);
281 format!("{rel_path}/{path}").into()
282}
283
284#[derive(Debug)]
285pub(crate) struct FormattedDuration(pub(crate) Duration);
286
287impl fmt::Display for FormattedDuration {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 let duration = self.0.as_secs_f64();
290 if duration > 60.0 {
291 write!(f, "{}m {:.2}s", duration as u32 / 60, duration % 60.0)
292 } else {
293 write!(f, "{duration:.2}s")
294 }
295 }
296}
297
298pub(crate) fn display_exited_with(exit_status: ExitStatus) -> String {
300 match AbortStatus::extract(exit_status) {
301 Some(abort_status) => display_abort_status(abort_status),
302 None => match exit_status.code() {
303 Some(code) => format!("exited with exit code {}", code),
304 None => "exited with an unknown error".to_owned(),
305 },
306 }
307}
308
309pub(crate) fn display_abort_status(abort_status: AbortStatus) -> String {
311 match abort_status {
312 #[cfg(unix)]
313 AbortStatus::UnixSignal(sig) => match crate::helpers::signal_str(sig) {
314 Some(s) => {
315 format!("aborted with signal {sig} (SIG{s})")
316 }
317 None => {
318 format!("aborted with signal {sig}")
319 }
320 },
321 #[cfg(windows)]
322 AbortStatus::WindowsNtStatus(nt_status) => {
323 format!(
324 "aborted with code {}",
325 crate::helpers::display_nt_status(nt_status, Style::new())
327 )
328 }
329 #[cfg(windows)]
330 AbortStatus::JobObject => "terminated via job object".to_string(),
331 }
332}
333
334#[cfg(unix)]
335pub(crate) fn signal_str(signal: i32) -> Option<&'static str> {
336 match signal {
343 1 => Some("HUP"),
344 2 => Some("INT"),
345 3 => Some("QUIT"),
346 4 => Some("ILL"),
347 5 => Some("TRAP"),
348 6 => Some("ABRT"),
349 8 => Some("FPE"),
350 9 => Some("KILL"),
351 11 => Some("SEGV"),
352 13 => Some("PIPE"),
353 14 => Some("ALRM"),
354 15 => Some("TERM"),
355 _ => None,
356 }
357}
358
359#[cfg(windows)]
360pub(crate) fn display_nt_status(
361 nt_status: windows_sys::Win32::Foundation::NTSTATUS,
362 bold_style: Style,
363) -> String {
364 let bolded_status = format!("{:#010x}", nt_status.style(bold_style));
368 let win32_code = unsafe { windows_sys::Win32::Foundation::RtlNtStatusToDosError(nt_status) };
370
371 if win32_code == windows_sys::Win32::Foundation::ERROR_MR_MID_NOT_FOUND {
372 return bolded_status;
374 }
375
376 format!(
377 "{bolded_status}: {}",
378 io::Error::from_raw_os_error(win32_code as i32)
379 )
380}
381
382#[derive(Copy, Clone, Debug)]
383pub(crate) struct QuotedDisplay<'a, T: ?Sized>(pub(crate) &'a T);
384
385impl<T: ?Sized> fmt::Display for QuotedDisplay<'_, T>
386where
387 T: fmt::Display,
388{
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 write!(f, "'{}'", self.0)
391 }
392}
393
394unsafe extern "C" {
396 fn __nextest_external_symbol_that_does_not_exist();
397}
398
399#[inline]
400#[expect(dead_code)]
401pub(crate) fn statically_unreachable() -> ! {
402 unsafe {
403 __nextest_external_symbol_that_does_not_exist();
404 }
405 unreachable!("linker symbol above cannot be resolved")
406}