Skip to main content

nextest_runner/
errors.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Errors produced by nextest.
5
6use crate::{
7    cargo_config::{TargetTriple, TargetTripleSource},
8    config::{
9        core::{
10            ConfigExperimental, ConfigPath, ConfigPathResolveError, ConfigSource, ConfigStyles,
11            NextestConfig, ToolName,
12        },
13        elements::{CustomTestGroup, TestGroup},
14        scripts::{ProfileScriptType, ScriptId, ScriptType},
15    },
16    helpers::{display_exited_with, dylib_path_envvar, plural},
17    indenter::{DisplayIndented, indented},
18    record::{
19        PortableRecordingFormatVersion, PortableRecordingVersionIncompatibility, RecordedRunInfo,
20        RunIdIndex, RunsJsonFormatVersion, StoreFormatVersion, StoreVersionIncompatibility,
21    },
22    redact::{Redactor, SizeDisplay},
23    reuse_build::{ArchiveFormat, ArchiveStep},
24    target_runner::PlatformRunnerSource,
25};
26use bytesize::ByteSize;
27use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
28use camino_anchored::{CurrentDirError, ResolvePathError};
29use config::ConfigError;
30use eazip::CompressionMethod;
31use etcetera::HomeDirError;
32use itertools::{Either, Itertools};
33use nextest_filtering::errors::FiltersetParseErrors;
34use nextest_metadata::{RustBinaryId, TestCaseName};
35use owo_colors::{OwoColorize, Style};
36use quick_junit::ReportUuid;
37use serde::{Deserialize, Serialize};
38use smol_str::SmolStr;
39use std::{
40    borrow::Cow,
41    collections::BTreeSet,
42    env::JoinPathsError,
43    fmt::{self, Write as _},
44    path::PathBuf,
45    process::ExitStatus,
46    sync::Arc,
47};
48use target_spec_miette::IntoMietteDiagnostic;
49use thiserror::Error;
50
51/// An error that occurred while parsing the config.
52#[derive(Debug, Error)]
53#[error("{}", self.display_header(ConfigStyles::default()))]
54#[non_exhaustive]
55pub struct ConfigParseError {
56    config_file: ConfigErrorPath,
57    tool: Option<ToolName>,
58    #[source]
59    kind: ConfigParseErrorKind,
60}
61
62impl ConfigParseError {
63    pub(crate) fn new(source: &ConfigSource, kind: ConfigParseErrorKind) -> Self {
64        Self {
65            config_file: ConfigErrorPath::Resolved(source.path().clone()),
66            tool: source.tool().cloned(),
67            kind,
68        }
69    }
70
71    /// Creates a new `ConfigParseError` for errors not attributable to a single
72    /// source, such as the composite config build.
73    pub(crate) fn from_path(config_file: &ConfigPath, kind: ConfigParseErrorKind) -> Self {
74        Self {
75            config_file: ConfigErrorPath::Resolved(config_file.clone()),
76            tool: None,
77            kind,
78        }
79    }
80
81    pub(crate) fn from_paths_capture_error(
82        workspace_root: &Utf8Path,
83        config_file: Option<&Utf8Path>,
84        error: ConfigPathsCaptureError,
85    ) -> Self {
86        let config_file = match config_file {
87            Some(config_file) => config_file.to_owned(),
88            None => workspace_root.join(NextestConfig::CONFIG_PATH),
89        };
90        Self {
91            config_file: ConfigErrorPath::Unresolved(config_file),
92            tool: None,
93            kind: ConfigParseErrorKind::PathsCaptureError(Box::new(error)),
94        }
95    }
96
97    /// Returns the config file for this error.
98    pub fn config_file(&self) -> &Utf8Path {
99        match &self.config_file {
100            ConfigErrorPath::Resolved(path) => path.absolute_path(),
101            ConfigErrorPath::Unresolved(path) => path,
102        }
103    }
104
105    /// Returns the invocation-relative path for diagnostics.
106    pub fn display_config_file(&self) -> impl fmt::Display + '_ {
107        &self.config_file
108    }
109
110    /// Renders "`path` provided by tool `x`".
111    pub fn display_file(&self, styles: ConfigStyles) -> impl fmt::Display + '_ {
112        DisplayConfigFile {
113            error: self,
114            styles,
115        }
116    }
117
118    /// Renders the full "failed to parse nextest config at ..." heading.
119    ///
120    /// The Display impl is this with no styling.
121    pub fn display_header(&self, styles: ConfigStyles) -> impl fmt::Display + '_ {
122        DisplayConfigHeader {
123            error: self,
124            styles,
125        }
126    }
127
128    /// Returns the tool name associated with this error.
129    pub fn tool(&self) -> Option<&ToolName> {
130        self.tool.as_ref()
131    }
132
133    /// Returns the kind of error this is.
134    pub fn kind(&self) -> &ConfigParseErrorKind {
135        &self.kind
136    }
137}
138
139struct DisplayConfigFile<'a> {
140    error: &'a ConfigParseError,
141    styles: ConfigStyles,
142}
143
144impl fmt::Display for DisplayConfigFile<'_> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(
147            f,
148            "`{}`{}",
149            self.error.display_config_file().style(self.styles.path),
150            provided_by_tool(self.error.tool(), self.styles.tool),
151        )
152    }
153}
154
155struct DisplayConfigHeader<'a> {
156    error: &'a ConfigParseError,
157    styles: ConfigStyles,
158}
159
160impl fmt::Display for DisplayConfigHeader<'_> {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        write!(
163            f,
164            "failed to parse nextest config at {}",
165            self.error.display_file(self.styles),
166        )
167    }
168}
169
170#[derive(Debug)]
171enum ConfigErrorPath {
172    Resolved(ConfigPath),
173    Unresolved(Utf8PathBuf),
174}
175
176impl fmt::Display for ConfigErrorPath {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        match self {
179            Self::Resolved(path) => fmt::Display::fmt(&path.display(), f),
180            Self::Unresolved(path) => fmt::Display::fmt(path, f),
181        }
182    }
183}
184
185impl From<ConfigPathResolveError> for ConfigParseError {
186    fn from(error: ConfigPathResolveError) -> Self {
187        Self {
188            config_file: ConfigErrorPath::Unresolved(error.path),
189            tool: None,
190            kind: ConfigParseErrorKind::PathResolveError(Box::new(error.error)),
191        }
192    }
193}
194
195/// An error produced by
196/// [`ConfigPaths::capture`](crate::config::core::ConfigPaths::capture).
197#[derive(Debug, Error)]
198pub enum ConfigPathsCaptureError {
199    /// The process's current directory could not be determined.
200    #[error("failed to determine the current directory, which config paths are resolved against")]
201    CurrentDir(#[source] CurrentDirError),
202
203    /// The workspace root could not be resolved against the current directory.
204    #[error("failed to resolve workspace root `{}`", .0.input())]
205    WorkspaceRoot(#[source] ResolvePathError),
206}
207
208/// Renders " provided by tool `x`" when a tool provided the file, and nothing
209/// otherwise.
210pub fn provided_by_tool(tool: Option<&ToolName>, style: Style) -> impl fmt::Display + '_ {
211    ProvidedByTool { tool, style }
212}
213
214struct ProvidedByTool<'a> {
215    tool: Option<&'a ToolName>,
216    style: Style,
217}
218
219impl fmt::Display for ProvidedByTool<'_> {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self.tool {
222            Some(tool) => write!(f, " provided by tool `{}`", tool.style(self.style)),
223            None => Ok(()),
224        }
225    }
226}
227
228/// The kind of error that occurred while parsing a config.
229///
230/// Returned by [`ConfigParseError::kind`].
231#[derive(Debug, Error)]
232#[non_exhaustive]
233pub enum ConfigParseErrorKind {
234    /// An input path could not be resolved to an absolute location.
235    #[error("error resolving the configuration path")]
236    PathResolveError(#[source] Box<ResolvePathError>),
237    /// The current directory or workspace root could not be determined.
238    #[error(transparent)]
239    PathsCaptureError(Box<ConfigPathsCaptureError>),
240    /// An error occurred while building the config.
241    #[error(transparent)]
242    BuildError(Box<ConfigError>),
243    /// An error occurred while parsing the config into a table.
244    #[error(transparent)]
245    TomlParseError(Box<toml::de::Error>),
246    #[error(transparent)]
247    /// An error occurred while deserializing the config.
248    DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
249    /// An error occurred while reading a config file.
250    #[error(transparent)]
251    ReadError(std::io::Error),
252    /// An error occurred while deserializing the config (version only).
253    #[error(transparent)]
254    VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
255    /// Errors occurred while compiling configuration strings.
256    #[error("error parsing compiled data (destructure this variant for more details)")]
257    CompileErrors(Vec<ConfigCompileError>),
258    /// An invalid set of test groups was defined by the user.
259    #[error("invalid test groups defined: {}\n(test groups cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
260    InvalidTestGroupsDefined(BTreeSet<CustomTestGroup>),
261    /// An invalid set of test groups was defined by a tool config file.
262    #[error(
263        "invalid test groups defined by tool: {}\n(test groups must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
264    InvalidTestGroupsDefinedByTool(BTreeSet<CustomTestGroup>),
265    /// Some test groups were unknown.
266    #[error("unknown test groups specified by config (destructure this variant for more details)")]
267    UnknownTestGroups {
268        /// The list of errors that occurred.
269        errors: Vec<UnknownTestGroupError>,
270
271        /// Known groups up to this point.
272        known_groups: BTreeSet<TestGroup>,
273    },
274    /// Both `[script.*]` and `[scripts.*]` were defined.
275    #[error(
276        "both `[script.*]` and `[scripts.*]` defined\n\
277         (hint: [script.*] will be removed in the future: switch to [scripts.setup.*])"
278    )]
279    BothScriptAndScriptsDefined,
280    /// An invalid set of config scripts was defined by the user.
281    #[error("invalid config scripts defined: {}\n(config scripts cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
282    InvalidConfigScriptsDefined(BTreeSet<ScriptId>),
283    /// An invalid set of config scripts was defined by a tool config file.
284    #[error(
285        "invalid config scripts defined by tool: {}\n(config scripts must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
286    InvalidConfigScriptsDefinedByTool(BTreeSet<ScriptId>),
287    /// The same config script name was used across config script types.
288    #[error(
289        "config script names used more than once: {}\n\
290         (config script names must be unique across all script types)", .0.iter().join(", ")
291    )]
292    DuplicateConfigScriptNames(BTreeSet<ScriptId>),
293    /// Errors occurred while parsing `[[profile.<profile-name>.scripts]]`.
294    #[error(
295        "errors in profile-specific config scripts (destructure this variant for more details)"
296    )]
297    ProfileScriptErrors {
298        /// The errors that occurred.
299        errors: Box<ProfileScriptErrors>,
300
301        /// Known scripts up to this point.
302        known_scripts: BTreeSet<ScriptId>,
303    },
304    /// An unknown experimental feature or features were defined.
305    #[error("unknown experimental features defined (destructure this variant for more details)")]
306    UnknownExperimentalFeatures {
307        /// The set of unknown features.
308        unknown: BTreeSet<String>,
309
310        /// The set of known features.
311        known: BTreeSet<ConfigExperimental>,
312    },
313    /// A tool specified an experimental feature.
314    ///
315    /// Tools are not allowed to specify experimental features.
316    #[error(
317        "tool config file specifies experimental features `{}` \
318         -- only repository config files can do so",
319        .features.iter().join(", "),
320    )]
321    ExperimentalFeaturesInToolConfig {
322        /// The name of the experimental feature.
323        features: BTreeSet<String>,
324    },
325    /// Experimental features were used but not enabled.
326    #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
327    ExperimentalFeaturesNotEnabled {
328        /// The features that were not enabled.
329        missing_features: BTreeSet<ConfigExperimental>,
330    },
331    /// An inheritance cycle was detected in the profile configuration.
332    #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
333    InheritanceErrors(Vec<InheritsError>),
334    /// A tool provided more than one config file.
335    #[error(
336        "tool `{tool}` already provided config file `{}`\n\
337         (hint: each tool can provide at most one config file: merge the files, \
338         or pass `--tool-config-file {tool}:<path>` only once)",
339        .first.display(),
340    )]
341    DuplicateToolConfigFile {
342        /// The tool that passed more than one file.
343        tool: ToolName,
344        /// The file from the earlier, higher-priority argument.
345        first: ConfigPath,
346    },
347}
348
349impl From<ConfigError> for ConfigParseErrorKind {
350    fn from(error: ConfigError) -> Self {
351        ConfigParseErrorKind::BuildError(Box::new(error))
352    }
353}
354
355/// An error that occurred while compiling overrides or scripts specified in
356/// configuration.
357#[derive(Debug)]
358#[non_exhaustive]
359pub struct ConfigCompileError {
360    /// The name of the profile under which the data was found.
361    pub profile_name: String,
362
363    /// The section within the profile where the error occurred.
364    pub section: ConfigCompileSection,
365
366    /// The kind of error that occurred.
367    pub kind: ConfigCompileErrorKind,
368}
369
370/// For a [`ConfigCompileError`], the section within the profile where the error
371/// occurred.
372#[derive(Debug)]
373pub enum ConfigCompileSection {
374    /// `profile.<profile-name>.default-filter`.
375    DefaultFilter,
376
377    /// `[[profile.<profile-name>.overrides]]` at the corresponding index.
378    Override(usize),
379
380    /// `[[profile.<profile-name>.scripts]]` at the corresponding index.
381    Script(usize),
382}
383
384/// The kind of error that occurred while parsing config overrides.
385#[derive(Debug)]
386#[non_exhaustive]
387pub enum ConfigCompileErrorKind {
388    /// Neither `platform` nor `filter` were specified.
389    ConstraintsNotSpecified {
390        /// Whether `default-filter` was specified.
391        ///
392        /// If default-filter is specified, then specifying `filter` is not
393        /// allowed -- so we show a different message in that case.
394        default_filter_specified: bool,
395    },
396
397    /// Both `filter` and `default-filter` were specified.
398    ///
399    /// It only makes sense to specify one of the two.
400    FilterAndDefaultFilterSpecified,
401
402    /// One or more errors occurred while parsing expressions.
403    Parse {
404        /// A potential error that occurred while parsing the host platform expression.
405        host_parse_error: Option<target_spec::Error>,
406
407        /// A potential error that occurred while parsing the target platform expression.
408        target_parse_error: Option<target_spec::Error>,
409
410        /// Filterset or default filter parse errors.
411        filter_parse_errors: Vec<FiltersetParseErrors>,
412    },
413}
414
415impl ConfigCompileErrorKind {
416    /// Returns [`miette::Report`]s for each error recorded by self.
417    pub fn reports(&self) -> impl Iterator<Item = miette::Report> + '_ {
418        match self {
419            Self::ConstraintsNotSpecified {
420                default_filter_specified,
421            } => {
422                let message = if *default_filter_specified {
423                    "for override with `default-filter`, `platform` must also be specified"
424                } else {
425                    "at least one of `platform` and `filter` must be specified"
426                };
427                Either::Left(std::iter::once(miette::Report::msg(message)))
428            }
429            Self::FilterAndDefaultFilterSpecified => {
430                Either::Left(std::iter::once(miette::Report::msg(
431                    "at most one of `filter` and `default-filter` must be specified",
432                )))
433            }
434            Self::Parse {
435                host_parse_error,
436                target_parse_error,
437                filter_parse_errors,
438            } => {
439                let host_parse_report = host_parse_error
440                    .as_ref()
441                    .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
442                let target_parse_report = target_parse_error
443                    .as_ref()
444                    .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
445                let filter_parse_reports =
446                    filter_parse_errors.iter().flat_map(|filter_parse_errors| {
447                        filter_parse_errors.errors.iter().map(|single_error| {
448                            miette::Report::new(single_error.clone())
449                                .with_source_code(filter_parse_errors.input.to_owned())
450                        })
451                    });
452
453                Either::Right(
454                    host_parse_report
455                        .into_iter()
456                        .chain(target_parse_report)
457                        .chain(filter_parse_reports),
458                )
459            }
460        }
461    }
462}
463
464/// A test priority specified was out of range.
465#[derive(Clone, Debug, Error)]
466#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
467pub struct TestPriorityOutOfRange {
468    /// The priority that was out of range.
469    pub priority: i8,
470}
471
472/// An execution error occurred while attempting to start a test.
473#[derive(Clone, Debug, Error)]
474pub enum ChildStartError {
475    /// An error occurred while creating a temporary path for a setup script.
476    #[error("error creating temporary path for setup script")]
477    TempPath(#[source] Arc<std::io::Error>),
478
479    /// An error occurred while spawning the child process.
480    #[error("error spawning child process")]
481    Spawn(#[source] Arc<std::io::Error>),
482}
483
484/// An error that occurred while reading the output of a setup script.
485#[derive(Clone, Debug, Error)]
486pub enum SetupScriptOutputError {
487    /// An error occurred while opening the setup script environment file.
488    #[error("error opening environment file `{path}`")]
489    EnvFileOpen {
490        /// The path to the environment file.
491        path: Utf8PathBuf,
492
493        /// The underlying error.
494        #[source]
495        error: Arc<std::io::Error>,
496    },
497
498    /// An error occurred while reading the setup script environment file.
499    #[error("error reading environment file `{path}`")]
500    EnvFileRead {
501        /// The path to the environment file.
502        path: Utf8PathBuf,
503
504        /// The underlying error.
505        #[source]
506        error: Arc<std::io::Error>,
507    },
508
509    /// An error occurred while parsing the setup script environment file.
510    #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
511    EnvFileParse {
512        /// The path to the environment file.
513        path: Utf8PathBuf,
514        /// The line at issue.
515        line: String,
516    },
517
518    /// An environment variable key in the environment file was invalid.
519    #[error("error in environment file `{path}`")]
520    EnvFileInvalidKey {
521        /// The path to the environment file.
522        path: Utf8PathBuf,
523
524        /// The underlying error.
525        #[source]
526        error: EnvVarError,
527    },
528}
529
530/// An error that describes an invalid/reserved key or value in an environment variable.
531#[derive(Clone, Debug, Error)]
532pub enum EnvVarError {
533    /// An environment variable key was reserved.
534    #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
535    ReservedKey {
536        /// The environment variable name.
537        key: String,
538    },
539
540    // See: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
541    /// An environment variable key is invalid.
542    #[error("key `{key}` does not consist solely of letters, digits, and underscores")]
543    InvalidKey {
544        /// The environment variable name.
545        key: String,
546    },
547
548    /// An environment variable key is invalid.
549    #[error("key `{key}` does not start with a letter or underscore")]
550    InvalidKeyStartChar {
551        /// The environment variable name.
552        key: String,
553    },
554}
555
556// `Display` messages are phrased as statements ("key X does not..."), but
557// `serde::de::Expected` needs them as expectations ("a key that..."). This impl
558// provides the expected-form messages for use with `invalid_value`.
559impl serde::de::Expected for EnvVarError {
560    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561        f.write_str(match self {
562            Self::ReservedKey { .. } => {
563                "a key that does not begin with `NEXTEST`, which is reserved for internal use"
564            }
565            Self::InvalidKey { .. } => {
566                "a key that consists solely of letters, digits, and underscores"
567            }
568            Self::InvalidKeyStartChar { .. } => "a key that starts with a letter or underscore",
569        })
570    }
571}
572
573/// A list of errors that implements `Error`.
574///
575/// In the future, we'll likely want to replace this with a `miette::Diagnostic`-based error, since
576/// that supports multiple causes via "related".
577#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
578pub struct ErrorList<T> {
579    // A description of what the errors are.
580    description: Cow<'static, str>,
581    // Invariant: this list is non-empty.
582    inner: Vec<T>,
583}
584
585impl<T: std::error::Error> ErrorList<T> {
586    pub(crate) fn new<U>(description: &'static str, errors: Vec<U>) -> Option<Self>
587    where
588        T: From<U>,
589    {
590        if errors.is_empty() {
591            None
592        } else {
593            Some(Self {
594                description: Cow::Borrowed(description),
595                inner: errors.into_iter().map(T::from).collect(),
596            })
597        }
598    }
599
600    /// Returns a short summary of the error list.
601    pub(crate) fn short_message(&self) -> String {
602        let string = self.to_string();
603        match string.lines().next() {
604            // Remove a trailing colon if it exists for a better UX.
605            Some(first_line) => first_line.trim_end_matches(':').to_string(),
606            None => String::new(),
607        }
608    }
609
610    /// Returns the description of what the errors are.
611    pub fn description(&self) -> &str {
612        &self.description
613    }
614
615    /// Iterates over the errors in this list.
616    pub fn iter(&self) -> impl Iterator<Item = &T> {
617        self.inner.iter()
618    }
619
620    /// Transforms the errors in this list using the given function.
621    pub fn map<U, F>(self, f: F) -> ErrorList<U>
622    where
623        U: std::error::Error,
624        F: FnMut(T) -> U,
625    {
626        ErrorList {
627            description: self.description,
628            inner: self.inner.into_iter().map(f).collect(),
629        }
630    }
631}
632
633impl<T: std::error::Error> IntoIterator for ErrorList<T> {
634    type Item = T;
635    type IntoIter = std::vec::IntoIter<T>;
636
637    fn into_iter(self) -> Self::IntoIter {
638        self.inner.into_iter()
639    }
640}
641
642impl<T: std::error::Error> fmt::Display for ErrorList<T> {
643    fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
644        // If a single error occurred, pretend that this is just that.
645        if self.inner.len() == 1 {
646            return write!(f, "{}", self.inner[0]);
647        }
648
649        // Otherwise, list all errors.
650        writeln!(
651            f,
652            "{} errors occurred {}:",
653            self.inner.len(),
654            self.description,
655        )?;
656        for error in &self.inner {
657            let mut indent = indented(f).with_str("  ").skip_initial();
658            writeln!(indent, "* {}", DisplayErrorChain::new(error))?;
659            f = indent.into_inner();
660        }
661        Ok(())
662    }
663}
664
665#[cfg(test)]
666impl<T: proptest::arbitrary::Arbitrary + std::fmt::Debug + 'static> proptest::arbitrary::Arbitrary
667    for ErrorList<T>
668{
669    type Parameters = ();
670    type Strategy = proptest::strategy::BoxedStrategy<Self>;
671
672    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
673        use proptest::prelude::*;
674
675        // Generate 1-5 errors (non-empty).
676        proptest::collection::vec(any::<T>(), 1..=5)
677            .prop_map(|inner| ErrorList {
678                description: Cow::Borrowed("test errors"),
679                inner,
680            })
681            .boxed()
682    }
683}
684
685impl<T: std::error::Error> std::error::Error for ErrorList<T> {
686    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
687        if self.inner.len() == 1 {
688            self.inner[0].source()
689        } else {
690            // More than one error occurred, so we can't return a single error here. Instead, we
691            // return `None` and display the chain of causes in `fmt::Display`.
692            None
693        }
694    }
695}
696
697/// A wrapper type to display a chain of errors with internal indentation.
698///
699/// This is similar to the display-error-chain crate, but uses an indenter
700/// internally to ensure that subsequent lines are also nested.
701pub struct DisplayErrorChain<E> {
702    error: E,
703    initial_indent: &'static str,
704}
705
706impl<E: std::error::Error> DisplayErrorChain<E> {
707    /// Creates a new `DisplayErrorChain` with the given error.
708    pub fn new(error: E) -> Self {
709        Self {
710            error,
711            initial_indent: "",
712        }
713    }
714
715    /// Creates a new `DisplayErrorChain` with the given error and initial indentation.
716    pub fn new_with_initial_indent(initial_indent: &'static str, error: E) -> Self {
717        Self {
718            error,
719            initial_indent,
720        }
721    }
722}
723
724impl<E> fmt::Display for DisplayErrorChain<E>
725where
726    E: std::error::Error,
727{
728    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
729        let mut writer = indented(f).with_str(self.initial_indent);
730        write!(writer, "{}", self.error)?;
731
732        let Some(mut cause) = self.error.source() else {
733            return Ok(());
734        };
735
736        write!(writer, "\n  caused by:")?;
737
738        loop {
739            writeln!(writer)?;
740            // Wrap the existing writer to accumulate indentation.
741            let mut indent = indented(&mut writer).with_str("    ").skip_initial();
742            write!(indent, "  - {cause}")?;
743
744            let Some(next_cause) = cause.source() else {
745                break Ok(());
746            };
747
748            cause = next_cause;
749        }
750    }
751}
752
753/// An error was returned while managing a child process or reading its output.
754#[derive(Clone, Debug, Error)]
755pub enum ChildError {
756    /// An error occurred while reading from a child file descriptor.
757    #[error(transparent)]
758    Fd(#[from] ChildFdError),
759
760    /// An error occurred while reading the output of a setup script.
761    #[error(transparent)]
762    SetupScriptOutput(#[from] SetupScriptOutputError),
763}
764
765/// An error was returned while reading from child a file descriptor.
766#[derive(Clone, Debug, Error)]
767pub enum ChildFdError {
768    /// An error occurred while reading standard output.
769    #[error("error reading standard output")]
770    ReadStdout(#[source] Arc<std::io::Error>),
771
772    /// An error occurred while reading standard error.
773    #[error("error reading standard error")]
774    ReadStderr(#[source] Arc<std::io::Error>),
775
776    /// An error occurred while reading a combined stream.
777    #[error("error reading combined stream")]
778    ReadCombined(#[source] Arc<std::io::Error>),
779
780    /// An error occurred while waiting for the child process to exit.
781    #[error("error waiting for child process to exit")]
782    Wait(#[source] Arc<std::io::Error>),
783}
784
785/// An unknown test group was specified in the config.
786#[derive(Clone, Debug, Eq, PartialEq)]
787#[non_exhaustive]
788pub struct UnknownTestGroupError {
789    /// The name of the profile under which the unknown test group was found.
790    pub profile_name: String,
791
792    /// The name of the unknown test group.
793    pub name: TestGroup,
794}
795
796/// While parsing profile-specific config scripts, an unknown script was
797/// encountered.
798#[derive(Clone, Debug, Eq, PartialEq)]
799pub struct ProfileUnknownScriptError {
800    /// The name of the profile under which the errors occurred.
801    pub profile_name: String,
802
803    /// The name of the unknown script.
804    pub name: ScriptId,
805}
806
807/// While parsing profile-specific config scripts, a script of the wrong type
808/// was encountered.
809#[derive(Clone, Debug, Eq, PartialEq)]
810pub struct ProfileWrongConfigScriptTypeError {
811    /// The name of the profile under which the errors occurred.
812    pub profile_name: String,
813
814    /// The name of the config script.
815    pub name: ScriptId,
816
817    /// The script type that the user attempted to use the script as.
818    pub attempted: ProfileScriptType,
819
820    /// The script type that the script actually is.
821    pub actual: ScriptType,
822}
823
824/// While parsing profile-specific config scripts, a list-time-enabled script
825/// used a filter that can only be used at test run time.
826#[derive(Clone, Debug, Eq, PartialEq)]
827pub struct ProfileListScriptUsesRunFiltersError {
828    /// The name of the profile under which the errors occurred.
829    pub profile_name: String,
830
831    /// The name of the config script.
832    pub name: ScriptId,
833
834    /// The script type.
835    pub script_type: ProfileScriptType,
836
837    /// The filters that were used.
838    pub filters: BTreeSet<String>,
839}
840
841/// Errors that occurred while parsing `[[profile.*.scripts]]`.
842#[derive(Clone, Debug, Default)]
843pub struct ProfileScriptErrors {
844    /// The list of unknown script errors.
845    pub unknown_scripts: Vec<ProfileUnknownScriptError>,
846
847    /// The list of wrong script type errors.
848    pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
849
850    /// The list of list-time-enabled scripts that used a run-time filter.
851    pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
852}
853
854impl ProfileScriptErrors {
855    /// Returns true if there are no errors recorded.
856    pub fn is_empty(&self) -> bool {
857        self.unknown_scripts.is_empty()
858            && self.wrong_script_types.is_empty()
859            && self.list_scripts_using_run_filters.is_empty()
860    }
861}
862
863/// An error which indicates that a profile was requested but not known to nextest.
864#[derive(Clone, Debug, Error)]
865#[error("profile `{profile}` not found (known profiles: {})", .all_profiles.join(", "))]
866pub struct ProfileNotFound {
867    profile: String,
868    all_profiles: Vec<String>,
869}
870
871impl ProfileNotFound {
872    pub(crate) fn new(
873        profile: impl Into<String>,
874        all_profiles: impl IntoIterator<Item = impl Into<String>>,
875    ) -> Self {
876        let mut all_profiles: Vec<_> = all_profiles.into_iter().map(|s| s.into()).collect();
877        all_profiles.sort_unstable();
878        Self {
879            profile: profile.into(),
880            all_profiles,
881        }
882    }
883}
884
885/// An identifier is invalid.
886#[derive(Clone, Debug, Error, Eq, PartialEq)]
887pub enum InvalidIdentifier {
888    /// The identifier is empty.
889    #[error("identifier is empty")]
890    Empty,
891
892    /// The identifier is not in the correct Unicode format.
893    #[error("invalid identifier `{0}`")]
894    InvalidXid(SmolStr),
895
896    /// This tool identifier doesn't match the expected pattern.
897    #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
898    ToolIdentifierInvalidFormat(SmolStr),
899
900    /// One of the components of this tool identifier is empty.
901    #[error("tool identifier has empty component: `{0}`")]
902    ToolComponentEmpty(SmolStr),
903
904    /// The tool identifier is not in the correct Unicode format.
905    #[error("invalid tool identifier `{0}`")]
906    ToolIdentifierInvalidXid(SmolStr),
907}
908
909/// A tool name is invalid.
910#[derive(Clone, Debug, Error, Eq, PartialEq)]
911pub enum InvalidToolName {
912    /// The tool name is empty.
913    #[error("tool name is empty")]
914    Empty,
915
916    /// The tool name is not in the correct Unicode format.
917    #[error("invalid tool name `{0}`")]
918    InvalidXid(SmolStr),
919
920    /// The tool name starts with "@tool", which is reserved for tool identifiers.
921    #[error("tool name cannot start with \"@tool\": `{0}`")]
922    StartsWithToolPrefix(SmolStr),
923}
924
925/// The name of a test group is invalid (not a valid identifier).
926#[derive(Clone, Debug, Error)]
927#[error("invalid custom test group name: {0}")]
928pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
929
930/// The name of a configuration script is invalid (not a valid identifier).
931#[derive(Clone, Debug, Error)]
932#[error("invalid configuration script name: {0}")]
933pub struct InvalidConfigScriptName(pub InvalidIdentifier);
934
935/// Error returned while parsing a [`ToolConfigFile`](crate::config::core::ToolConfigFile) value.
936#[derive(Clone, Debug, Error, PartialEq, Eq)]
937pub enum ToolConfigFileParseError {
938    #[error(
939        "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
940    )]
941    /// The input was not in the format "tool:path".
942    InvalidFormat {
943        /// The input that failed to parse.
944        input: String,
945    },
946
947    /// The tool name was invalid.
948    #[error("tool-config-file has invalid tool name: {input}")]
949    InvalidToolName {
950        /// The input that failed to parse.
951        input: String,
952
953        /// The error that occurred.
954        #[source]
955        error: InvalidToolName,
956    },
957
958    /// The config file path was empty.
959    #[error("tool-config-file has empty config file path: {input}")]
960    EmptyConfigFile {
961        /// The input that failed to parse.
962        input: String,
963    },
964
965    /// The config file was not an absolute path.
966    #[error("tool-config-file is not an absolute path: {config_file}")]
967    ConfigFileNotAbsolute {
968        /// The file name that wasn't absolute.
969        config_file: Utf8PathBuf,
970    },
971}
972
973/// Errors that can occur while loading user config.
974#[derive(Debug, Error)]
975#[non_exhaustive]
976pub enum UserConfigError {
977    /// The user config file specified via `--user-config-file` or
978    /// `NEXTEST_USER_CONFIG_FILE` does not exist.
979    #[error("user config file not found at {path}")]
980    FileNotFound {
981        /// The path that was specified.
982        path: Utf8PathBuf,
983    },
984
985    /// Failed to read the user config file.
986    #[error("failed to read user config at {path}")]
987    Read {
988        /// The path to the config file.
989        path: Utf8PathBuf,
990        /// The underlying I/O error.
991        #[source]
992        error: std::io::Error,
993    },
994
995    /// Failed to parse the user config file.
996    #[error("failed to parse user config at {path}")]
997    Parse {
998        /// The path to the config file.
999        path: Utf8PathBuf,
1000        /// The underlying TOML parse error.
1001        #[source]
1002        error: toml::de::Error,
1003    },
1004
1005    /// The user config path contains non-UTF-8 characters.
1006    #[error("user config path contains non-UTF-8 characters")]
1007    NonUtf8Path {
1008        /// The underlying error from path conversion.
1009        #[source]
1010        error: FromPathBufError,
1011    },
1012
1013    /// Failed to compile a platform spec in an override.
1014    #[error(
1015        "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
1016    )]
1017    OverridePlatformSpec {
1018        /// The path to the config file.
1019        path: Utf8PathBuf,
1020        /// The index of the override in the array.
1021        index: usize,
1022        /// The underlying target-spec error.
1023        #[source]
1024        error: Box<target_spec::Error>,
1025    },
1026}
1027
1028/// Error returned while parsing a [`MaxFail`](crate::config::elements::MaxFail) input.
1029#[derive(Clone, Debug, Error)]
1030#[error("unrecognized value for max-fail: {reason}")]
1031pub struct MaxFailParseError {
1032    /// The reason parsing failed.
1033    pub reason: String,
1034}
1035
1036impl MaxFailParseError {
1037    pub(crate) fn new(reason: impl Into<String>) -> Self {
1038        Self {
1039            reason: reason.into(),
1040        }
1041    }
1042}
1043
1044/// Error returned while parsing a [`StressCount`](crate::runner::StressCount) input.
1045#[derive(Clone, Debug, Error)]
1046#[error(
1047    "unrecognized value for stress-count: {input}\n\
1048     (hint: expected either a positive integer or \"infinite\")"
1049)]
1050pub struct StressCountParseError {
1051    /// The input that failed to parse.
1052    pub input: String,
1053}
1054
1055impl StressCountParseError {
1056    pub(crate) fn new(input: impl Into<String>) -> Self {
1057        Self {
1058            input: input.into(),
1059        }
1060    }
1061}
1062
1063/// An error that occurred while parsing a debugger command.
1064#[derive(Clone, Debug, Error)]
1065#[non_exhaustive]
1066pub enum DebuggerCommandParseError {
1067    /// The command string could not be parsed as shell words.
1068    #[error(transparent)]
1069    ShellWordsParse(shell_words::ParseError),
1070
1071    /// The command was empty.
1072    #[error("debugger command cannot be empty")]
1073    EmptyCommand,
1074}
1075
1076/// An error that occurred while parsing a tracer command.
1077#[derive(Clone, Debug, Error)]
1078#[non_exhaustive]
1079pub enum TracerCommandParseError {
1080    /// The command string could not be parsed as shell words.
1081    #[error(transparent)]
1082    ShellWordsParse(shell_words::ParseError),
1083
1084    /// The command was empty.
1085    #[error("tracer command cannot be empty")]
1086    EmptyCommand,
1087}
1088
1089/// Error returned while parsing a [`TestThreads`](crate::config::elements::TestThreads) value.
1090#[derive(Clone, Debug, Error)]
1091#[error(
1092    "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
1093)]
1094pub struct TestThreadsParseError {
1095    /// The input that failed to parse.
1096    pub input: String,
1097}
1098
1099impl TestThreadsParseError {
1100    pub(crate) fn new(input: impl Into<String>) -> Self {
1101        Self {
1102            input: input.into(),
1103        }
1104    }
1105}
1106
1107/// An error that occurs while parsing a
1108/// [`PartitionerBuilder`](crate::partition::PartitionerBuilder) input.
1109#[derive(Clone, Debug, Error)]
1110pub struct PartitionerBuilderParseError {
1111    expected_format: Option<&'static str>,
1112    message: Cow<'static, str>,
1113}
1114
1115impl PartitionerBuilderParseError {
1116    pub(crate) fn new(
1117        expected_format: Option<&'static str>,
1118        message: impl Into<Cow<'static, str>>,
1119    ) -> Self {
1120        Self {
1121            expected_format,
1122            message: message.into(),
1123        }
1124    }
1125}
1126
1127impl fmt::Display for PartitionerBuilderParseError {
1128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1129        match self.expected_format {
1130            Some(format) => {
1131                write!(
1132                    f,
1133                    "partition must be in the format \"{}\":\n{}",
1134                    format, self.message
1135                )
1136            }
1137            None => write!(f, "{}", self.message),
1138        }
1139    }
1140}
1141
1142/// An error that occurs while building a
1143/// [`TestFilter`](crate::test_filter::TestFilter).
1144#[derive(Clone, Debug, Error)]
1145pub enum TestFilterBuildError {
1146    /// An error that occurred while constructing test filters.
1147    #[error("error constructing test filters")]
1148    Construct {
1149        /// The underlying error.
1150        #[from]
1151        error: aho_corasick::BuildError,
1152    },
1153}
1154
1155/// An error occurred in [`PathMapper::new`](crate::reuse_build::PathMapper::new).
1156#[derive(Debug, Error)]
1157pub enum PathMapperConstructError {
1158    /// An error occurred while canonicalizing a directory.
1159    #[error("{kind} `{input}` failed to canonicalize")]
1160    Canonicalization {
1161        /// The directory that failed to be canonicalized.
1162        kind: PathMapperConstructKind,
1163
1164        /// The input provided.
1165        input: Utf8PathBuf,
1166
1167        /// The error that occurred.
1168        #[source]
1169        err: std::io::Error,
1170    },
1171    /// The canonicalized path isn't valid UTF-8.
1172    #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
1173    NonUtf8Path {
1174        /// The directory that failed to be canonicalized.
1175        kind: PathMapperConstructKind,
1176
1177        /// The input provided.
1178        input: Utf8PathBuf,
1179
1180        /// The underlying error.
1181        #[source]
1182        err: FromPathBufError,
1183    },
1184    /// A provided input is not a directory.
1185    #[error("{kind} `{canonicalized_path}` is not a directory")]
1186    NotADirectory {
1187        /// The directory that failed to be canonicalized.
1188        kind: PathMapperConstructKind,
1189
1190        /// The input provided.
1191        input: Utf8PathBuf,
1192
1193        /// The canonicalized path that wasn't a directory.
1194        canonicalized_path: Utf8PathBuf,
1195    },
1196}
1197
1198impl PathMapperConstructError {
1199    /// The kind of directory.
1200    pub fn kind(&self) -> PathMapperConstructKind {
1201        match self {
1202            Self::Canonicalization { kind, .. }
1203            | Self::NonUtf8Path { kind, .. }
1204            | Self::NotADirectory { kind, .. } => *kind,
1205        }
1206    }
1207
1208    /// The input path that failed.
1209    pub fn input(&self) -> &Utf8Path {
1210        match self {
1211            Self::Canonicalization { input, .. }
1212            | Self::NonUtf8Path { input, .. }
1213            | Self::NotADirectory { input, .. } => input,
1214        }
1215    }
1216}
1217
1218/// The kind of directory that failed to be read in
1219/// [`PathMapper::new`](crate::reuse_build::PathMapper::new).
1220///
1221/// Returned as part of [`PathMapperConstructError`].
1222#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1223pub enum PathMapperConstructKind {
1224    /// The workspace root.
1225    WorkspaceRoot,
1226
1227    /// The target directory.
1228    TargetDir,
1229
1230    /// The build directory.
1231    BuildDir,
1232}
1233
1234impl fmt::Display for PathMapperConstructKind {
1235    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1236        match self {
1237            Self::WorkspaceRoot => write!(f, "remapped workspace root"),
1238            Self::TargetDir => write!(f, "remapped target directory"),
1239            Self::BuildDir => write!(f, "remapped build directory"),
1240        }
1241    }
1242}
1243
1244/// An error that occurs while parsing Rust build metadata from a summary.
1245#[derive(Debug, Error)]
1246pub enum RustBuildMetaParseError {
1247    /// An error occurred while deserializing the platform.
1248    #[error("error deserializing platform from build metadata")]
1249    PlatformDeserializeError(#[from] target_spec::Error),
1250
1251    /// The host platform could not be determined.
1252    #[error("the host platform could not be determined")]
1253    DetectBuildTargetError(#[source] target_spec::Error),
1254
1255    /// The build metadata includes features unsupported.
1256    #[error("unsupported features in the build metadata: {message}")]
1257    Unsupported {
1258        /// The detailed error message.
1259        message: String,
1260    },
1261}
1262
1263/// Error returned when a user-supplied format version fails to be parsed to a
1264/// valid and supported version.
1265#[derive(Clone, Debug, thiserror::Error)]
1266#[error("invalid format version: {input}")]
1267pub struct FormatVersionError {
1268    /// The input that failed to parse.
1269    pub input: String,
1270    /// The underlying error.
1271    #[source]
1272    pub error: FormatVersionErrorInner,
1273}
1274
1275/// The different errors that can occur when parsing and validating a format version.
1276#[derive(Clone, Debug, thiserror::Error)]
1277pub enum FormatVersionErrorInner {
1278    /// The input did not have a valid syntax.
1279    #[error("expected format version in form of `{expected}`")]
1280    InvalidFormat {
1281        /// The expected pseudo format.
1282        expected: &'static str,
1283    },
1284    /// A decimal integer was expected but could not be parsed.
1285    #[error("version component `{which}` could not be parsed as an integer")]
1286    InvalidInteger {
1287        /// Which component was invalid.
1288        which: &'static str,
1289        /// The parse failure.
1290        #[source]
1291        err: std::num::ParseIntError,
1292    },
1293    /// The version component was not within the expected range.
1294    #[error("version component `{which}` value {value} is out of range {range:?}")]
1295    InvalidValue {
1296        /// The component which was out of range.
1297        which: &'static str,
1298        /// The value that was parsed.
1299        value: u8,
1300        /// The range of valid values for the component.
1301        range: std::ops::Range<u8>,
1302    },
1303}
1304
1305/// An error that occurs in [`BinaryList::from_messages`](crate::list::BinaryList::from_messages),
1306/// [`BinaryListBuilder`](crate::list::BinaryListBuilder), or
1307/// [`RustTestArtifact::from_binary_list`](crate::list::RustTestArtifact::from_binary_list).
1308#[derive(Debug, Error)]
1309#[non_exhaustive]
1310pub enum FromMessagesError {
1311    /// An error occurred while reading Cargo's JSON messages.
1312    #[error("error reading Cargo JSON messages")]
1313    ReadMessages(#[source] std::io::Error),
1314
1315    /// An error occurred while querying the package graph.
1316    #[error("error querying package graph")]
1317    PackageGraph(#[source] guppy::Error),
1318
1319    /// A target in the package graph was missing `kind` information.
1320    #[error("missing kind for target {binary_name} in package {package_name}")]
1321    MissingTargetKind {
1322        /// The name of the malformed package.
1323        package_name: String,
1324        /// The name of the malformed target within the package.
1325        binary_name: String,
1326    },
1327}
1328
1329/// An error that occurs while parsing test list output.
1330#[derive(Debug, Error)]
1331#[non_exhaustive]
1332pub enum CreateTestListError {
1333    /// The proposed cwd for a process is not a directory.
1334    #[error(
1335        "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
1336         (hint: ensure project source is available at this location)"
1337    )]
1338    CwdIsNotDir {
1339        /// The binary ID for which the current directory wasn't found.
1340        binary_id: RustBinaryId,
1341
1342        /// The current directory that wasn't found.
1343        cwd: Utf8PathBuf,
1344    },
1345
1346    /// Running a command to gather the list of tests failed to execute.
1347    #[error(
1348        "for `{binary_id}`, running command `{}` failed to execute",
1349        shell_words::join(command)
1350    )]
1351    CommandExecFail {
1352        /// The binary ID for which gathering the list of tests failed.
1353        binary_id: RustBinaryId,
1354
1355        /// The command that was run.
1356        command: Vec<String>,
1357
1358        /// The underlying error.
1359        #[source]
1360        error: std::io::Error,
1361    },
1362
1363    /// Running a command to gather the list of tests failed failed with a non-zero exit code.
1364    #[error(
1365        "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1366        shell_words::join(command),
1367        display_exited_with(*exit_status),
1368        String::from_utf8_lossy(stdout),
1369        String::from_utf8_lossy(stderr),
1370    )]
1371    CommandFail {
1372        /// The binary ID for which gathering the list of tests failed.
1373        binary_id: RustBinaryId,
1374
1375        /// The command that was run.
1376        command: Vec<String>,
1377
1378        /// The exit status with which the command failed.
1379        exit_status: ExitStatus,
1380
1381        /// Standard output for the command.
1382        stdout: Vec<u8>,
1383
1384        /// Standard error for the command.
1385        stderr: Vec<u8>,
1386    },
1387
1388    /// Running a command to gather the list of tests produced a non-UTF-8 standard output.
1389    #[error(
1390        "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1391        shell_words::join(command),
1392        String::from_utf8_lossy(stdout),
1393        String::from_utf8_lossy(stderr)
1394    )]
1395    CommandNonUtf8 {
1396        /// The binary ID for which gathering the list of tests failed.
1397        binary_id: RustBinaryId,
1398
1399        /// The command that was run.
1400        command: Vec<String>,
1401
1402        /// Standard output for the command.
1403        stdout: Vec<u8>,
1404
1405        /// Standard error for the command.
1406        stderr: Vec<u8>,
1407    },
1408
1409    /// An error occurred while parsing a line in the test output.
1410    #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1411    ParseLine {
1412        /// The binary ID for which parsing the list of tests failed.
1413        binary_id: RustBinaryId,
1414
1415        /// A descriptive message.
1416        message: Cow<'static, str>,
1417
1418        /// The full output.
1419        full_output: String,
1420    },
1421
1422    /// An error occurred while joining paths for dynamic libraries.
1423    #[error(
1424        "error joining dynamic library paths for {}: [{}]",
1425        dylib_path_envvar(),
1426        itertools::join(.new_paths, ", ")
1427    )]
1428    DylibJoinPaths {
1429        /// New paths attempted to be added to the dynamic library environment variable.
1430        new_paths: Vec<Utf8PathBuf>,
1431
1432        /// The underlying error.
1433        #[source]
1434        error: JoinPathsError,
1435    },
1436
1437    /// Creating a Tokio runtime failed.
1438    #[error("error creating Tokio runtime")]
1439    TokioRuntimeCreate(#[source] std::io::Error),
1440}
1441
1442impl CreateTestListError {
1443    pub(crate) fn parse_line(
1444        binary_id: RustBinaryId,
1445        message: impl Into<Cow<'static, str>>,
1446        full_output: impl Into<String>,
1447    ) -> Self {
1448        Self::ParseLine {
1449            binary_id,
1450            message: message.into(),
1451            full_output: full_output.into(),
1452        }
1453    }
1454
1455    pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1456        Self::DylibJoinPaths { new_paths, error }
1457    }
1458}
1459
1460/// An error that occurs while writing list output.
1461#[derive(Debug, Error)]
1462#[non_exhaustive]
1463pub enum WriteTestListError {
1464    /// An error occurred while writing the list to the provided output.
1465    #[error("error writing to output")]
1466    Io(#[source] std::io::Error),
1467
1468    /// An error occurred while serializing JSON, or while writing it to the provided output.
1469    #[error("error serializing to JSON")]
1470    Json(#[source] serde_json::Error),
1471}
1472
1473/// An error occurred while configuring handles.
1474///
1475/// Only relevant on Windows.
1476#[derive(Debug, Error)]
1477pub enum ConfigureHandleInheritanceError {
1478    /// An error occurred. This can only happen on Windows.
1479    #[cfg(windows)]
1480    #[error("error configuring handle inheritance")]
1481    WindowsError(#[from] std::io::Error),
1482}
1483
1484/// An error that occurs while building the test runner.
1485#[derive(Debug, Error)]
1486#[non_exhaustive]
1487pub enum TestRunnerBuildError {
1488    /// An error occurred while creating the Tokio runtime.
1489    #[error("error creating Tokio runtime")]
1490    TokioRuntimeCreate(#[source] std::io::Error),
1491
1492    /// An error occurred while setting up signals.
1493    #[error("error setting up signals")]
1494    SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1495}
1496
1497/// Errors that occurred while managing test runner Tokio tasks.
1498#[derive(Debug, Error)]
1499pub struct TestRunnerExecuteErrors<E> {
1500    /// An error that occurred while reporting results to the reporter callback.
1501    pub report_error: Option<E>,
1502
1503    /// Join errors (typically panics) that occurred while running the test
1504    /// runner.
1505    pub join_errors: Vec<tokio::task::JoinError>,
1506}
1507
1508impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1510        if let Some(report_error) = &self.report_error {
1511            write!(f, "error reporting results: {report_error}")?;
1512        }
1513
1514        if !self.join_errors.is_empty() {
1515            if self.report_error.is_some() {
1516                write!(f, "; ")?;
1517            }
1518
1519            write!(f, "errors joining tasks: ")?;
1520
1521            for (i, join_error) in self.join_errors.iter().enumerate() {
1522                if i > 0 {
1523                    write!(f, ", ")?;
1524                }
1525
1526                write!(f, "{join_error}")?;
1527            }
1528        }
1529
1530        Ok(())
1531    }
1532}
1533
1534/// Represents an unknown archive format.
1535///
1536/// Returned by [`ArchiveFormat::autodetect`].
1537#[derive(Debug, Error)]
1538#[error(
1539    "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1540    supported_extensions()
1541)]
1542pub struct UnknownArchiveFormat {
1543    /// The name of the archive file without any leading components.
1544    pub file_name: String,
1545}
1546
1547fn supported_extensions() -> String {
1548    ArchiveFormat::SUPPORTED_FORMATS
1549        .iter()
1550        .map(|(extension, _)| *extension)
1551        .join(", ")
1552}
1553
1554/// An error that occurs while archiving data.
1555#[derive(Debug, Error)]
1556#[non_exhaustive]
1557pub enum ArchiveCreateError {
1558    /// An error occurred while creating the binary list to be written.
1559    #[error("error creating binary list")]
1560    CreateBinaryList(#[source] WriteTestListError),
1561
1562    /// An extra path was missing.
1563    #[error("extra path `{}` not found", .redactor.redact_path(path))]
1564    MissingExtraPath {
1565        /// The path that was missing.
1566        path: Utf8PathBuf,
1567
1568        /// A redactor for the path.
1569        ///
1570        /// (This should eventually move to being a field for a wrapper struct, but it's okay for
1571        /// now.)
1572        redactor: Redactor,
1573    },
1574
1575    /// An error occurred while reading data from a file on disk.
1576    #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1577    InputFileRead {
1578        /// The step that the archive errored at.
1579        step: ArchiveStep,
1580
1581        /// The name of the file that could not be read.
1582        path: Utf8PathBuf,
1583
1584        /// Whether this is a directory. `None` means the status was unknown.
1585        is_dir: Option<bool>,
1586
1587        /// The error that occurred.
1588        #[source]
1589        error: std::io::Error,
1590    },
1591
1592    /// An error occurred while reading entries from a directory on disk.
1593    #[error("error reading directory entry from `{path}")]
1594    DirEntryRead {
1595        /// The name of the directory from which entries couldn't be read.
1596        path: Utf8PathBuf,
1597
1598        /// The error that occurred.
1599        #[source]
1600        error: std::io::Error,
1601    },
1602
1603    /// An error occurred while writing data to the output file.
1604    #[error("error writing to archive")]
1605    OutputArchiveIo(#[source] std::io::Error),
1606
1607    /// An error occurred in the reporter.
1608    #[error("error reporting archive status")]
1609    ReporterIo(#[source] std::io::Error),
1610}
1611
1612fn kind_str(is_dir: Option<bool>) -> &'static str {
1613    match is_dir {
1614        Some(true) => "directory",
1615        Some(false) => "file",
1616        None => "path",
1617    }
1618}
1619
1620/// An error occurred while materializing a metadata path.
1621#[derive(Debug, Error)]
1622pub enum MetadataMaterializeError {
1623    /// An I/O error occurred while reading the metadata file.
1624    #[error("I/O error reading metadata file `{path}`")]
1625    Read {
1626        /// The path that was being read.
1627        path: Utf8PathBuf,
1628
1629        /// The error that occurred.
1630        #[source]
1631        error: std::io::Error,
1632    },
1633
1634    /// A JSON deserialization error occurred while reading the metadata file.
1635    #[error("error deserializing metadata file `{path}`")]
1636    Deserialize {
1637        /// The path that was being read.
1638        path: Utf8PathBuf,
1639
1640        /// The error that occurred.
1641        #[source]
1642        error: serde_json::Error,
1643    },
1644
1645    /// An error occurred while parsing Rust build metadata.
1646    #[error("error parsing Rust build metadata from `{path}`")]
1647    RustBuildMeta {
1648        /// The path that was deserialized.
1649        path: Utf8PathBuf,
1650
1651        /// The error that occurred.
1652        #[source]
1653        error: Box<RustBuildMetaParseError>,
1654    },
1655
1656    /// An error occurred converting data into a `PackageGraph`.
1657    #[error("error building package graph from `{path}`")]
1658    PackageGraphConstruct {
1659        /// The path that was deserialized.
1660        path: Utf8PathBuf,
1661
1662        /// The error that occurred.
1663        #[source]
1664        error: Box<guppy::Error>,
1665    },
1666}
1667
1668/// An error occurred while reading a file.
1669///
1670/// Returned as part of both [`ArchiveCreateError`] and [`ArchiveExtractError`].
1671#[derive(Debug, Error)]
1672#[non_exhaustive]
1673pub enum ArchiveReadError {
1674    /// An I/O error occurred while reading the archive.
1675    #[error("I/O error reading archive")]
1676    Io(#[source] std::io::Error),
1677
1678    /// A path wasn't valid UTF-8.
1679    #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1680    NonUtf8Path(Vec<u8>),
1681
1682    /// A file path within the archive didn't begin with "target/".
1683    #[error("path in archive `{0}` doesn't start with `target/`")]
1684    NoTargetPrefix(Utf8PathBuf),
1685
1686    /// A file path within the archive had an invalid component within it.
1687    #[error("path in archive `{path}` contains an invalid component `{component}`")]
1688    InvalidComponent {
1689        /// The path that had an invalid component.
1690        path: Utf8PathBuf,
1691
1692        /// The invalid component.
1693        component: String,
1694    },
1695
1696    /// An error occurred while reading a checksum.
1697    #[error("corrupted archive: checksum read error for path `{path}`")]
1698    ChecksumRead {
1699        /// The path for which there was a checksum read error.
1700        path: Utf8PathBuf,
1701
1702        /// The error that occurred.
1703        #[source]
1704        error: std::io::Error,
1705    },
1706
1707    /// An entry had an invalid checksum.
1708    #[error("corrupted archive: invalid checksum for path `{path}`")]
1709    InvalidChecksum {
1710        /// The path that had an invalid checksum.
1711        path: Utf8PathBuf,
1712
1713        /// The expected checksum.
1714        expected: u32,
1715
1716        /// The actual checksum.
1717        actual: u32,
1718    },
1719
1720    /// A metadata file wasn't found.
1721    #[error("metadata file `{0}` not found in archive")]
1722    MetadataFileNotFound(&'static Utf8Path),
1723
1724    /// An error occurred while deserializing a metadata file.
1725    #[error("error deserializing metadata file `{path}` in archive")]
1726    MetadataDeserializeError {
1727        /// The name of the metadata file.
1728        path: &'static Utf8Path,
1729
1730        /// The deserialize error.
1731        #[source]
1732        error: serde_json::Error,
1733    },
1734
1735    /// An error occurred while building a `PackageGraph`.
1736    #[error("error building package graph from `{path}` in archive")]
1737    PackageGraphConstructError {
1738        /// The name of the metadata file.
1739        path: &'static Utf8Path,
1740
1741        /// The error.
1742        #[source]
1743        error: Box<guppy::Error>,
1744    },
1745}
1746
1747/// An error occurred while extracting a file.
1748///
1749/// Returned by [`extract_archive`](crate::reuse_build::ReuseBuildInfo::extract_archive).
1750#[derive(Debug, Error)]
1751#[non_exhaustive]
1752pub enum ArchiveExtractError {
1753    /// An error occurred while creating a temporary directory.
1754    #[error("error creating temporary directory")]
1755    TempDirCreate(#[source] std::io::Error),
1756
1757    /// An error occurred while canonicalizing the destination directory.
1758    #[error("error canonicalizing destination directory `{dir}`")]
1759    DestDirCanonicalization {
1760        /// The directory that failed to canonicalize.
1761        dir: Utf8PathBuf,
1762
1763        /// The error that occurred.
1764        #[source]
1765        error: std::io::Error,
1766    },
1767
1768    /// The destination already exists and `--overwrite` was not passed in.
1769    #[error("destination `{0}` already exists")]
1770    DestinationExists(Utf8PathBuf),
1771
1772    /// An error occurred while reading the archive.
1773    #[error("error reading archive")]
1774    Read(#[source] ArchiveReadError),
1775
1776    /// An error occurred while deserializing Rust build metadata.
1777    #[error("error deserializing Rust build metadata")]
1778    RustBuildMeta(#[from] RustBuildMetaParseError),
1779
1780    /// An error occurred while writing out a file to the destination directory.
1781    #[error("error writing file `{path}` to disk")]
1782    WriteFile {
1783        /// The path that we couldn't write out.
1784        path: Utf8PathBuf,
1785
1786        /// The error that occurred.
1787        #[source]
1788        error: std::io::Error,
1789    },
1790
1791    /// An error occurred while reporting the extraction status.
1792    #[error("error reporting extract status")]
1793    ReporterIo(std::io::Error),
1794}
1795
1796/// An error that occurs while writing an event.
1797#[derive(Debug, Error)]
1798#[non_exhaustive]
1799pub enum WriteEventError {
1800    /// An error occurred while writing the event to the provided output.
1801    #[error("error writing to output")]
1802    Io(#[source] std::io::Error),
1803
1804    /// An error occurred while operating on the file system.
1805    #[error("error operating on path {file}")]
1806    Fs {
1807        /// The file being operated on.
1808        file: Utf8PathBuf,
1809
1810        /// The underlying IO error.
1811        #[source]
1812        error: std::io::Error,
1813    },
1814
1815    /// An error occurred while producing JUnit XML.
1816    #[error("error writing JUnit output to {file}")]
1817    Junit {
1818        /// The output file.
1819        file: Utf8PathBuf,
1820
1821        /// The underlying error.
1822        #[source]
1823        error: quick_junit::SerializeError,
1824    },
1825}
1826
1827/// An error occurred while constructing a [`CargoConfigs`](crate::cargo_config::CargoConfigs)
1828/// instance.
1829#[derive(Debug, Error)]
1830#[non_exhaustive]
1831pub enum CargoConfigError {
1832    /// Failed to retrieve the current directory.
1833    #[error("failed to retrieve current directory")]
1834    GetCurrentDir(#[source] std::io::Error),
1835
1836    /// The current directory was invalid UTF-8.
1837    #[error("current directory is invalid UTF-8")]
1838    CurrentDirInvalidUtf8(#[source] FromPathBufError),
1839
1840    /// Parsing a CLI config option failed.
1841    #[error("failed to parse --config argument `{config_str}` as TOML")]
1842    CliConfigParseError {
1843        /// The CLI config option.
1844        config_str: String,
1845
1846        /// The error that occurred trying to parse the config.
1847        #[source]
1848        error: toml_edit::TomlError,
1849    },
1850
1851    /// Deserializing a CLI config option into domain types failed.
1852    #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1853    CliConfigDeError {
1854        /// The CLI config option.
1855        config_str: String,
1856
1857        /// The error that occurred trying to deserialize the config.
1858        #[source]
1859        error: toml_edit::de::Error,
1860    },
1861
1862    /// A CLI config option is not in the dotted key format.
1863    #[error(
1864        "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1865    )]
1866    InvalidCliConfig {
1867        /// The CLI config option.
1868        config_str: String,
1869
1870        /// The reason why this Cargo CLI config is invalid.
1871        #[source]
1872        reason: InvalidCargoCliConfigReason,
1873    },
1874
1875    /// A non-UTF-8 path was encountered.
1876    #[error("non-UTF-8 path encountered")]
1877    NonUtf8Path(#[source] FromPathBufError),
1878
1879    /// Failed to retrieve the Cargo home directory.
1880    #[error("failed to retrieve the Cargo home directory")]
1881    GetCargoHome(#[source] std::io::Error),
1882
1883    /// Failed to canonicalize a path
1884    #[error("failed to canonicalize path `{path}")]
1885    FailedPathCanonicalization {
1886        /// The path that failed to canonicalize
1887        path: Utf8PathBuf,
1888
1889        /// The error the occurred during canonicalization
1890        #[source]
1891        error: std::io::Error,
1892    },
1893
1894    /// Failed to read config file
1895    #[error("failed to read config at `{path}`")]
1896    ConfigReadError {
1897        /// The path of the config file
1898        path: Utf8PathBuf,
1899
1900        /// The error that occurred trying to read the config file
1901        #[source]
1902        error: std::io::Error,
1903    },
1904
1905    /// Failed to deserialize config file
1906    #[error(transparent)]
1907    ConfigParseError(#[from] Box<CargoConfigParseError>),
1908}
1909
1910/// Failed to deserialize config file
1911///
1912/// We introduce this extra indirection, because of the `clippy::result_large_err` rule on Windows.
1913#[derive(Debug, Error)]
1914#[error("failed to parse config at `{path}`")]
1915pub struct CargoConfigParseError {
1916    /// The path of the config file
1917    pub path: Utf8PathBuf,
1918
1919    /// The error that occurred trying to deserialize the config file
1920    #[source]
1921    pub error: toml::de::Error,
1922}
1923
1924/// The reason an invalid CLI config failed.
1925///
1926/// Part of [`CargoConfigError::InvalidCliConfig`].
1927#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1928#[non_exhaustive]
1929pub enum InvalidCargoCliConfigReason {
1930    /// The argument is not a TOML dotted key expression.
1931    #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1932    NotDottedKv,
1933
1934    /// The argument includes non-whitespace decoration.
1935    #[error("includes non-whitespace decoration")]
1936    IncludesNonWhitespaceDecoration,
1937
1938    /// The argument sets a value to an inline table.
1939    #[error("sets a value to an inline table, which is not accepted")]
1940    SetsValueToInlineTable,
1941
1942    /// The argument sets a value to an array of tables.
1943    #[error("sets a value to an array of tables, which is not accepted")]
1944    SetsValueToArrayOfTables,
1945
1946    /// The argument doesn't provide a value.
1947    #[error("doesn't provide a value")]
1948    DoesntProvideValue,
1949}
1950
1951/// The host platform couldn't be detected.
1952#[derive(Debug, Error)]
1953pub enum HostPlatformDetectError {
1954    /// Spawning `rustc -vV` failed, and detecting the build target failed as
1955    /// well.
1956    #[error(
1957        "error spawning `rustc -vV`, and detecting the build \
1958         target failed as well\n\
1959         - rustc spawn error: {}\n\
1960         - build target error: {}\n",
1961        DisplayErrorChain::new_with_initial_indent("  ", error),
1962        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
1963    )]
1964    RustcVvSpawnError {
1965        /// The error.
1966        error: std::io::Error,
1967
1968        /// The error that occurred while detecting the build target.
1969        build_target_error: Box<target_spec::Error>,
1970    },
1971
1972    /// `rustc -vV` exited with a non-zero code, and detecting the build target
1973    /// failed as well.
1974    #[error(
1975        "`rustc -vV` failed with {}, and detecting the \
1976         build target failed as well\n\
1977         - `rustc -vV` stdout:\n{}\n\
1978         - `rustc -vV` stderr:\n{}\n\
1979         - build target error:\n{}\n",
1980        status,
1981        DisplayIndented { item: String::from_utf8_lossy(stdout), indent: "  " },
1982        DisplayIndented { item: String::from_utf8_lossy(stderr), indent: "  " },
1983        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
1984    )]
1985    RustcVvFailed {
1986        /// The status.
1987        status: ExitStatus,
1988
1989        /// The standard output from `rustc -vV`.
1990        stdout: Vec<u8>,
1991
1992        /// The standard error from `rustc -vV`.
1993        stderr: Vec<u8>,
1994
1995        /// The error that occurred while detecting the build target.
1996        build_target_error: Box<target_spec::Error>,
1997    },
1998
1999    /// Parsing the host platform failed, and detecting the build target failed
2000    /// as well.
2001    #[error(
2002        "parsing `rustc -vV` output failed, and detecting the build target \
2003         failed as well\n\
2004         - host platform error:\n{}\n\
2005         - build target error:\n{}\n",
2006        DisplayErrorChain::new_with_initial_indent("  ", host_platform_error),
2007        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
2008    )]
2009    HostPlatformParseError {
2010        /// The error that occurred while parsing the host platform.
2011        host_platform_error: Box<target_spec::Error>,
2012
2013        /// The error that occurred while detecting the build target.
2014        build_target_error: Box<target_spec::Error>,
2015    },
2016
2017    /// Test-only code: `rustc -vV` was not queried, and detecting the build
2018    /// target failed as well.
2019    #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
2020    BuildTargetError {
2021        /// The error that occurred while detecting the build target.
2022        #[source]
2023        build_target_error: Box<target_spec::Error>,
2024    },
2025}
2026
2027/// An error occurred while determining the cross-compiling target triple.
2028#[derive(Debug, Error)]
2029pub enum TargetTripleError {
2030    /// The environment variable contained non-utf8 content
2031    #[error(
2032        "environment variable '{}' contained non-UTF-8 data",
2033        TargetTriple::CARGO_BUILD_TARGET_ENV
2034    )]
2035    InvalidEnvironmentVar,
2036
2037    /// An error occurred while deserializing the platform.
2038    #[error("error deserializing target triple from {source}")]
2039    TargetSpecError {
2040        /// The source from which the triple couldn't be parsed.
2041        source: TargetTripleSource,
2042
2043        /// The error that occurred parsing the triple.
2044        #[source]
2045        error: target_spec::Error,
2046    },
2047
2048    /// For a custom platform, reading the target path failed.
2049    #[error("target path `{path}` is not a valid file")]
2050    TargetPathReadError {
2051        /// The source from which the triple couldn't be parsed.
2052        source: TargetTripleSource,
2053
2054        /// The path that we tried to read.
2055        path: Utf8PathBuf,
2056
2057        /// The error that occurred parsing the triple.
2058        #[source]
2059        error: std::io::Error,
2060    },
2061
2062    /// Failed to create a temporary directory for a custom platform.
2063    #[error(
2064        "for custom platform obtained from {source}, \
2065         failed to create temporary directory for custom platform"
2066    )]
2067    CustomPlatformTempDirError {
2068        /// The source of the target triple.
2069        source: TargetTripleSource,
2070
2071        /// The error that occurred during the create.
2072        #[source]
2073        error: std::io::Error,
2074    },
2075
2076    /// Failed to write a custom platform to disk.
2077    #[error(
2078        "for custom platform obtained from {source}, \
2079         failed to write JSON to temporary path `{path}`"
2080    )]
2081    CustomPlatformWriteError {
2082        /// The source of the target triple.
2083        source: TargetTripleSource,
2084
2085        /// The path that we tried to write to.
2086        path: Utf8PathBuf,
2087
2088        /// The error that occurred during the write.
2089        #[source]
2090        error: std::io::Error,
2091    },
2092
2093    /// Failed to close a temporary directory for an extracted custom platform.
2094    #[error(
2095        "for custom platform obtained from {source}, \
2096         failed to close temporary directory `{dir_path}`"
2097    )]
2098    CustomPlatformCloseError {
2099        /// The source of the target triple.
2100        source: TargetTripleSource,
2101
2102        /// The directory that we tried to delete.
2103        dir_path: Utf8PathBuf,
2104
2105        /// The error that occurred during the close.
2106        #[source]
2107        error: std::io::Error,
2108    },
2109}
2110
2111impl TargetTripleError {
2112    /// Returns a [`miette::Report`] for the source, if available.
2113    ///
2114    /// This should be preferred over [`std::error::Error::source`] if
2115    /// available.
2116    pub fn source_report(&self) -> Option<miette::Report> {
2117        match self {
2118            Self::TargetSpecError { error, .. } => {
2119                Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
2120            }
2121            // The remaining types are covered via the error source path.
2122            TargetTripleError::InvalidEnvironmentVar
2123            | TargetTripleError::TargetPathReadError { .. }
2124            | TargetTripleError::CustomPlatformTempDirError { .. }
2125            | TargetTripleError::CustomPlatformWriteError { .. }
2126            | TargetTripleError::CustomPlatformCloseError { .. } => None,
2127        }
2128    }
2129}
2130
2131/// An error occurred determining the target runner
2132#[derive(Debug, Error)]
2133pub enum TargetRunnerError {
2134    /// An environment variable contained non-utf8 content
2135    #[error("environment variable '{0}' contained non-UTF-8 data")]
2136    InvalidEnvironmentVar(String),
2137
2138    /// An environment variable or config key was found that matches the target
2139    /// triple, but it didn't actually contain a binary
2140    #[error("runner '{key}' = '{value}' did not contain a runner binary")]
2141    BinaryNotSpecified {
2142        /// The source under consideration.
2143        key: PlatformRunnerSource,
2144
2145        /// The value that was read from the key
2146        value: String,
2147    },
2148}
2149
2150/// An error that occurred while setting up the signal handler.
2151#[derive(Debug, Error)]
2152#[error("error setting up signal handler")]
2153pub struct SignalHandlerSetupError(#[from] std::io::Error);
2154
2155/// An error occurred while showing test groups.
2156#[derive(Debug, Error)]
2157pub enum ShowTestGroupsError {
2158    /// Unknown test groups were specified.
2159    #[error(
2160        "unknown test groups specified: {}\n(known groups: {})",
2161        unknown_groups.iter().join(", "),
2162        known_groups.iter().join(", "),
2163    )]
2164    UnknownGroups {
2165        /// The unknown test groups.
2166        unknown_groups: BTreeSet<TestGroup>,
2167
2168        /// All known test groups.
2169        known_groups: BTreeSet<TestGroup>,
2170    },
2171}
2172
2173/// An error occurred while processing profile's inherits setting
2174#[derive(Debug, Error, PartialEq, Eq, Hash)]
2175pub enum InheritsError {
2176    /// The default profile should not be able to inherit from other profiles
2177    #[error("the {} profile should not inherit from other profiles", .0)]
2178    DefaultProfileInheritance(String),
2179    /// An unknown/unfound profile was detected to inherit from in profile configuration
2180    #[error("profile {} inherits from an unknown profile {}", .0, .1)]
2181    UnknownInheritance(String, String),
2182    /// A self referential inheritance is detected from this profile
2183    #[error("a self referential inheritance is detected from profile: {}", .0)]
2184    SelfReferentialInheritance(String),
2185    /// An inheritance cycle was detected in the profile configuration.
2186    #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
2187        format!("[{}]", scc.iter().join(", "))
2188    }).join(", "))]
2189    InheritanceCycle(Vec<Vec<String>>),
2190}
2191
2192// ---
2193// Record and replay errors
2194// ---
2195
2196/// An error that occurred while managing the run store.
2197#[derive(Debug, Error)]
2198pub enum RunStoreError {
2199    /// An error occurred while creating the run directory.
2200    #[error("error creating run directory `{run_dir}`")]
2201    RunDirCreate {
2202        /// The run directory that could not be created.
2203        run_dir: Utf8PathBuf,
2204
2205        /// The underlying error.
2206        #[source]
2207        error: std::io::Error,
2208    },
2209
2210    /// An error occurred while acquiring a file lock.
2211    #[error("error acquiring lock on `{path}`")]
2212    FileLock {
2213        /// The path to the lock file.
2214        path: Utf8PathBuf,
2215
2216        /// The underlying error.
2217        #[source]
2218        error: std::io::Error,
2219    },
2220
2221    /// Timed out waiting to acquire a file lock.
2222    #[error(
2223        "timed out acquiring lock on `{path}` after {timeout_secs}s (is the state directory \
2224         on a networked filesystem?)"
2225    )]
2226    FileLockTimeout {
2227        /// The path to the lock file.
2228        path: Utf8PathBuf,
2229
2230        /// The timeout duration in seconds.
2231        timeout_secs: u64,
2232    },
2233
2234    /// An error occurred while reading the run list.
2235    #[error("error reading run list from `{path}`")]
2236    RunListRead {
2237        /// The path to the run list file.
2238        path: Utf8PathBuf,
2239
2240        /// The underlying error.
2241        #[source]
2242        error: std::io::Error,
2243    },
2244
2245    /// An error occurred while deserializing the run list.
2246    #[error("error deserializing run list from `{path}`")]
2247    RunListDeserialize {
2248        /// The path to the run list file.
2249        path: Utf8PathBuf,
2250
2251        /// The underlying error.
2252        #[source]
2253        error: serde_json::Error,
2254    },
2255
2256    /// An error occurred while serializing the run list.
2257    #[error("error serializing run list to `{path}`")]
2258    RunListSerialize {
2259        /// The path to the run list file.
2260        path: Utf8PathBuf,
2261
2262        /// The underlying error.
2263        #[source]
2264        error: serde_json::Error,
2265    },
2266
2267    /// An error occurred while serializing rerun info.
2268    #[error("error serializing rerun info")]
2269    RerunInfoSerialize {
2270        /// The underlying error.
2271        #[source]
2272        error: serde_json::Error,
2273    },
2274
2275    /// An error occurred while serializing the test list.
2276    #[error("error serializing test list")]
2277    TestListSerialize {
2278        /// The underlying error.
2279        #[source]
2280        error: serde_json::Error,
2281    },
2282
2283    /// An error occurred while serializing the record options.
2284    #[error("error serializing record options")]
2285    RecordOptionsSerialize {
2286        /// The underlying error.
2287        #[source]
2288        error: serde_json::Error,
2289    },
2290
2291    /// An error occurred while serializing a test event.
2292    #[error("error serializing test event")]
2293    TestEventSerialize {
2294        /// The underlying error.
2295        #[source]
2296        error: serde_json::Error,
2297    },
2298
2299    /// An error occurred while writing the run list.
2300    #[error("error writing run list to `{path}`")]
2301    RunListWrite {
2302        /// The path to the run list file.
2303        path: Utf8PathBuf,
2304
2305        /// The underlying error.
2306        #[source]
2307        error: atomicwrites::Error<std::io::Error>,
2308    },
2309
2310    /// An error occurred while writing to the store.
2311    #[error("error writing to store at `{store_path}`")]
2312    StoreWrite {
2313        /// The path to the store file.
2314        store_path: Utf8PathBuf,
2315
2316        /// The underlying error.
2317        #[source]
2318        error: StoreWriterError,
2319    },
2320
2321    /// An error occurred while creating the run log.
2322    #[error("error creating run log at `{path}`")]
2323    RunLogCreate {
2324        /// The path to the run log file.
2325        path: Utf8PathBuf,
2326
2327        /// The underlying error.
2328        #[source]
2329        error: std::io::Error,
2330    },
2331
2332    /// An error occurred while writing to the run log.
2333    #[error("error writing to run log at `{path}`")]
2334    RunLogWrite {
2335        /// The path to the run log file.
2336        path: Utf8PathBuf,
2337
2338        /// The underlying error.
2339        #[source]
2340        error: std::io::Error,
2341    },
2342
2343    /// An error occurred while flushing the run log.
2344    #[error("error flushing run log at `{path}`")]
2345    RunLogFlush {
2346        /// The path to the run log file.
2347        path: Utf8PathBuf,
2348
2349        /// The underlying error.
2350        #[source]
2351        error: std::io::Error,
2352    },
2353
2354    /// Cannot write to runs.json.zst because it has a newer format version.
2355    #[error(
2356        "cannot write to record store: runs.json.zst format version {file_version} is newer than \
2357         supported version {max_supported_version}"
2358    )]
2359    FormatVersionTooNew {
2360        /// The format version in the file.
2361        file_version: RunsJsonFormatVersion,
2362        /// The maximum version this nextest can write.
2363        max_supported_version: RunsJsonFormatVersion,
2364    },
2365}
2366
2367/// An error that occurred while writing to a zip store.
2368#[derive(Debug, Error)]
2369#[non_exhaustive]
2370pub enum StoreWriterError {
2371    /// An error occurred while creating the store file.
2372    #[error("error creating store")]
2373    Create {
2374        /// The underlying error.
2375        #[source]
2376        error: std::io::Error,
2377    },
2378
2379    /// An error occurred while writing to a file in the store.
2380    #[error("error writing to path `{path}` in store")]
2381    Write {
2382        /// The path within the store.
2383        path: Utf8PathBuf,
2384
2385        /// The underlying error.
2386        #[source]
2387        error: std::io::Error,
2388    },
2389
2390    /// An error occurred while compressing data with a zstd dictionary.
2391    #[error("error compressing data")]
2392    Compress {
2393        /// The underlying error.
2394        #[source]
2395        error: std::io::Error,
2396    },
2397
2398    /// An error occurred while finalizing the store.
2399    #[error("error finalizing store")]
2400    Finish {
2401        /// The underlying error.
2402        #[source]
2403        error: std::io::Error,
2404    },
2405
2406    /// An error occurred while flushing the store.
2407    #[error("error flushing store")]
2408    Flush {
2409        /// The underlying error.
2410        #[source]
2411        error: std::io::Error,
2412    },
2413}
2414
2415/// An error that occurred in the record reporter.
2416#[derive(Debug, Error)]
2417pub enum RecordReporterError {
2418    /// An error occurred while writing to the run store.
2419    #[error(transparent)]
2420    RunStore(RunStoreError),
2421
2422    /// The writer thread panicked.
2423    #[error("record writer thread panicked: {message}")]
2424    WriterPanic {
2425        /// A message extracted from the panic payload, if available.
2426        message: String,
2427    },
2428}
2429
2430/// An error determining the state directory for recordings.
2431#[derive(Debug, Error)]
2432pub enum StateDirError {
2433    /// The platform base strategy could not be determined.
2434    ///
2435    /// This typically means the platform doesn't support standard directory layouts.
2436    #[error("could not determine platform base directory strategy")]
2437    BaseDirStrategy(#[source] HomeDirError),
2438
2439    /// The platform state directory path is not valid UTF-8.
2440    #[error("platform state directory is not valid UTF-8: {path:?}")]
2441    StateDirNotUtf8 {
2442        /// The path that was not valid UTF-8.
2443        path: PathBuf,
2444    },
2445
2446    /// The workspace path could not be canonicalized.
2447    #[error("could not canonicalize workspace path `{workspace_root}`")]
2448    Canonicalize {
2449        /// The workspace root that could not be canonicalized.
2450        workspace_root: Utf8PathBuf,
2451        /// The underlying I/O error.
2452        #[source]
2453        error: std::io::Error,
2454    },
2455}
2456
2457/// An error during recording session setup.
2458#[derive(Debug, Error)]
2459pub enum RecordSetupError {
2460    /// The platform state directory could not be determined.
2461    #[error("could not determine platform state directory for recording")]
2462    StateDirNotFound(#[source] StateDirError),
2463
2464    /// Failed to create the run store.
2465    #[error("failed to create run store")]
2466    StoreCreate(#[source] RunStoreError),
2467
2468    /// Failed to acquire exclusive lock on the run store.
2469    #[error("failed to lock run store")]
2470    StoreLock(#[source] RunStoreError),
2471
2472    /// Failed to create the run recorder.
2473    #[error("failed to create run recorder")]
2474    RecorderCreate(#[source] RunStoreError),
2475}
2476
2477/// An error that occurred while pruning recorded runs.
2478#[derive(Debug, Error)]
2479pub enum RecordPruneError {
2480    /// An error occurred while deleting a run directory.
2481    #[error("error deleting run `{run_id}` at `{path}`")]
2482    DeleteRun {
2483        /// The run ID that could not be deleted.
2484        run_id: ReportUuid,
2485
2486        /// The path to the run directory.
2487        path: Utf8PathBuf,
2488
2489        /// The underlying error.
2490        #[source]
2491        error: std::io::Error,
2492    },
2493
2494    /// An error occurred while calculating the size of a path.
2495    #[error("error calculating size of `{path}`")]
2496    CalculateSize {
2497        /// The path whose size could not be calculated.
2498        path: Utf8PathBuf,
2499
2500        /// The underlying error.
2501        #[source]
2502        error: std::io::Error,
2503    },
2504
2505    /// An error occurred while deleting an orphaned directory.
2506    #[error("error deleting orphaned directory `{path}`")]
2507    DeleteOrphan {
2508        /// The path to the orphaned directory.
2509        path: Utf8PathBuf,
2510
2511        /// The underlying error.
2512        #[source]
2513        error: std::io::Error,
2514    },
2515
2516    /// An error occurred while reading the runs directory.
2517    #[error("error reading runs directory `{path}`")]
2518    ReadRunsDir {
2519        /// The path to the runs directory.
2520        path: Utf8PathBuf,
2521
2522        /// The underlying error.
2523        #[source]
2524        error: std::io::Error,
2525    },
2526
2527    /// An error occurred while reading a directory entry.
2528    #[error("error reading directory entry in `{dir}`")]
2529    ReadDirEntry {
2530        /// The path to the directory being read.
2531        dir: Utf8PathBuf,
2532
2533        /// The underlying error.
2534        #[source]
2535        error: std::io::Error,
2536    },
2537
2538    /// An error occurred while reading file type.
2539    #[error("error reading file type for `{path}`")]
2540    ReadFileType {
2541        /// The path whose file type could not be read.
2542        path: Utf8PathBuf,
2543
2544        /// The underlying error.
2545        #[source]
2546        error: std::io::Error,
2547    },
2548}
2549
2550/// Error returned when parsing an invalid run ID selector.
2551///
2552/// A valid selector is either "latest" or a string containing only hex digits
2553/// and dashes (for UUID format).
2554#[derive(Clone, Debug, PartialEq, Eq, Error)]
2555#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2556pub struct InvalidRunIdSelector {
2557    /// The invalid input string.
2558    pub input: String,
2559}
2560
2561/// Error returned when parsing a [`RunIdOrRecordingSelector`](crate::record::RunIdOrRecordingSelector) fails.
2562///
2563/// A valid selector is either "latest", a string containing only hex digits
2564/// and dashes (for UUID format), or a file path (ending in `.zip` or
2565/// containing path separators).
2566#[derive(Clone, Debug, PartialEq, Eq, Error)]
2567#[error(
2568    "invalid run ID selector `{input}`: expected `latest`, hex digits, \
2569     or a file path (ending in `.zip` or containing path separators)"
2570)]
2571pub struct InvalidRunIdOrRecordingSelector {
2572    /// The invalid input string.
2573    pub input: String,
2574}
2575
2576/// An error resolving a run ID prefix.
2577#[derive(Debug, Error)]
2578pub enum RunIdResolutionError {
2579    /// No run found matching the prefix.
2580    #[error("no recorded run found matching `{prefix}`")]
2581    NotFound {
2582        /// The prefix that was searched for.
2583        prefix: String,
2584    },
2585
2586    /// Multiple runs match the prefix.
2587    #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2588    Ambiguous {
2589        /// The prefix that was searched for.
2590        prefix: String,
2591
2592        /// The number of matching runs.
2593        count: usize,
2594
2595        /// The candidates that matched (up to a limit).
2596        candidates: Vec<RecordedRunInfo>,
2597
2598        /// The run ID index for computing shortest unique prefixes.
2599        run_id_index: RunIdIndex,
2600    },
2601
2602    /// The prefix contains invalid characters.
2603    #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2604    InvalidPrefix {
2605        /// The invalid prefix.
2606        prefix: String,
2607    },
2608
2609    /// No recorded runs exist.
2610    #[error("no recorded runs exist")]
2611    NoRuns,
2612}
2613
2614/// An error that occurred while reading a recorded run.
2615#[derive(Debug, Error)]
2616pub enum RecordReadError {
2617    /// The run was not found.
2618    #[error("run not found at `{path}`")]
2619    RunNotFound {
2620        /// The path where the run was expected.
2621        path: Utf8PathBuf,
2622    },
2623
2624    /// Failed to open the archive.
2625    #[error("error opening archive at `{path}`")]
2626    OpenArchive {
2627        /// The path to the archive.
2628        path: Utf8PathBuf,
2629
2630        /// The underlying error.
2631        #[source]
2632        error: std::io::Error,
2633    },
2634
2635    /// Failed to parse the archive (corrupt or truncated).
2636    #[error("error parsing archive at `{path}`")]
2637    ParseArchive {
2638        /// The path to the archive.
2639        path: Utf8PathBuf,
2640
2641        /// The underlying error.
2642        #[source]
2643        error: std::io::Error,
2644    },
2645
2646    /// Failed to read an archive file.
2647    #[error("error reading `{file_name}` from archive")]
2648    ReadArchiveFile {
2649        /// The file name within the archive.
2650        file_name: String,
2651
2652        /// The underlying error.
2653        #[source]
2654        error: std::io::Error,
2655    },
2656
2657    /// Failed to open the run log.
2658    #[error("error opening run log at `{path}`")]
2659    OpenRunLog {
2660        /// The path to the run log.
2661        path: Utf8PathBuf,
2662
2663        /// The underlying error.
2664        #[source]
2665        error: std::io::Error,
2666    },
2667
2668    /// Failed to read a line from the run log.
2669    #[error("error reading line {line_number} from run log")]
2670    ReadRunLog {
2671        /// The line number that failed.
2672        line_number: usize,
2673
2674        /// The underlying error.
2675        #[source]
2676        error: std::io::Error,
2677    },
2678
2679    /// Failed to parse an event from the run log.
2680    #[error("error parsing event at line {line_number}")]
2681    ParseEvent {
2682        /// The line number that failed.
2683        line_number: usize,
2684
2685        /// The underlying error.
2686        #[source]
2687        error: serde_json::Error,
2688    },
2689
2690    /// Required file not found in archive.
2691    #[error("required file `{file_name}` not found in archive")]
2692    FileNotFound {
2693        /// The file name that was not found.
2694        file_name: String,
2695    },
2696
2697    /// Failed to decompress archive data.
2698    #[error("error decompressing data from `{file_name}`")]
2699    Decompress {
2700        /// The file name being decompressed.
2701        file_name: String,
2702
2703        /// The underlying error.
2704        #[source]
2705        error: std::io::Error,
2706    },
2707
2708    /// Unknown output file type in archive.
2709    ///
2710    /// This indicates the archive was created by a newer version of nextest
2711    /// with additional output types that this version doesn't recognize.
2712    #[error(
2713        "unknown output file type `{file_name}` in archive \
2714         (archive may have been created by a newer version of nextest)"
2715    )]
2716    UnknownOutputType {
2717        /// The file name with the unknown type.
2718        file_name: String,
2719    },
2720
2721    /// Archive file exceeds maximum allowed size.
2722    #[error(
2723        "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2724    )]
2725    FileTooLarge {
2726        /// The file name in the archive.
2727        file_name: String,
2728
2729        /// The size of the file.
2730        size: u64,
2731
2732        /// The maximum allowed size.
2733        limit: u64,
2734    },
2735
2736    /// Archive file size doesn't match the header.
2737    ///
2738    /// This indicates a corrupt or tampered archive since nextest controls
2739    /// archive creation.
2740    #[error(
2741        "file `{file_name}` size mismatch: header claims {claimed_size} bytes, \
2742         but read {actual_size} bytes (archive may be corrupt or tampered)"
2743    )]
2744    SizeMismatch {
2745        /// The file name in the archive.
2746        file_name: String,
2747
2748        /// The size claimed in the ZIP header.
2749        claimed_size: u64,
2750
2751        /// The actual size read.
2752        actual_size: u64,
2753    },
2754
2755    /// Failed to deserialize metadata.
2756    #[error("error deserializing `{file_name}`")]
2757    DeserializeMetadata {
2758        /// The file name being deserialized.
2759        file_name: String,
2760
2761        /// The underlying error.
2762        #[source]
2763        error: serde_json::Error,
2764    },
2765
2766    /// Failed to extract a file from the store.
2767    #[error("failed to extract `{store_path}` to `{output_path}`")]
2768    ExtractFile {
2769        /// The path within the store.
2770        store_path: String,
2771
2772        /// The output path where extraction was attempted.
2773        output_path: Utf8PathBuf,
2774
2775        /// The underlying I/O error.
2776        #[source]
2777        error: std::io::Error,
2778    },
2779
2780    /// An error occurred while reading a portable recording.
2781    #[error("error reading portable recording")]
2782    PortableRecording(#[source] PortableRecordingReadError),
2783}
2784
2785/// An error that occurred while creating a portable recording.
2786#[derive(Debug, Error)]
2787#[non_exhaustive]
2788pub enum PortableRecordingError {
2789    /// The run directory does not exist.
2790    #[error("run directory does not exist: {path}")]
2791    RunDirNotFound {
2792        /// The path that was expected to exist.
2793        path: Utf8PathBuf,
2794    },
2795
2796    /// A required file is missing from the run directory.
2797    #[error("required file missing from run directory `{run_dir}`: `{file_name}`")]
2798    RequiredFileMissing {
2799        /// The run directory that was being validated.
2800        run_dir: Utf8PathBuf,
2801        /// The name of the missing file.
2802        file_name: &'static str,
2803    },
2804
2805    /// Failed to serialize the manifest.
2806    #[error("failed to serialize manifest")]
2807    SerializeManifest(#[source] serde_json::Error),
2808
2809    /// Failed to start a file in the zip archive.
2810    #[error("failed to start file {file_name} in archive")]
2811    ZipStartFile {
2812        /// The file that failed to start.
2813        file_name: &'static str,
2814        /// The underlying I/O error.
2815        #[source]
2816        source: std::io::Error,
2817    },
2818
2819    /// Failed to write to the zip archive.
2820    #[error("failed to write {file_name} to archive")]
2821    ZipWrite {
2822        /// The file being written.
2823        file_name: &'static str,
2824        /// The underlying I/O error.
2825        #[source]
2826        source: std::io::Error,
2827    },
2828
2829    /// Failed to read a source file.
2830    #[error("failed to read {file_name}")]
2831    ReadFile {
2832        /// The file being read.
2833        file_name: &'static str,
2834        /// The underlying I/O error.
2835        #[source]
2836        source: std::io::Error,
2837    },
2838
2839    /// Failed to finalize the zip archive.
2840    #[error("failed to finalize archive")]
2841    ZipFinalize(#[source] std::io::Error),
2842
2843    /// Failed to write the archive atomically.
2844    #[error("failed to write archive atomically to {path}")]
2845    AtomicWrite {
2846        /// The destination path.
2847        path: Utf8PathBuf,
2848        /// The underlying error.
2849        #[source]
2850        source: std::io::Error,
2851    },
2852}
2853
2854/// An error that occurred while reading a portable recording.
2855#[derive(Debug, Error)]
2856#[non_exhaustive]
2857pub enum PortableRecordingReadError {
2858    /// Failed to open the archive file.
2859    #[error("failed to open archive at `{path}`")]
2860    OpenArchive {
2861        /// The path to the archive.
2862        path: Utf8PathBuf,
2863        /// The underlying I/O error.
2864        #[source]
2865        error: std::io::Error,
2866    },
2867
2868    /// Failed to read from the archive.
2869    #[error("failed to read archive at `{path}`")]
2870    ReadArchive {
2871        /// The path to the archive.
2872        path: Utf8PathBuf,
2873        /// The underlying I/O error.
2874        #[source]
2875        error: std::io::Error,
2876    },
2877
2878    /// A required file is missing from the archive.
2879    #[error("required file `{file_name}` missing from archive at `{path}`")]
2880    MissingFile {
2881        /// The path to the archive.
2882        path: Utf8PathBuf,
2883        /// The name of the missing file.
2884        file_name: Cow<'static, str>,
2885    },
2886
2887    /// Failed to parse the manifest.
2888    #[error("failed to parse manifest from archive at `{path}`")]
2889    ParseManifest {
2890        /// The path to the archive.
2891        path: Utf8PathBuf,
2892        /// The underlying JSON error.
2893        #[source]
2894        error: serde_json::Error,
2895    },
2896
2897    /// The portable recording format version is not supported.
2898    #[error(
2899        "portable recording format version {found} in `{path}` is incompatible: {incompatibility} \
2900         (this nextest supports version {supported})"
2901    )]
2902    UnsupportedFormatVersion {
2903        /// The path to the archive.
2904        path: Utf8PathBuf,
2905        /// The format version found in the archive.
2906        found: PortableRecordingFormatVersion,
2907        /// The supported format version.
2908        supported: PortableRecordingFormatVersion,
2909        /// The specific incompatibility.
2910        incompatibility: PortableRecordingVersionIncompatibility,
2911    },
2912
2913    /// The store format version is not supported.
2914    #[error(
2915        "store format version {found} in `{path}` is incompatible: {incompatibility} \
2916         (this nextest supports version {supported})"
2917    )]
2918    UnsupportedStoreFormatVersion {
2919        /// The path to the archive.
2920        path: Utf8PathBuf,
2921        /// The store format version found in the archive.
2922        found: StoreFormatVersion,
2923        /// The supported store format version.
2924        supported: StoreFormatVersion,
2925        /// The specific incompatibility.
2926        incompatibility: StoreVersionIncompatibility,
2927    },
2928
2929    /// A file in the archive exceeds the size limit.
2930    #[error(
2931        "file `{file_name}` in archive `{path}` is too large \
2932         ({size} bytes, limit is {limit} bytes)"
2933    )]
2934    FileTooLarge {
2935        /// The path to the archive.
2936        path: Utf8PathBuf,
2937        /// The name of the file.
2938        file_name: Cow<'static, str>,
2939        /// The size of the file.
2940        size: u64,
2941        /// The size limit.
2942        limit: u64,
2943    },
2944
2945    /// Failed to extract a file from the archive.
2946    #[error("failed to extract `{file_name}` from archive `{archive_path}` to `{output_path}`")]
2947    ExtractFile {
2948        /// The path to the archive.
2949        archive_path: Utf8PathBuf,
2950        /// The name of the file being extracted.
2951        file_name: &'static str,
2952        /// The path where extraction was attempted.
2953        output_path: Utf8PathBuf,
2954        /// The underlying I/O error.
2955        #[source]
2956        error: std::io::Error,
2957    },
2958
2959    /// The inner archive is compressed.
2960    ///
2961    /// With portable recordings, the inner archive must be stored uncompressed.
2962    #[error(
2963        "for portable recording `{archive_path}`, the inner archive is stored \
2964         with {:?} compression -- it must be stored uncompressed",
2965        compression
2966    )]
2967    CompressedInnerArchive {
2968        /// The path to the archive.
2969        archive_path: Utf8PathBuf,
2970        /// The compression method used.
2971        compression: CompressionMethod,
2972    },
2973
2974    /// The archive has no manifest and is not a valid wrapper archive.
2975    ///
2976    /// A wrapper archive must contain exactly one `.zip` file.
2977    #[error(
2978        "archive at `{path}` has no manifest and is not a wrapper archive \
2979         (contains {file_count} {}, {zip_count} of which {} in .zip)",
2980        plural::files_str(*file_count),
2981        plural::end_str(*zip_count)
2982    )]
2983    NotAWrapperArchive {
2984        /// The path to the archive.
2985        path: Utf8PathBuf,
2986        /// The total number of files in the archive.
2987        file_count: usize,
2988        /// The number of files ending in `.zip`.
2989        zip_count: usize,
2990    },
2991
2992    /// An unexpected I/O error occurred while probing whether the input is
2993    /// seekable.
2994    ///
2995    /// This is distinct from the expected `ESPIPE`/`ERROR_INVALID_FUNCTION`
2996    /// errors that indicate a pipe or FIFO. Unexpected errors (e.g. `EBADF`,
2997    /// `EIO`) are propagated here rather than falling into the spool path.
2998    #[error("unexpected I/O error while probing seekability of `{path}`")]
2999    SeekProbe {
3000        /// The path to the recording.
3001        path: Utf8PathBuf,
3002        /// The underlying I/O error.
3003        #[source]
3004        error: std::io::Error,
3005    },
3006
3007    /// An I/O error occurred while spooling a non-seekable input to a
3008    /// temporary file.
3009    ///
3010    /// This covers temp file creation, data copying, and seeking back to
3011    /// the start.
3012    #[error("failed to spool non-seekable input `{path}` to a temporary file")]
3013    SpoolTempFile {
3014        /// The path to the recording (e.g. `/proc/self/fd/11`).
3015        path: Utf8PathBuf,
3016        /// The underlying I/O error.
3017        #[source]
3018        error: std::io::Error,
3019    },
3020
3021    /// The input being spooled to a temporary file exceeded the size limit.
3022    #[error(
3023        "recording at `{path}` exceeds the spool size limit \
3024         ({}); use a file path instead of process substitution",
3025        SizeDisplay(.limit.0)
3026    )]
3027    SpoolTooLarge {
3028        /// The path to the recording.
3029        path: Utf8PathBuf,
3030        /// The size limit.
3031        limit: ByteSize,
3032    },
3033}
3034
3035/// Errors that can occur during Chrome trace conversion.
3036#[derive(Debug, Error)]
3037pub enum ChromeTraceError {
3038    /// An error occurred while reading recorded events.
3039    #[error("error reading recorded events")]
3040    ReadError(#[source] RecordReadError),
3041
3042    /// An event referenced a test that was never started.
3043    #[error(
3044        "event for test `{test_name}` in binary `{binary_id}` \
3045         has no prior TestStarted event (corrupt or truncated log?)"
3046    )]
3047    MissingTestStart {
3048        /// The test name.
3049        test_name: TestCaseName,
3050
3051        /// The binary ID.
3052        binary_id: RustBinaryId,
3053    },
3054
3055    /// A SetupScriptSlow event referenced a script that is not running.
3056    #[error(
3057        "SetupScriptSlow for script `{script_id}` \
3058         has no prior SetupScriptStarted event (corrupt or truncated log?)"
3059    )]
3060    MissingScriptStart {
3061        /// The script ID.
3062        script_id: ScriptId,
3063    },
3064
3065    /// A StressSubRunFinished event arrived without a prior
3066    /// StressSubRunStarted.
3067    #[error(
3068        "StressSubRunFinished has no prior StressSubRunStarted event \
3069         (corrupt or truncated log?)"
3070    )]
3071    MissingStressSubRunStart,
3072
3073    /// An error occurred while serializing the trace to JSON.
3074    #[error("error serializing Chrome trace JSON")]
3075    SerializeError(#[source] serde_json::Error),
3076}
3077
3078/// An error that occurred while reconstructing a TestList from a summary.
3079///
3080/// Returned by [`TestList::from_summary`](crate::list::TestList::from_summary).
3081#[derive(Debug, Error)]
3082pub enum TestListFromSummaryError {
3083    /// Package not found in the package graph.
3084    #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
3085    PackageNotFound {
3086        /// The package name.
3087        name: String,
3088
3089        /// The package ID that was looked up.
3090        package_id: String,
3091    },
3092
3093    /// Error parsing rust build metadata.
3094    #[error("error parsing rust build metadata")]
3095    RustBuildMeta(#[source] RustBuildMetaParseError),
3096}
3097
3098#[cfg(feature = "self-update")]
3099mod self_update_errors {
3100    use super::*;
3101    use crate::update::PrereleaseKind;
3102    use mukti_metadata::ReleaseStatus;
3103    use semver::{Version, VersionReq};
3104
3105    /// An error that occurs while performing a self-update.
3106    ///
3107    /// Returned by methods in the [`update`](crate::update) module.
3108    #[derive(Debug, Error)]
3109    #[non_exhaustive]
3110    pub enum UpdateError {
3111        /// Failed to read release metadata from a local path on disk.
3112        #[error("failed to read release metadata from `{path}`")]
3113        ReadLocalMetadata {
3114            /// The path that was read.
3115            path: Utf8PathBuf,
3116
3117            /// The error that occurred.
3118            #[source]
3119            error: std::io::Error,
3120        },
3121
3122        /// An error was generated by `self_update`.
3123        #[error("self-update failed")]
3124        SelfUpdate(#[source] self_update::errors::Error),
3125
3126        /// An HTTP error occurred while performing a request.
3127        #[error("error performing HTTP request")]
3128        Http(#[source] ureq::Error),
3129
3130        /// An error occurred while reading an HTTP response body.
3131        #[error("error reading HTTP response body")]
3132        HttpBody(#[source] std::io::Error),
3133
3134        /// The server's Content-Length header was present but could not be
3135        /// parsed.
3136        #[error("Content-Length header present but could not be parsed as an integer: {value:?}")]
3137        ContentLengthInvalid {
3138            /// The raw header value.
3139            value: String,
3140        },
3141
3142        /// The server's Content-Length header didn't match the number of bytes
3143        /// received.
3144        #[error("content length mismatch: expected {expected} bytes, received {actual} bytes")]
3145        ContentLengthMismatch {
3146            /// The expected number of bytes (from the Content-Length header).
3147            expected: u64,
3148            /// The actual number of bytes received.
3149            actual: u64,
3150        },
3151
3152        /// Deserializing release metadata failed.
3153        #[error("deserializing release metadata failed")]
3154        ReleaseMetadataDe(#[source] serde_json::Error),
3155
3156        /// This version was not found.
3157        #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
3158        VersionNotFound {
3159            /// The version that wasn't found.
3160            version: Version,
3161
3162            /// A list of all known versions.
3163            known: Vec<(Version, ReleaseStatus)>,
3164        },
3165
3166        /// No version was found matching a requirement.
3167        #[error("no version found matching requirement `{req}`")]
3168        NoMatchForVersionReq {
3169            /// The version requirement that had no matches.
3170            req: VersionReq,
3171        },
3172
3173        /// No stable (non-prerelease) version was found.
3174        #[error("no stable version found")]
3175        NoStableVersion,
3176
3177        /// No version matching the requested prerelease kind was found.
3178        #[error("no version found matching {} channel", kind.description())]
3179        NoVersionForPrereleaseKind {
3180            /// The kind of prerelease that was requested.
3181            kind: PrereleaseKind,
3182        },
3183
3184        /// The specified mukti project was not found.
3185        #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
3186        MuktiProjectNotFound {
3187            /// The project that was not found.
3188            not_found: String,
3189
3190            /// Known projects.
3191            known: Vec<String>,
3192        },
3193
3194        /// No release information was found for the given target triple.
3195        #[error(
3196            "for version {version}, no release information found for target `{triple}` \
3197            (known targets: {})",
3198            known_triples.iter().join(", ")
3199        )]
3200        NoTargetData {
3201            /// The version that was fetched.
3202            version: Version,
3203
3204            /// The target triple.
3205            triple: String,
3206
3207            /// The triples that were found.
3208            known_triples: BTreeSet<String>,
3209        },
3210
3211        /// The current executable could not be determined.
3212        #[error("the current executable's path could not be determined")]
3213        CurrentExe(#[source] std::io::Error),
3214
3215        /// A temporary directory could not be created.
3216        #[error("temporary directory could not be created at `{location}`")]
3217        TempDirCreate {
3218            /// The location where the temporary directory could not be created.
3219            location: Utf8PathBuf,
3220
3221            /// The error that occurred.
3222            #[source]
3223            error: std::io::Error,
3224        },
3225
3226        /// The temporary archive could not be created.
3227        #[error("temporary archive could not be created at `{archive_path}`")]
3228        TempArchiveCreate {
3229            /// The archive file that couldn't be created.
3230            archive_path: Utf8PathBuf,
3231
3232            /// The error that occurred.
3233            #[source]
3234            error: std::io::Error,
3235        },
3236
3237        /// An error occurred while writing to a temporary archive.
3238        #[error("error writing to temporary archive at `{archive_path}`")]
3239        TempArchiveWrite {
3240            /// The archive path for which there was an error.
3241            archive_path: Utf8PathBuf,
3242
3243            /// The error that occurred.
3244            #[source]
3245            error: std::io::Error,
3246        },
3247
3248        /// An error occurred while reading from a temporary archive.
3249        #[error("error reading from temporary archive at `{archive_path}`")]
3250        TempArchiveRead {
3251            /// The archive path for which there was an error.
3252            archive_path: Utf8PathBuf,
3253
3254            /// The error that occurred.
3255            #[source]
3256            error: std::io::Error,
3257        },
3258
3259        /// A checksum mismatch occurred. (Currently, the SHA-256 checksum is checked.)
3260        #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
3261        ChecksumMismatch {
3262            /// The expected checksum.
3263            expected: String,
3264
3265            /// The actual checksum.
3266            actual: String,
3267        },
3268
3269        /// An error occurred while renaming a file.
3270        #[error("error renaming `{source}` to `{dest}`")]
3271        FsRename {
3272            /// The rename source.
3273            source: Utf8PathBuf,
3274
3275            /// The rename destination.
3276            dest: Utf8PathBuf,
3277
3278            /// The error that occurred.
3279            #[source]
3280            error: std::io::Error,
3281        },
3282
3283        /// An error occurred while running `cargo nextest self setup`.
3284        #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
3285        SelfSetup(#[source] std::io::Error),
3286    }
3287
3288    fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
3289        use std::fmt::Write;
3290
3291        // Take the first few versions here.
3292        const DISPLAY_COUNT: usize = 4;
3293
3294        let display_versions: Vec<_> = versions
3295            .iter()
3296            .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
3297            .map(|(v, _)| v.to_string())
3298            .take(DISPLAY_COUNT)
3299            .collect();
3300        let mut display_str = display_versions.join(", ");
3301        if versions.len() > display_versions.len() {
3302            write!(
3303                display_str,
3304                " and {} others",
3305                versions.len() - display_versions.len()
3306            )
3307            .unwrap();
3308        }
3309
3310        display_str
3311    }
3312
3313    /// An error occurred while parsing an [`UpdateVersion`](crate::update::UpdateVersion).
3314    #[derive(Debug, Error)]
3315    pub enum UpdateVersionParseError {
3316        /// The version string is empty.
3317        #[error("version string is empty")]
3318        EmptyString,
3319
3320        /// The input is not a valid version requirement.
3321        #[error(
3322            "`{input}` is not a valid semver requirement\n\
3323                (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
3324        )]
3325        InvalidVersionReq {
3326            /// The input that was provided.
3327            input: String,
3328
3329            /// The error.
3330            #[source]
3331            error: semver::Error,
3332        },
3333
3334        /// The version is not a valid semver.
3335        #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3336        InvalidVersion {
3337            /// The input that was provided.
3338            input: String,
3339
3340            /// The error.
3341            #[source]
3342            error: semver::Error,
3343        },
3344    }
3345
3346    fn extra_semver_output(input: &str) -> String {
3347        // If it is not a valid version but it is a valid version
3348        // requirement, add a note to the warning
3349        if input.parse::<VersionReq>().is_ok() {
3350            format!(
3351                "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
3352            )
3353        } else {
3354            "".to_owned()
3355        }
3356    }
3357}
3358
3359#[cfg(feature = "self-update")]
3360pub use self_update_errors::*;
3361
3362#[cfg(test)]
3363mod tests {
3364    use super::*;
3365
3366    #[test]
3367    fn display_error_chain() {
3368        let err1 = StringError::new("err1", None);
3369
3370        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
3371
3372        let err2 = StringError::new("err2", Some(err1));
3373        let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
3374
3375        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
3376        err3
3377        err3 line 2
3378          caused by:
3379          - err2
3380          - err1
3381        ");
3382    }
3383
3384    #[test]
3385    fn display_error_list() {
3386        let err1 = StringError::new("err1", None);
3387
3388        let error_list =
3389            ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
3390                .expect(">= 1 error");
3391        insta::assert_snapshot!(format!("{}", error_list), @"err1");
3392        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
3393
3394        let err2 = StringError::new("err2", Some(err1));
3395        let err3 = StringError::new("err3", Some(err2));
3396
3397        let error_list =
3398            ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
3399                .expect(">= 1 error");
3400        insta::assert_snapshot!(format!("{}", error_list), @"err3");
3401        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3402        err3
3403          caused by:
3404          - err2
3405          - err1
3406        ");
3407
3408        let err4 = StringError::new("err4", None);
3409        let err5 = StringError::new("err5", Some(err4));
3410        let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
3411
3412        let error_list = ErrorList::<StringError>::new(
3413            "waiting for the heat death of the universe",
3414            vec![err3, err6],
3415        )
3416        .expect(">= 1 error");
3417
3418        insta::assert_snapshot!(format!("{}", error_list), @"
3419        2 errors occurred waiting for the heat death of the universe:
3420        * err3
3421            caused by:
3422            - err2
3423            - err1
3424        * err6
3425          err6 line 2
3426            caused by:
3427            - err5
3428            - err4
3429        ");
3430        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3431        2 errors occurred waiting for the heat death of the universe:
3432        * err3
3433            caused by:
3434            - err2
3435            - err1
3436        * err6
3437          err6 line 2
3438            caused by:
3439            - err5
3440            - err4
3441        ");
3442    }
3443
3444    #[derive(Clone, Debug, Error)]
3445    struct StringError {
3446        message: String,
3447        #[source]
3448        source: Option<Box<StringError>>,
3449    }
3450
3451    impl StringError {
3452        fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
3453            Self {
3454                message: message.into(),
3455                source: source.map(Box::new),
3456            }
3457        }
3458    }
3459
3460    impl fmt::Display for StringError {
3461        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3462            write!(f, "{}", self.message)
3463        }
3464    }
3465}