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