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