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