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