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,
10        elements::{CustomTestGroup, TestGroup},
11        scripts::{ProfileScriptType, ScriptId, ScriptType},
12    },
13    helpers::{display_exited_with, dylib_path_envvar},
14    redact::Redactor,
15    reuse_build::{ArchiveFormat, ArchiveStep},
16    target_runner::PlatformRunnerSource,
17};
18use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
19use config::ConfigError;
20use indent_write::{fmt::IndentWriter, indentable::Indented};
21use itertools::{Either, Itertools};
22use nextest_filtering::errors::FiltersetParseErrors;
23use nextest_metadata::RustBinaryId;
24use smol_str::SmolStr;
25use std::{
26    borrow::Cow,
27    collections::BTreeSet,
28    env::JoinPathsError,
29    fmt::{self, Write as _},
30    process::ExitStatus,
31    sync::Arc,
32};
33use target_spec_miette::IntoMietteDiagnostic;
34use thiserror::Error;
35
36/// An error that occurred while parsing the config.
37#[derive(Debug, Error)]
38#[error(
39    "failed to parse nextest config at `{config_file}`{}",
40    provided_by_tool(tool.as_deref())
41)]
42#[non_exhaustive]
43pub struct ConfigParseError {
44    config_file: Utf8PathBuf,
45    tool: Option<String>,
46    #[source]
47    kind: ConfigParseErrorKind,
48}
49
50impl ConfigParseError {
51    pub(crate) fn new(
52        config_file: impl Into<Utf8PathBuf>,
53        tool: Option<&str>,
54        kind: ConfigParseErrorKind,
55    ) -> Self {
56        Self {
57            config_file: config_file.into(),
58            tool: tool.map(|s| s.to_owned()),
59            kind,
60        }
61    }
62
63    /// Returns the config file for this error.
64    pub fn config_file(&self) -> &Utf8Path {
65        &self.config_file
66    }
67
68    /// Returns the tool name associated with this error.
69    pub fn tool(&self) -> Option<&str> {
70        self.tool.as_deref()
71    }
72
73    /// Returns the kind of error this is.
74    pub fn kind(&self) -> &ConfigParseErrorKind {
75        &self.kind
76    }
77}
78
79/// Returns the string ` provided by tool <tool>`, if `tool` is `Some`.
80pub fn provided_by_tool(tool: Option<&str>) -> String {
81    match tool {
82        Some(tool) => format!(" provided by tool `{tool}`"),
83        None => String::new(),
84    }
85}
86
87/// The kind of error that occurred while parsing a config.
88///
89/// Returned by [`ConfigParseError::kind`].
90#[derive(Debug, Error)]
91#[non_exhaustive]
92pub enum ConfigParseErrorKind {
93    /// An error occurred while building the config.
94    #[error(transparent)]
95    BuildError(Box<ConfigError>),
96    #[error(transparent)]
97    /// An error occurred while deserializing the config.
98    DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
99    /// An error occurred while reading the config file (version only).
100    #[error(transparent)]
101    VersionOnlyReadError(std::io::Error),
102    /// An error occurred while deserializing the config (version only).
103    #[error(transparent)]
104    VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
105    /// Errors occurred while compiling configuration strings.
106    #[error("error parsing compiled data (destructure this variant for more details)")]
107    CompileErrors(Vec<ConfigCompileError>),
108    /// An invalid set of test groups was defined by the user.
109    #[error("invalid test groups defined: {}\n(test groups cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
110    InvalidTestGroupsDefined(BTreeSet<CustomTestGroup>),
111    /// An invalid set of test groups was defined by a tool config file.
112    #[error(
113        "invalid test groups defined by tool: {}\n(test groups must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
114    InvalidTestGroupsDefinedByTool(BTreeSet<CustomTestGroup>),
115    /// Some test groups were unknown.
116    #[error("unknown test groups specified by config (destructure this variant for more details)")]
117    UnknownTestGroups {
118        /// The list of errors that occurred.
119        errors: Vec<UnknownTestGroupError>,
120
121        /// Known groups up to this point.
122        known_groups: BTreeSet<TestGroup>,
123    },
124    /// Both `[script.*]` and `[scripts.*]` were defined.
125    #[error(
126        "both `[script.*]` and `[scripts.*]` defined\n\
127         (hint: [script.*] will be removed in the future: switch to [scripts.setup.*])"
128    )]
129    BothScriptAndScriptsDefined,
130    /// An invalid set of config scripts was defined by the user.
131    #[error("invalid config scripts defined: {}\n(config scripts cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
132    InvalidConfigScriptsDefined(BTreeSet<ScriptId>),
133    /// An invalid set of config scripts was defined by a tool config file.
134    #[error(
135        "invalid config scripts defined by tool: {}\n(config scripts must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
136    InvalidConfigScriptsDefinedByTool(BTreeSet<ScriptId>),
137    /// The same config script name was used across config script types.
138    #[error(
139        "config script names used more than once: {}\n\
140         (config script names must be unique across all script types)", .0.iter().join(", ")
141    )]
142    DuplicateConfigScriptNames(BTreeSet<ScriptId>),
143    /// Errors occurred while parsing `[[profile.<profile-name>.scripts]]`.
144    #[error(
145        "errors in profile-specific config scripts (destructure this variant for more details)"
146    )]
147    ProfileScriptErrors {
148        /// The errors that occurred.
149        errors: Box<ProfileScriptErrors>,
150
151        /// Known scripts up to this point.
152        known_scripts: BTreeSet<ScriptId>,
153    },
154    /// An unknown experimental feature or features were defined.
155    #[error("unknown experimental features defined (destructure this variant for more details)")]
156    UnknownExperimentalFeatures {
157        /// The set of unknown features.
158        unknown: BTreeSet<String>,
159
160        /// The set of known features.
161        known: BTreeSet<ConfigExperimental>,
162    },
163    /// A tool specified an experimental feature.
164    ///
165    /// Tools are not allowed to specify experimental features.
166    #[error(
167        "tool config file specifies experimental features `{}` \
168         -- only repository config files can do so",
169        .features.iter().join(", "),
170    )]
171    ExperimentalFeaturesInToolConfig {
172        /// The name of the experimental feature.
173        features: BTreeSet<String>,
174    },
175    /// Experimental features were used but not enabled.
176    #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
177    ExperimentalFeaturesNotEnabled {
178        /// The features that were not enabled.
179        missing_features: BTreeSet<ConfigExperimental>,
180    },
181    /// An inheritance cycle was detected in the profile configuration.
182    #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
183    InheritanceErrors(Vec<InheritsError>),
184}
185
186/// An error that occurred while compiling overrides or scripts specified in
187/// configuration.
188#[derive(Debug)]
189#[non_exhaustive]
190pub struct ConfigCompileError {
191    /// The name of the profile under which the data was found.
192    pub profile_name: String,
193
194    /// The section within the profile where the error occurred.
195    pub section: ConfigCompileSection,
196
197    /// The kind of error that occurred.
198    pub kind: ConfigCompileErrorKind,
199}
200
201/// For a [`ConfigCompileError`], the section within the profile where the error
202/// occurred.
203#[derive(Debug)]
204pub enum ConfigCompileSection {
205    /// `profile.<profile-name>.default-filter`.
206    DefaultFilter,
207
208    /// `[[profile.<profile-name>.overrides]]` at the corresponding index.
209    Override(usize),
210
211    /// `[[profile.<profile-name>.scripts]]` at the corresponding index.
212    Script(usize),
213}
214
215/// The kind of error that occurred while parsing config overrides.
216#[derive(Debug)]
217#[non_exhaustive]
218pub enum ConfigCompileErrorKind {
219    /// Neither `platform` nor `filter` were specified.
220    ConstraintsNotSpecified {
221        /// Whether `default-filter` was specified.
222        ///
223        /// If default-filter is specified, then specifying `filter` is not
224        /// allowed -- so we show a different message in that case.
225        default_filter_specified: bool,
226    },
227
228    /// Both `filter` and `default-filter` were specified.
229    ///
230    /// It only makes sense to specify one of the two.
231    FilterAndDefaultFilterSpecified,
232
233    /// One or more errors occured while parsing expressions.
234    Parse {
235        /// A potential error that occurred while parsing the host platform expression.
236        host_parse_error: Option<target_spec::Error>,
237
238        /// A potential error that occurred while parsing the target platform expression.
239        target_parse_error: Option<target_spec::Error>,
240
241        /// Filterset or default filter parse errors.
242        filter_parse_errors: Vec<FiltersetParseErrors>,
243    },
244}
245
246impl ConfigCompileErrorKind {
247    /// Returns [`miette::Report`]s for each error recorded by self.
248    pub fn reports(&self) -> impl Iterator<Item = miette::Report> + '_ {
249        match self {
250            Self::ConstraintsNotSpecified {
251                default_filter_specified,
252            } => {
253                let message = if *default_filter_specified {
254                    "for override with `default-filter`, `platform` must also be specified"
255                } else {
256                    "at least one of `platform` and `filter` must be specified"
257                };
258                Either::Left(std::iter::once(miette::Report::msg(message)))
259            }
260            Self::FilterAndDefaultFilterSpecified => {
261                Either::Left(std::iter::once(miette::Report::msg(
262                    "at most one of `filter` and `default-filter` must be specified",
263                )))
264            }
265            Self::Parse {
266                host_parse_error,
267                target_parse_error,
268                filter_parse_errors,
269            } => {
270                let host_parse_report = host_parse_error
271                    .as_ref()
272                    .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
273                let target_parse_report = target_parse_error
274                    .as_ref()
275                    .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
276                let filter_parse_reports =
277                    filter_parse_errors.iter().flat_map(|filter_parse_errors| {
278                        filter_parse_errors.errors.iter().map(|single_error| {
279                            miette::Report::new(single_error.clone())
280                                .with_source_code(filter_parse_errors.input.to_owned())
281                        })
282                    });
283
284                Either::Right(
285                    host_parse_report
286                        .into_iter()
287                        .chain(target_parse_report)
288                        .chain(filter_parse_reports),
289                )
290            }
291        }
292    }
293}
294
295/// A test priority specified was out of range.
296#[derive(Clone, Debug, Error)]
297#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
298pub struct TestPriorityOutOfRange {
299    /// The priority that was out of range.
300    pub priority: i8,
301}
302
303/// An execution error occurred while attempting to start a test.
304#[derive(Clone, Debug, Error)]
305pub enum ChildStartError {
306    /// An error occurred while creating a temporary path for a setup script.
307    #[error("error creating temporary path for setup script")]
308    TempPath(#[source] Arc<std::io::Error>),
309
310    /// An error occurred while spawning the child process.
311    #[error("error spawning child process")]
312    Spawn(#[source] Arc<std::io::Error>),
313}
314
315/// An error that occurred while reading the output of a setup script.
316#[derive(Clone, Debug, Error)]
317pub enum SetupScriptOutputError {
318    /// An error occurred while opening the setup script environment file.
319    #[error("error opening environment file `{path}`")]
320    EnvFileOpen {
321        /// The path to the environment file.
322        path: Utf8PathBuf,
323
324        /// The underlying error.
325        #[source]
326        error: Arc<std::io::Error>,
327    },
328
329    /// An error occurred while reading the setup script environment file.
330    #[error("error reading environment file `{path}`")]
331    EnvFileRead {
332        /// The path to the environment file.
333        path: Utf8PathBuf,
334
335        /// The underlying error.
336        #[source]
337        error: Arc<std::io::Error>,
338    },
339
340    /// An error occurred while parsing the setup script environment file.
341    #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
342    EnvFileParse {
343        /// The path to the environment file.
344        path: Utf8PathBuf,
345        /// The line at issue.
346        line: String,
347    },
348
349    /// An environment variable key was reserved.
350    #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
351    EnvFileReservedKey {
352        /// The environment variable name.
353        key: String,
354    },
355}
356
357/// A list of errors that implements `Error`.
358///
359/// In the future, we'll likely want to replace this with a `miette::Diagnostic`-based error, since
360/// that supports multiple causes via "related".
361#[derive(Clone, Debug)]
362pub struct ErrorList<T> {
363    // A description of what the errors are.
364    description: &'static str,
365    // Invariant: this list is non-empty.
366    inner: Vec<T>,
367}
368
369impl<T: std::error::Error> ErrorList<T> {
370    pub(crate) fn new<U>(description: &'static str, errors: Vec<U>) -> Option<Self>
371    where
372        T: From<U>,
373    {
374        if errors.is_empty() {
375            None
376        } else {
377            Some(Self {
378                description,
379                inner: errors.into_iter().map(T::from).collect(),
380            })
381        }
382    }
383
384    /// Returns a short summary of the error list.
385    pub(crate) fn short_message(&self) -> String {
386        let string = self.to_string();
387        match string.lines().next() {
388            // Remove a trailing colon if it exists for a better UX.
389            Some(first_line) => first_line.trim_end_matches(':').to_string(),
390            None => String::new(),
391        }
392    }
393
394    pub(crate) fn iter(&self) -> impl Iterator<Item = &T> {
395        self.inner.iter()
396    }
397}
398
399impl<T: std::error::Error> fmt::Display for ErrorList<T> {
400    fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
401        // If a single error occurred, pretend that this is just that.
402        if self.inner.len() == 1 {
403            return write!(f, "{}", self.inner[0]);
404        }
405
406        // Otherwise, list all errors.
407        writeln!(
408            f,
409            "{} errors occurred {}:",
410            self.inner.len(),
411            self.description,
412        )?;
413        for error in &self.inner {
414            let mut indent = IndentWriter::new_skip_initial("  ", f);
415            writeln!(indent, "* {}", DisplayErrorChain::new(error))?;
416            f = indent.into_inner();
417        }
418        Ok(())
419    }
420}
421
422impl<T: std::error::Error> std::error::Error for ErrorList<T> {
423    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
424        if self.inner.len() == 1 {
425            self.inner[0].source()
426        } else {
427            // More than one error occurred, so we can't return a single error here. Instead, we
428            // return `None` and display the chain of causes in `fmt::Display`.
429            None
430        }
431    }
432}
433
434/// A wrapper type to display a chain of errors with internal indentation.
435///
436/// This is similar to the display-error-chain crate, but uses IndentWriter
437/// internally to ensure that subsequent lines are also nested.
438pub(crate) struct DisplayErrorChain<E> {
439    error: E,
440    initial_indent: &'static str,
441}
442
443impl<E: std::error::Error> DisplayErrorChain<E> {
444    pub(crate) fn new(error: E) -> Self {
445        Self {
446            error,
447            initial_indent: "",
448        }
449    }
450
451    pub(crate) fn new_with_initial_indent(initial_indent: &'static str, error: E) -> Self {
452        Self {
453            error,
454            initial_indent,
455        }
456    }
457}
458
459impl<E> fmt::Display for DisplayErrorChain<E>
460where
461    E: std::error::Error,
462{
463    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464        let mut writer = IndentWriter::new(self.initial_indent, f);
465        write!(writer, "{}", self.error)?;
466
467        let Some(mut cause) = self.error.source() else {
468            return Ok(());
469        };
470
471        write!(writer, "\n  caused by:")?;
472
473        loop {
474            writeln!(writer)?;
475            let mut indent = IndentWriter::new_skip_initial("    ", writer);
476            write!(indent, "  - {cause}")?;
477
478            let Some(next_cause) = cause.source() else {
479                break Ok(());
480            };
481
482            cause = next_cause;
483            writer = indent.into_inner();
484        }
485    }
486}
487
488/// An error was returned while managing a child process or reading its output.
489#[derive(Clone, Debug, Error)]
490pub enum ChildError {
491    /// An error occurred while reading from a child file descriptor.
492    #[error(transparent)]
493    Fd(#[from] ChildFdError),
494
495    /// An error occurred while reading the output of a setup script.
496    #[error(transparent)]
497    SetupScriptOutput(#[from] SetupScriptOutputError),
498}
499
500/// An error was returned while reading from child a file descriptor.
501#[derive(Clone, Debug, Error)]
502pub enum ChildFdError {
503    /// An error occurred while reading standard output.
504    #[error("error reading standard output")]
505    ReadStdout(#[source] Arc<std::io::Error>),
506
507    /// An error occurred while reading standard error.
508    #[error("error reading standard error")]
509    ReadStderr(#[source] Arc<std::io::Error>),
510
511    /// An error occurred while reading a combined stream.
512    #[error("error reading combined stream")]
513    ReadCombined(#[source] Arc<std::io::Error>),
514
515    /// An error occurred while waiting for the child process to exit.
516    #[error("error waiting for child process to exit")]
517    Wait(#[source] Arc<std::io::Error>),
518}
519
520/// An unknown test group was specified in the config.
521#[derive(Clone, Debug, Eq, PartialEq)]
522#[non_exhaustive]
523pub struct UnknownTestGroupError {
524    /// The name of the profile under which the unknown test group was found.
525    pub profile_name: String,
526
527    /// The name of the unknown test group.
528    pub name: TestGroup,
529}
530
531/// While parsing profile-specific config scripts, an unknown script was
532/// encountered.
533#[derive(Clone, Debug, Eq, PartialEq)]
534pub struct ProfileUnknownScriptError {
535    /// The name of the profile under which the errors occurred.
536    pub profile_name: String,
537
538    /// The name of the unknown script.
539    pub name: ScriptId,
540}
541
542/// While parsing profile-specific config scripts, a script of the wrong type
543/// was encountered.
544#[derive(Clone, Debug, Eq, PartialEq)]
545pub struct ProfileWrongConfigScriptTypeError {
546    /// The name of the profile under which the errors occurred.
547    pub profile_name: String,
548
549    /// The name of the config script.
550    pub name: ScriptId,
551
552    /// The script type that the user attempted to use the script as.
553    pub attempted: ProfileScriptType,
554
555    /// The script type that the script actually is.
556    pub actual: ScriptType,
557}
558
559/// While parsing profile-specific config scripts, a list-time-enabled script
560/// used a filter that can only be used at test run time.
561#[derive(Clone, Debug, Eq, PartialEq)]
562pub struct ProfileListScriptUsesRunFiltersError {
563    /// The name of the profile under which the errors occurred.
564    pub profile_name: String,
565
566    /// The name of the config script.
567    pub name: ScriptId,
568
569    /// The script type.
570    pub script_type: ProfileScriptType,
571
572    /// The filters that were used.
573    pub filters: BTreeSet<String>,
574}
575
576/// Errors that occurred while parsing `[[profile.*.scripts]]`.
577#[derive(Clone, Debug, Default)]
578pub struct ProfileScriptErrors {
579    /// The list of unknown script errors.
580    pub unknown_scripts: Vec<ProfileUnknownScriptError>,
581
582    /// The list of wrong script type errors.
583    pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
584
585    /// The list of list-time-enabled scripts that used a run-time filter.
586    pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
587}
588
589impl ProfileScriptErrors {
590    /// Returns true if there are no errors recorded.
591    pub fn is_empty(&self) -> bool {
592        self.unknown_scripts.is_empty()
593            && self.wrong_script_types.is_empty()
594            && self.list_scripts_using_run_filters.is_empty()
595    }
596}
597
598/// An error which indicates that a profile was requested but not known to nextest.
599#[derive(Clone, Debug, Error)]
600#[error("profile `{profile} not found (known profiles: {})`", .all_profiles.join(", "))]
601pub struct ProfileNotFound {
602    profile: String,
603    all_profiles: Vec<String>,
604}
605
606impl ProfileNotFound {
607    pub(crate) fn new(
608        profile: impl Into<String>,
609        all_profiles: impl IntoIterator<Item = impl Into<String>>,
610    ) -> Self {
611        let mut all_profiles: Vec<_> = all_profiles.into_iter().map(|s| s.into()).collect();
612        all_profiles.sort_unstable();
613        Self {
614            profile: profile.into(),
615            all_profiles,
616        }
617    }
618}
619
620/// An identifier is invalid.
621#[derive(Clone, Debug, Error, Eq, PartialEq)]
622pub enum InvalidIdentifier {
623    /// The identifier is empty.
624    #[error("identifier is empty")]
625    Empty,
626
627    /// The identifier is not in the correct Unicode format.
628    #[error("invalid identifier `{0}`")]
629    InvalidXid(SmolStr),
630
631    /// This tool identifier doesn't match the expected pattern.
632    #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
633    ToolIdentifierInvalidFormat(SmolStr),
634
635    /// One of the components of this tool identifier is empty.
636    #[error("tool identifier has empty component: `{0}`")]
637    ToolComponentEmpty(SmolStr),
638
639    /// The tool identifier is not in the correct Unicode format.
640    #[error("invalid tool identifier `{0}`")]
641    ToolIdentifierInvalidXid(SmolStr),
642}
643
644/// The name of a test group is invalid (not a valid identifier).
645#[derive(Clone, Debug, Error)]
646#[error("invalid custom test group name: {0}")]
647pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
648
649/// The name of a configuration script is invalid (not a valid identifier).
650#[derive(Clone, Debug, Error)]
651#[error("invalid configuration script name: {0}")]
652pub struct InvalidConfigScriptName(pub InvalidIdentifier);
653
654/// Error returned while parsing a [`ToolConfigFile`](crate::config::core::ToolConfigFile) value.
655#[derive(Clone, Debug, Error)]
656pub enum ToolConfigFileParseError {
657    #[error(
658        "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
659    )]
660    /// The input was not in the format "tool:path".
661    InvalidFormat {
662        /// The input that failed to parse.
663        input: String,
664    },
665
666    /// The tool name was empty.
667    #[error("tool-config-file has empty tool name: {input}")]
668    EmptyToolName {
669        /// The input that failed to parse.
670        input: String,
671    },
672
673    /// The config file path was empty.
674    #[error("tool-config-file has empty config file path: {input}")]
675    EmptyConfigFile {
676        /// The input that failed to parse.
677        input: String,
678    },
679
680    /// The config file was not an absolute path.
681    #[error("tool-config-file is not an absolute path: {config_file}")]
682    ConfigFileNotAbsolute {
683        /// The file name that wasn't absolute.
684        config_file: Utf8PathBuf,
685    },
686}
687
688/// Error returned while parsing a [`MaxFail`](crate::config::elements::MaxFail) input.
689#[derive(Clone, Debug, Error)]
690#[error("unrecognized value for max-fail: {reason}")]
691pub struct MaxFailParseError {
692    /// The reason parsing failed.
693    pub reason: String,
694}
695
696impl MaxFailParseError {
697    pub(crate) fn new(reason: impl Into<String>) -> Self {
698        Self {
699            reason: reason.into(),
700        }
701    }
702}
703
704/// Error returned while parsing a [`StressCount`](crate::runner::StressCount) input.
705#[derive(Clone, Debug, Error)]
706#[error(
707    "unrecognized value for stress-count: {input}\n\
708     (hint: expected either a positive integer or \"infinite\")"
709)]
710pub struct StressCountParseError {
711    /// The input that failed to parse.
712    pub input: String,
713}
714
715impl StressCountParseError {
716    pub(crate) fn new(input: impl Into<String>) -> Self {
717        Self {
718            input: input.into(),
719        }
720    }
721}
722
723/// An error that occurred while parsing a debugger command.
724#[derive(Clone, Debug, Error)]
725#[non_exhaustive]
726pub enum DebuggerCommandParseError {
727    /// The command string could not be parsed as shell words.
728    #[error(transparent)]
729    ShellWordsParse(shell_words::ParseError),
730
731    /// The command was empty.
732    #[error("debugger command cannot be empty")]
733    EmptyCommand,
734}
735
736/// An error that occurred while parsing a tracer command.
737#[derive(Clone, Debug, Error)]
738#[non_exhaustive]
739pub enum TracerCommandParseError {
740    /// The command string could not be parsed as shell words.
741    #[error(transparent)]
742    ShellWordsParse(shell_words::ParseError),
743
744    /// The command was empty.
745    #[error("tracer command cannot be empty")]
746    EmptyCommand,
747}
748
749/// Error returned while parsing a [`TestThreads`](crate::config::elements::TestThreads) value.
750#[derive(Clone, Debug, Error)]
751#[error(
752    "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
753)]
754pub struct TestThreadsParseError {
755    /// The input that failed to parse.
756    pub input: String,
757}
758
759impl TestThreadsParseError {
760    pub(crate) fn new(input: impl Into<String>) -> Self {
761        Self {
762            input: input.into(),
763        }
764    }
765}
766
767/// An error that occurs while parsing a
768/// [`PartitionerBuilder`](crate::partition::PartitionerBuilder) input.
769#[derive(Clone, Debug, Error)]
770pub struct PartitionerBuilderParseError {
771    expected_format: Option<&'static str>,
772    message: Cow<'static, str>,
773}
774
775impl PartitionerBuilderParseError {
776    pub(crate) fn new(
777        expected_format: Option<&'static str>,
778        message: impl Into<Cow<'static, str>>,
779    ) -> Self {
780        Self {
781            expected_format,
782            message: message.into(),
783        }
784    }
785}
786
787impl fmt::Display for PartitionerBuilderParseError {
788    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
789        match self.expected_format {
790            Some(format) => {
791                write!(
792                    f,
793                    "partition must be in the format \"{}\":\n{}",
794                    format, self.message
795                )
796            }
797            None => write!(f, "{}", self.message),
798        }
799    }
800}
801
802/// An error that occurs while operating on a
803/// [`TestFilterBuilder`](crate::test_filter::TestFilterBuilder).
804#[derive(Clone, Debug, Error)]
805pub enum TestFilterBuilderError {
806    /// An error that occurred while constructing test filters.
807    #[error("error constructing test filters")]
808    Construct {
809        /// The underlying error.
810        #[from]
811        error: aho_corasick::BuildError,
812    },
813}
814
815/// An error occurred in [`PathMapper::new`](crate::reuse_build::PathMapper::new).
816#[derive(Debug, Error)]
817pub enum PathMapperConstructError {
818    /// An error occurred while canonicalizing a directory.
819    #[error("{kind} `{input}` failed to canonicalize")]
820    Canonicalization {
821        /// The directory that failed to be canonicalized.
822        kind: PathMapperConstructKind,
823
824        /// The input provided.
825        input: Utf8PathBuf,
826
827        /// The error that occurred.
828        #[source]
829        err: std::io::Error,
830    },
831    /// The canonicalized path isn't valid UTF-8.
832    #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
833    NonUtf8Path {
834        /// The directory that failed to be canonicalized.
835        kind: PathMapperConstructKind,
836
837        /// The input provided.
838        input: Utf8PathBuf,
839
840        /// The underlying error.
841        #[source]
842        err: FromPathBufError,
843    },
844    /// A provided input is not a directory.
845    #[error("{kind} `{canonicalized_path}` is not a directory")]
846    NotADirectory {
847        /// The directory that failed to be canonicalized.
848        kind: PathMapperConstructKind,
849
850        /// The input provided.
851        input: Utf8PathBuf,
852
853        /// The canonicalized path that wasn't a directory.
854        canonicalized_path: Utf8PathBuf,
855    },
856}
857
858impl PathMapperConstructError {
859    /// The kind of directory.
860    pub fn kind(&self) -> PathMapperConstructKind {
861        match self {
862            Self::Canonicalization { kind, .. }
863            | Self::NonUtf8Path { kind, .. }
864            | Self::NotADirectory { kind, .. } => *kind,
865        }
866    }
867
868    /// The input path that failed.
869    pub fn input(&self) -> &Utf8Path {
870        match self {
871            Self::Canonicalization { input, .. }
872            | Self::NonUtf8Path { input, .. }
873            | Self::NotADirectory { input, .. } => input,
874        }
875    }
876}
877
878/// The kind of directory that failed to be read in
879/// [`PathMapper::new`](crate::reuse_build::PathMapper::new).
880///
881/// Returned as part of [`PathMapperConstructError`].
882#[derive(Copy, Clone, Debug, PartialEq, Eq)]
883pub enum PathMapperConstructKind {
884    /// The workspace root.
885    WorkspaceRoot,
886
887    /// The target directory.
888    TargetDir,
889}
890
891impl fmt::Display for PathMapperConstructKind {
892    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
893        match self {
894            Self::WorkspaceRoot => write!(f, "remapped workspace root"),
895            Self::TargetDir => write!(f, "remapped target directory"),
896        }
897    }
898}
899
900/// An error that occurs while parsing Rust build metadata from a summary.
901#[derive(Debug, Error)]
902pub enum RustBuildMetaParseError {
903    /// An error occurred while deserializing the platform.
904    #[error("error deserializing platform from build metadata")]
905    PlatformDeserializeError(#[from] target_spec::Error),
906
907    /// The host platform could not be determined.
908    #[error("the host platform could not be determined")]
909    DetectBuildTargetError(#[source] target_spec::Error),
910
911    /// The build metadata includes features unsupported.
912    #[error("unsupported features in the build metadata: {message}")]
913    Unsupported {
914        /// The detailed error message.
915        message: String,
916    },
917}
918
919/// Error returned when a user-supplied format version fails to be parsed to a
920/// valid and supported version.
921#[derive(Clone, Debug, thiserror::Error)]
922#[error("invalid format version: {input}")]
923pub struct FormatVersionError {
924    /// The input that failed to parse.
925    pub input: String,
926    /// The underlying error.
927    #[source]
928    pub error: FormatVersionErrorInner,
929}
930
931/// The different errors that can occur when parsing and validating a format version.
932#[derive(Clone, Debug, thiserror::Error)]
933pub enum FormatVersionErrorInner {
934    /// The input did not have a valid syntax.
935    #[error("expected format version in form of `{expected}`")]
936    InvalidFormat {
937        /// The expected pseudo format.
938        expected: &'static str,
939    },
940    /// A decimal integer was expected but could not be parsed.
941    #[error("version component `{which}` could not be parsed as an integer")]
942    InvalidInteger {
943        /// Which component was invalid.
944        which: &'static str,
945        /// The parse failure.
946        #[source]
947        err: std::num::ParseIntError,
948    },
949    /// The version component was not within the expected range.
950    #[error("version component `{which}` value {value} is out of range {range:?}")]
951    InvalidValue {
952        /// The component which was out of range.
953        which: &'static str,
954        /// The value that was parsed.
955        value: u8,
956        /// The range of valid values for the component.
957        range: std::ops::Range<u8>,
958    },
959}
960
961/// An error that occurs in [`BinaryList::from_messages`](crate::list::BinaryList::from_messages) or
962/// [`RustTestArtifact::from_binary_list`](crate::list::RustTestArtifact::from_binary_list).
963#[derive(Debug, Error)]
964#[non_exhaustive]
965pub enum FromMessagesError {
966    /// An error occurred while reading Cargo's JSON messages.
967    #[error("error reading Cargo JSON messages")]
968    ReadMessages(#[source] std::io::Error),
969
970    /// An error occurred while querying the package graph.
971    #[error("error querying package graph")]
972    PackageGraph(#[source] guppy::Error),
973
974    /// A target in the package graph was missing `kind` information.
975    #[error("missing kind for target {binary_name} in package {package_name}")]
976    MissingTargetKind {
977        /// The name of the malformed package.
978        package_name: String,
979        /// The name of the malformed target within the package.
980        binary_name: String,
981    },
982}
983
984/// An error that occurs while parsing test list output.
985#[derive(Debug, Error)]
986#[non_exhaustive]
987pub enum CreateTestListError {
988    /// The proposed cwd for a process is not a directory.
989    #[error(
990        "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
991         (hint: ensure project source is available at this location)"
992    )]
993    CwdIsNotDir {
994        /// The binary ID for which the current directory wasn't found.
995        binary_id: RustBinaryId,
996
997        /// The current directory that wasn't found.
998        cwd: Utf8PathBuf,
999    },
1000
1001    /// Running a command to gather the list of tests failed to execute.
1002    #[error(
1003        "for `{binary_id}`, running command `{}` failed to execute",
1004        shell_words::join(command)
1005    )]
1006    CommandExecFail {
1007        /// The binary ID for which gathering the list of tests failed.
1008        binary_id: RustBinaryId,
1009
1010        /// The command that was run.
1011        command: Vec<String>,
1012
1013        /// The underlying error.
1014        #[source]
1015        error: std::io::Error,
1016    },
1017
1018    /// Running a command to gather the list of tests failed failed with a non-zero exit code.
1019    #[error(
1020        "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1021        shell_words::join(command),
1022        display_exited_with(*exit_status),
1023        String::from_utf8_lossy(stdout),
1024        String::from_utf8_lossy(stderr),
1025    )]
1026    CommandFail {
1027        /// The binary ID for which gathering the list of tests failed.
1028        binary_id: RustBinaryId,
1029
1030        /// The command that was run.
1031        command: Vec<String>,
1032
1033        /// The exit status with which the command failed.
1034        exit_status: ExitStatus,
1035
1036        /// Standard output for the command.
1037        stdout: Vec<u8>,
1038
1039        /// Standard error for the command.
1040        stderr: Vec<u8>,
1041    },
1042
1043    /// Running a command to gather the list of tests produced a non-UTF-8 standard output.
1044    #[error(
1045        "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1046        shell_words::join(command),
1047        String::from_utf8_lossy(stdout),
1048        String::from_utf8_lossy(stderr)
1049    )]
1050    CommandNonUtf8 {
1051        /// The binary ID for which gathering the list of tests failed.
1052        binary_id: RustBinaryId,
1053
1054        /// The command that was run.
1055        command: Vec<String>,
1056
1057        /// Standard output for the command.
1058        stdout: Vec<u8>,
1059
1060        /// Standard error for the command.
1061        stderr: Vec<u8>,
1062    },
1063
1064    /// An error occurred while parsing a line in the test output.
1065    #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1066    ParseLine {
1067        /// The binary ID for which parsing the list of tests failed.
1068        binary_id: RustBinaryId,
1069
1070        /// A descriptive message.
1071        message: Cow<'static, str>,
1072
1073        /// The full output.
1074        full_output: String,
1075    },
1076
1077    /// An error occurred while joining paths for dynamic libraries.
1078    #[error(
1079        "error joining dynamic library paths for {}: [{}]",
1080        dylib_path_envvar(),
1081        itertools::join(.new_paths, ", ")
1082    )]
1083    DylibJoinPaths {
1084        /// New paths attempted to be added to the dynamic library environment variable.
1085        new_paths: Vec<Utf8PathBuf>,
1086
1087        /// The underlying error.
1088        #[source]
1089        error: JoinPathsError,
1090    },
1091
1092    /// Creating a Tokio runtime failed.
1093    #[error("error creating Tokio runtime")]
1094    TokioRuntimeCreate(#[source] std::io::Error),
1095}
1096
1097impl CreateTestListError {
1098    pub(crate) fn parse_line(
1099        binary_id: RustBinaryId,
1100        message: impl Into<Cow<'static, str>>,
1101        full_output: impl Into<String>,
1102    ) -> Self {
1103        Self::ParseLine {
1104            binary_id,
1105            message: message.into(),
1106            full_output: full_output.into(),
1107        }
1108    }
1109
1110    pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1111        Self::DylibJoinPaths { new_paths, error }
1112    }
1113}
1114
1115/// An error that occurs while writing list output.
1116#[derive(Debug, Error)]
1117#[non_exhaustive]
1118pub enum WriteTestListError {
1119    /// An error occurred while writing the list to the provided output.
1120    #[error("error writing to output")]
1121    Io(#[source] std::io::Error),
1122
1123    /// An error occurred while serializing JSON, or while writing it to the provided output.
1124    #[error("error serializing to JSON")]
1125    Json(#[source] serde_json::Error),
1126}
1127
1128/// An error occurred while configuring handles.
1129///
1130/// Only relevant on Windows.
1131#[derive(Debug, Error)]
1132pub enum ConfigureHandleInheritanceError {
1133    /// An error occurred. This can only happen on Windows.
1134    #[cfg(windows)]
1135    #[error("error configuring handle inheritance")]
1136    WindowsError(#[from] std::io::Error),
1137}
1138
1139/// An error that occurs while building the test runner.
1140#[derive(Debug, Error)]
1141#[non_exhaustive]
1142pub enum TestRunnerBuildError {
1143    /// An error occurred while creating the Tokio runtime.
1144    #[error("error creating Tokio runtime")]
1145    TokioRuntimeCreate(#[source] std::io::Error),
1146
1147    /// An error occurred while setting up signals.
1148    #[error("error setting up signals")]
1149    SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1150}
1151
1152/// Errors that occurred while managing test runner Tokio tasks.
1153#[derive(Debug, Error)]
1154pub struct TestRunnerExecuteErrors<E> {
1155    /// An error that occurred while reporting results to the reporter callback.
1156    pub report_error: Option<E>,
1157
1158    /// Join errors (typically panics) that occurred while running the test
1159    /// runner.
1160    pub join_errors: Vec<tokio::task::JoinError>,
1161}
1162
1163impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1165        if let Some(report_error) = &self.report_error {
1166            write!(f, "error reporting results: {report_error}")?;
1167        }
1168
1169        if !self.join_errors.is_empty() {
1170            if self.report_error.is_some() {
1171                write!(f, "; ")?;
1172            }
1173
1174            write!(f, "errors joining tasks: ")?;
1175
1176            for (i, join_error) in self.join_errors.iter().enumerate() {
1177                if i > 0 {
1178                    write!(f, ", ")?;
1179                }
1180
1181                write!(f, "{join_error}")?;
1182            }
1183        }
1184
1185        Ok(())
1186    }
1187}
1188
1189/// Represents an unknown archive format.
1190///
1191/// Returned by [`ArchiveFormat::autodetect`].
1192#[derive(Debug, Error)]
1193#[error(
1194    "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1195    supported_extensions()
1196)]
1197pub struct UnknownArchiveFormat {
1198    /// The name of the archive file without any leading components.
1199    pub file_name: String,
1200}
1201
1202fn supported_extensions() -> String {
1203    ArchiveFormat::SUPPORTED_FORMATS
1204        .iter()
1205        .map(|(extension, _)| *extension)
1206        .join(", ")
1207}
1208
1209/// An error that occurs while archiving data.
1210#[derive(Debug, Error)]
1211#[non_exhaustive]
1212pub enum ArchiveCreateError {
1213    /// An error occurred while creating the binary list to be written.
1214    #[error("error creating binary list")]
1215    CreateBinaryList(#[source] WriteTestListError),
1216
1217    /// An extra path was missing.
1218    #[error("extra path `{}` not found", .redactor.redact_path(path))]
1219    MissingExtraPath {
1220        /// The path that was missing.
1221        path: Utf8PathBuf,
1222
1223        /// A redactor for the path.
1224        ///
1225        /// (This should eventually move to being a field for a wrapper struct, but it's okay for
1226        /// now.)
1227        redactor: Redactor,
1228    },
1229
1230    /// An error occurred while reading data from a file on disk.
1231    #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1232    InputFileRead {
1233        /// The step that the archive errored at.
1234        step: ArchiveStep,
1235
1236        /// The name of the file that could not be read.
1237        path: Utf8PathBuf,
1238
1239        /// Whether this is a directory. `None` means the status was unknown.
1240        is_dir: Option<bool>,
1241
1242        /// The error that occurred.
1243        #[source]
1244        error: std::io::Error,
1245    },
1246
1247    /// An error occurred while reading entries from a directory on disk.
1248    #[error("error reading directory entry from `{path}")]
1249    DirEntryRead {
1250        /// The name of the directory from which entries couldn't be read.
1251        path: Utf8PathBuf,
1252
1253        /// The error that occurred.
1254        #[source]
1255        error: std::io::Error,
1256    },
1257
1258    /// An error occurred while writing data to the output file.
1259    #[error("error writing to archive")]
1260    OutputArchiveIo(#[source] std::io::Error),
1261
1262    /// An error occurred in the reporter.
1263    #[error("error reporting archive status")]
1264    ReporterIo(#[source] std::io::Error),
1265}
1266
1267fn kind_str(is_dir: Option<bool>) -> &'static str {
1268    match is_dir {
1269        Some(true) => "directory",
1270        Some(false) => "file",
1271        None => "path",
1272    }
1273}
1274
1275/// An error occurred while materializing a metadata path.
1276#[derive(Debug, Error)]
1277pub enum MetadataMaterializeError {
1278    /// An I/O error occurred while reading the metadata file.
1279    #[error("I/O error reading metadata file `{path}`")]
1280    Read {
1281        /// The path that was being read.
1282        path: Utf8PathBuf,
1283
1284        /// The error that occurred.
1285        #[source]
1286        error: std::io::Error,
1287    },
1288
1289    /// A JSON deserialization error occurred while reading the metadata file.
1290    #[error("error deserializing metadata file `{path}`")]
1291    Deserialize {
1292        /// The path that was being read.
1293        path: Utf8PathBuf,
1294
1295        /// The error that occurred.
1296        #[source]
1297        error: serde_json::Error,
1298    },
1299
1300    /// An error occurred while parsing Rust build metadata.
1301    #[error("error parsing Rust build metadata from `{path}`")]
1302    RustBuildMeta {
1303        /// The path that was deserialized.
1304        path: Utf8PathBuf,
1305
1306        /// The error that occurred.
1307        #[source]
1308        error: RustBuildMetaParseError,
1309    },
1310
1311    /// An error occurred converting data into a `PackageGraph`.
1312    #[error("error building package graph from `{path}`")]
1313    PackageGraphConstruct {
1314        /// The path that was deserialized.
1315        path: Utf8PathBuf,
1316
1317        /// The error that occurred.
1318        #[source]
1319        error: guppy::Error,
1320    },
1321}
1322
1323/// An error occurred while reading a file.
1324///
1325/// Returned as part of both [`ArchiveCreateError`] and [`ArchiveExtractError`].
1326#[derive(Debug, Error)]
1327#[non_exhaustive]
1328pub enum ArchiveReadError {
1329    /// An I/O error occurred while reading the archive.
1330    #[error("I/O error reading archive")]
1331    Io(#[source] std::io::Error),
1332
1333    /// A path wasn't valid UTF-8.
1334    #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1335    NonUtf8Path(Vec<u8>),
1336
1337    /// A file path within the archive didn't begin with "target/".
1338    #[error("path in archive `{0}` doesn't start with `target/`")]
1339    NoTargetPrefix(Utf8PathBuf),
1340
1341    /// A file path within the archive had an invalid component within it.
1342    #[error("path in archive `{path}` contains an invalid component `{component}`")]
1343    InvalidComponent {
1344        /// The path that had an invalid component.
1345        path: Utf8PathBuf,
1346
1347        /// The invalid component.
1348        component: String,
1349    },
1350
1351    /// An error occurred while reading a checksum.
1352    #[error("corrupted archive: checksum read error for path `{path}`")]
1353    ChecksumRead {
1354        /// The path for which there was a checksum read error.
1355        path: Utf8PathBuf,
1356
1357        /// The error that occurred.
1358        #[source]
1359        error: std::io::Error,
1360    },
1361
1362    /// An entry had an invalid checksum.
1363    #[error("corrupted archive: invalid checksum for path `{path}`")]
1364    InvalidChecksum {
1365        /// The path that had an invalid checksum.
1366        path: Utf8PathBuf,
1367
1368        /// The expected checksum.
1369        expected: u32,
1370
1371        /// The actual checksum.
1372        actual: u32,
1373    },
1374
1375    /// A metadata file wasn't found.
1376    #[error("metadata file `{0}` not found in archive")]
1377    MetadataFileNotFound(&'static Utf8Path),
1378
1379    /// An error occurred while deserializing a metadata file.
1380    #[error("error deserializing metadata file `{path}` in archive")]
1381    MetadataDeserializeError {
1382        /// The name of the metadata file.
1383        path: &'static Utf8Path,
1384
1385        /// The deserialize error.
1386        #[source]
1387        error: serde_json::Error,
1388    },
1389
1390    /// An error occurred while building a `PackageGraph`.
1391    #[error("error building package graph from `{path}` in archive")]
1392    PackageGraphConstructError {
1393        /// The name of the metadata file.
1394        path: &'static Utf8Path,
1395
1396        /// The error.
1397        #[source]
1398        error: guppy::Error,
1399    },
1400}
1401
1402/// An error occurred while extracting a file.
1403///
1404/// Returned by [`extract_archive`](crate::reuse_build::ReuseBuildInfo::extract_archive).
1405#[derive(Debug, Error)]
1406#[non_exhaustive]
1407pub enum ArchiveExtractError {
1408    /// An error occurred while creating a temporary directory.
1409    #[error("error creating temporary directory")]
1410    TempDirCreate(#[source] std::io::Error),
1411
1412    /// An error occurred while canonicalizing the destination directory.
1413    #[error("error canonicalizing destination directory `{dir}`")]
1414    DestDirCanonicalization {
1415        /// The directory that failed to canonicalize.
1416        dir: Utf8PathBuf,
1417
1418        /// The error that occurred.
1419        #[source]
1420        error: std::io::Error,
1421    },
1422
1423    /// The destination already exists and `--overwrite` was not passed in.
1424    #[error("destination `{0}` already exists")]
1425    DestinationExists(Utf8PathBuf),
1426
1427    /// An error occurred while reading the archive.
1428    #[error("error reading archive")]
1429    Read(#[source] ArchiveReadError),
1430
1431    /// An error occurred while deserializing Rust build metadata.
1432    #[error("error deserializing Rust build metadata")]
1433    RustBuildMeta(#[from] RustBuildMetaParseError),
1434
1435    /// An error occurred while writing out a file to the destination directory.
1436    #[error("error writing file `{path}` to disk")]
1437    WriteFile {
1438        /// The path that we couldn't write out.
1439        path: Utf8PathBuf,
1440
1441        /// The error that occurred.
1442        #[source]
1443        error: std::io::Error,
1444    },
1445
1446    /// An error occurred while reporting the extraction status.
1447    #[error("error reporting extract status")]
1448    ReporterIo(std::io::Error),
1449}
1450
1451/// An error that occurs while writing an event.
1452#[derive(Debug, Error)]
1453#[non_exhaustive]
1454pub enum WriteEventError {
1455    /// An error occurred while writing the event to the provided output.
1456    #[error("error writing to output")]
1457    Io(#[source] std::io::Error),
1458
1459    /// An error occurred while operating on the file system.
1460    #[error("error operating on path {file}")]
1461    Fs {
1462        /// The file being operated on.
1463        file: Utf8PathBuf,
1464
1465        /// The underlying IO error.
1466        #[source]
1467        error: std::io::Error,
1468    },
1469
1470    /// An error occurred while producing JUnit XML.
1471    #[error("error writing JUnit output to {file}")]
1472    Junit {
1473        /// The output file.
1474        file: Utf8PathBuf,
1475
1476        /// The underlying error.
1477        #[source]
1478        error: quick_junit::SerializeError,
1479    },
1480}
1481
1482/// An error occurred while constructing a [`CargoConfigs`](crate::cargo_config::CargoConfigs)
1483/// instance.
1484#[derive(Debug, Error)]
1485#[non_exhaustive]
1486pub enum CargoConfigError {
1487    /// Failed to retrieve the current directory.
1488    #[error("failed to retrieve current directory")]
1489    GetCurrentDir(#[source] std::io::Error),
1490
1491    /// The current directory was invalid UTF-8.
1492    #[error("current directory is invalid UTF-8")]
1493    CurrentDirInvalidUtf8(#[source] FromPathBufError),
1494
1495    /// Parsing a CLI config option failed.
1496    #[error("failed to parse --config argument `{config_str}` as TOML")]
1497    CliConfigParseError {
1498        /// The CLI config option.
1499        config_str: String,
1500
1501        /// The error that occurred trying to parse the config.
1502        #[source]
1503        error: toml_edit::TomlError,
1504    },
1505
1506    /// Deserializing a CLI config option into domain types failed.
1507    #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1508    CliConfigDeError {
1509        /// The CLI config option.
1510        config_str: String,
1511
1512        /// The error that occurred trying to deserialize the config.
1513        #[source]
1514        error: toml_edit::de::Error,
1515    },
1516
1517    /// A CLI config option is not in the dotted key format.
1518    #[error(
1519        "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1520    )]
1521    InvalidCliConfig {
1522        /// The CLI config option.
1523        config_str: String,
1524
1525        /// The reason why this Cargo CLI config is invalid.
1526        #[source]
1527        reason: InvalidCargoCliConfigReason,
1528    },
1529
1530    /// A non-UTF-8 path was encountered.
1531    #[error("non-UTF-8 path encountered")]
1532    NonUtf8Path(#[source] FromPathBufError),
1533
1534    /// Failed to retrieve the Cargo home directory.
1535    #[error("failed to retrieve the Cargo home directory")]
1536    GetCargoHome(#[source] std::io::Error),
1537
1538    /// Failed to canonicalize a path
1539    #[error("failed to canonicalize path `{path}")]
1540    FailedPathCanonicalization {
1541        /// The path that failed to canonicalize
1542        path: Utf8PathBuf,
1543
1544        /// The error the occurred during canonicalization
1545        #[source]
1546        error: std::io::Error,
1547    },
1548
1549    /// Failed to read config file
1550    #[error("failed to read config at `{path}`")]
1551    ConfigReadError {
1552        /// The path of the config file
1553        path: Utf8PathBuf,
1554
1555        /// The error that occurred trying to read the config file
1556        #[source]
1557        error: std::io::Error,
1558    },
1559
1560    /// Failed to deserialize config file
1561    #[error(transparent)]
1562    ConfigParseError(#[from] Box<CargoConfigParseError>),
1563}
1564
1565/// Failed to deserialize config file
1566///
1567/// We introduce this extra indirection, because of the `clippy::result_large_err` rule on Windows.
1568#[derive(Debug, Error)]
1569#[error("failed to parse config at `{path}`")]
1570pub struct CargoConfigParseError {
1571    /// The path of the config file
1572    pub path: Utf8PathBuf,
1573
1574    /// The error that occurred trying to deserialize the config file
1575    #[source]
1576    pub error: toml::de::Error,
1577}
1578
1579/// The reason an invalid CLI config failed.
1580///
1581/// Part of [`CargoConfigError::InvalidCliConfig`].
1582#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1583#[non_exhaustive]
1584pub enum InvalidCargoCliConfigReason {
1585    /// The argument is not a TOML dotted key expression.
1586    #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1587    NotDottedKv,
1588
1589    /// The argument includes non-whitespace decoration.
1590    #[error("includes non-whitespace decoration")]
1591    IncludesNonWhitespaceDecoration,
1592
1593    /// The argument sets a value to an inline table.
1594    #[error("sets a value to an inline table, which is not accepted")]
1595    SetsValueToInlineTable,
1596
1597    /// The argument sets a value to an array of tables.
1598    #[error("sets a value to an array of tables, which is not accepted")]
1599    SetsValueToArrayOfTables,
1600
1601    /// The argument doesn't provide a value.
1602    #[error("doesn't provide a value")]
1603    DoesntProvideValue,
1604}
1605
1606/// The host platform couldn't be detected.
1607#[derive(Debug, Error)]
1608pub enum HostPlatformDetectError {
1609    /// Spawning `rustc -vV` failed, and detecting the build target failed as
1610    /// well.
1611    #[error(
1612        "error spawning `rustc -vV`, and detecting the build \
1613         target failed as well\n\
1614         - rustc spawn error: {}\n\
1615         - build target error: {}\n",
1616        DisplayErrorChain::new_with_initial_indent("  ", error),
1617        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
1618    )]
1619    RustcVvSpawnError {
1620        /// The error.
1621        error: std::io::Error,
1622
1623        /// The error that occurred while detecting the build target.
1624        build_target_error: Box<target_spec::Error>,
1625    },
1626
1627    /// `rustc -vV` exited with a non-zero code, and detecting the build target
1628    /// failed as well.
1629    #[error(
1630        "`rustc -vV` failed with {}, and detecting the \
1631         build target failed as well\n\
1632         - `rustc -vV` stdout:\n{}\n\
1633         - `rustc -vV` stderr:\n{}\n\
1634         - build target error:\n{}\n",
1635        status,
1636        Indented { item: String::from_utf8_lossy(stdout), indent: "  " },
1637        Indented { item: String::from_utf8_lossy(stderr), indent: "  " },
1638        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
1639    )]
1640    RustcVvFailed {
1641        /// The status.
1642        status: ExitStatus,
1643
1644        /// The standard output from `rustc -vV`.
1645        stdout: Vec<u8>,
1646
1647        /// The standard error from `rustc -vV`.
1648        stderr: Vec<u8>,
1649
1650        /// The error that occurred while detecting the build target.
1651        build_target_error: Box<target_spec::Error>,
1652    },
1653
1654    /// Parsing the host platform failed, and detecting the build target failed
1655    /// as well.
1656    #[error(
1657        "parsing `rustc -vV` output failed, and detecting the build target \
1658         failed as well\n\
1659         - host platform error:\n{}\n\
1660         - build target error:\n{}\n",
1661        DisplayErrorChain::new_with_initial_indent("  ", host_platform_error),
1662        DisplayErrorChain::new_with_initial_indent("  ", build_target_error)
1663    )]
1664    HostPlatformParseError {
1665        /// The error that occurred while parsing the host platform.
1666        host_platform_error: Box<target_spec::Error>,
1667
1668        /// The error that occurred while detecting the build target.
1669        build_target_error: Box<target_spec::Error>,
1670    },
1671
1672    /// Test-only code: `rustc -vV` was not queried, and detecting the build
1673    /// target failed as well.
1674    #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
1675    BuildTargetError {
1676        /// The error that occurred while detecting the build target.
1677        #[source]
1678        build_target_error: Box<target_spec::Error>,
1679    },
1680}
1681
1682/// An error occurred while determining the cross-compiling target triple.
1683#[derive(Debug, Error)]
1684pub enum TargetTripleError {
1685    /// The environment variable contained non-utf8 content
1686    #[error(
1687        "environment variable '{}' contained non-UTF-8 data",
1688        TargetTriple::CARGO_BUILD_TARGET_ENV
1689    )]
1690    InvalidEnvironmentVar,
1691
1692    /// An error occurred while deserializing the platform.
1693    #[error("error deserializing target triple from {source}")]
1694    TargetSpecError {
1695        /// The source from which the triple couldn't be parsed.
1696        source: TargetTripleSource,
1697
1698        /// The error that occurred parsing the triple.
1699        #[source]
1700        error: target_spec::Error,
1701    },
1702
1703    /// For a custom platform, reading the target path failed.
1704    #[error("target path `{path}` is not a valid file")]
1705    TargetPathReadError {
1706        /// The source from which the triple couldn't be parsed.
1707        source: TargetTripleSource,
1708
1709        /// The path that we tried to read.
1710        path: Utf8PathBuf,
1711
1712        /// The error that occurred parsing the triple.
1713        #[source]
1714        error: std::io::Error,
1715    },
1716
1717    /// Failed to create a temporary directory for a custom platform.
1718    #[error(
1719        "for custom platform obtained from {source}, \
1720         failed to create temporary directory for custom platform"
1721    )]
1722    CustomPlatformTempDirError {
1723        /// The source of the target triple.
1724        source: TargetTripleSource,
1725
1726        /// The error that occurred during the create.
1727        #[source]
1728        error: std::io::Error,
1729    },
1730
1731    /// Failed to write a custom platform to disk.
1732    #[error(
1733        "for custom platform obtained from {source}, \
1734         failed to write JSON to temporary path `{path}`"
1735    )]
1736    CustomPlatformWriteError {
1737        /// The source of the target triple.
1738        source: TargetTripleSource,
1739
1740        /// The path that we tried to write to.
1741        path: Utf8PathBuf,
1742
1743        /// The error that occurred during the write.
1744        #[source]
1745        error: std::io::Error,
1746    },
1747
1748    /// Failed to close a temporary directory for an extracted custom platform.
1749    #[error(
1750        "for custom platform obtained from {source}, \
1751         failed to close temporary directory `{dir_path}`"
1752    )]
1753    CustomPlatformCloseError {
1754        /// The source of the target triple.
1755        source: TargetTripleSource,
1756
1757        /// The directory that we tried to delete.
1758        dir_path: Utf8PathBuf,
1759
1760        /// The error that occurred during the close.
1761        #[source]
1762        error: std::io::Error,
1763    },
1764}
1765
1766impl TargetTripleError {
1767    /// Returns a [`miette::Report`] for the source, if available.
1768    ///
1769    /// This should be preferred over [`std::error::Error::source`] if
1770    /// available.
1771    pub fn source_report(&self) -> Option<miette::Report> {
1772        match self {
1773            Self::TargetSpecError { error, .. } => {
1774                Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
1775            }
1776            // The remaining types are covered via the error source path.
1777            TargetTripleError::InvalidEnvironmentVar
1778            | TargetTripleError::TargetPathReadError { .. }
1779            | TargetTripleError::CustomPlatformTempDirError { .. }
1780            | TargetTripleError::CustomPlatformWriteError { .. }
1781            | TargetTripleError::CustomPlatformCloseError { .. } => None,
1782        }
1783    }
1784}
1785
1786/// An error occurred determining the target runner
1787#[derive(Debug, Error)]
1788pub enum TargetRunnerError {
1789    /// An environment variable contained non-utf8 content
1790    #[error("environment variable '{0}' contained non-UTF-8 data")]
1791    InvalidEnvironmentVar(String),
1792
1793    /// An environment variable or config key was found that matches the target
1794    /// triple, but it didn't actually contain a binary
1795    #[error("runner '{key}' = '{value}' did not contain a runner binary")]
1796    BinaryNotSpecified {
1797        /// The source under consideration.
1798        key: PlatformRunnerSource,
1799
1800        /// The value that was read from the key
1801        value: String,
1802    },
1803}
1804
1805/// An error that occurred while setting up the signal handler.
1806#[derive(Debug, Error)]
1807#[error("error setting up signal handler")]
1808pub struct SignalHandlerSetupError(#[from] std::io::Error);
1809
1810/// An error occurred while showing test groups.
1811#[derive(Debug, Error)]
1812pub enum ShowTestGroupsError {
1813    /// Unknown test groups were specified.
1814    #[error(
1815        "unknown test groups specified: {}\n(known groups: {})",
1816        unknown_groups.iter().join(", "),
1817        known_groups.iter().join(", "),
1818    )]
1819    UnknownGroups {
1820        /// The unknown test groups.
1821        unknown_groups: BTreeSet<TestGroup>,
1822
1823        /// All known test groups.
1824        known_groups: BTreeSet<TestGroup>,
1825    },
1826}
1827
1828/// An error occurred while processing profile's inherits setting
1829#[derive(Debug, Error, PartialEq, Eq, Hash)]
1830pub enum InheritsError {
1831    /// The default profile should not be able to inherit from other profiles
1832    #[error("the {} profile should not inherit from other profiles", .0)]
1833    DefaultProfileInheritance(String),
1834    /// An unknown/unfound profile was detected to inherit from in profile configuration
1835    #[error("profile {} inherits from an unknown profile {}", .0, .1)]
1836    UnknownInheritance(String, String),
1837    /// A self referential inheritance is detected from this profile
1838    #[error("a self referential inheritance is detected from profile: {}", .0)]
1839    SelfReferentialInheritance(String),
1840    /// An inheritance cycle was detected in the profile configuration.
1841    #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
1842        format!("[{}]", scc.iter().join(", "))
1843    }).join(", "))]
1844    InheritanceCycle(Vec<Vec<String>>),
1845}
1846
1847#[cfg(feature = "self-update")]
1848mod self_update_errors {
1849    use super::*;
1850    use mukti_metadata::ReleaseStatus;
1851    use semver::{Version, VersionReq};
1852
1853    /// An error that occurs while performing a self-update.
1854    ///
1855    /// Returned by methods in the [`update`](crate::update) module.
1856    #[cfg(feature = "self-update")]
1857    #[derive(Debug, Error)]
1858    #[non_exhaustive]
1859    pub enum UpdateError {
1860        /// Failed to read release metadata from a local path on disk.
1861        #[error("failed to read release metadata from `{path}`")]
1862        ReadLocalMetadata {
1863            /// The path that was read.
1864            path: Utf8PathBuf,
1865
1866            /// The error that occurred.
1867            #[source]
1868            error: std::io::Error,
1869        },
1870
1871        /// An error was generated by `self_update`.
1872        #[error("self-update failed")]
1873        SelfUpdate(#[source] self_update::errors::Error),
1874
1875        /// Deserializing release metadata failed.
1876        #[error("deserializing release metadata failed")]
1877        ReleaseMetadataDe(#[source] serde_json::Error),
1878
1879        /// This version was not found.
1880        #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
1881        VersionNotFound {
1882            /// The version that wasn't found.
1883            version: Version,
1884
1885            /// A list of all known versions.
1886            known: Vec<(Version, ReleaseStatus)>,
1887        },
1888
1889        /// No version was found matching a requirement.
1890        #[error("no version found matching requirement `{req}`")]
1891        NoMatchForVersionReq {
1892            /// The version requirement that had no matches.
1893            req: VersionReq,
1894        },
1895
1896        /// The specified mukti project was not found.
1897        #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
1898        MuktiProjectNotFound {
1899            /// The project that was not found.
1900            not_found: String,
1901
1902            /// Known projects.
1903            known: Vec<String>,
1904        },
1905
1906        /// No release information was found for the given target triple.
1907        #[error(
1908            "for version {version}, no release information found for target `{triple}` \
1909            (known targets: {})",
1910            known_triples.iter().join(", ")
1911        )]
1912        NoTargetData {
1913            /// The version that was fetched.
1914            version: Version,
1915
1916            /// The target triple.
1917            triple: String,
1918
1919            /// The triples that were found.
1920            known_triples: BTreeSet<String>,
1921        },
1922
1923        /// The current executable could not be determined.
1924        #[error("the current executable's path could not be determined")]
1925        CurrentExe(#[source] std::io::Error),
1926
1927        /// A temporary directory could not be created.
1928        #[error("temporary directory could not be created at `{location}`")]
1929        TempDirCreate {
1930            /// The location where the temporary directory could not be created.
1931            location: Utf8PathBuf,
1932
1933            /// The error that occurred.
1934            #[source]
1935            error: std::io::Error,
1936        },
1937
1938        /// The temporary archive could not be created.
1939        #[error("temporary archive could not be created at `{archive_path}`")]
1940        TempArchiveCreate {
1941            /// The archive file that couldn't be created.
1942            archive_path: Utf8PathBuf,
1943
1944            /// The error that occurred.
1945            #[source]
1946            error: std::io::Error,
1947        },
1948
1949        /// An error occurred while writing to a temporary archive.
1950        #[error("error writing to temporary archive at `{archive_path}`")]
1951        TempArchiveWrite {
1952            /// The archive path for which there was an error.
1953            archive_path: Utf8PathBuf,
1954
1955            /// The error that occurred.
1956            #[source]
1957            error: std::io::Error,
1958        },
1959
1960        /// An error occurred while reading from a temporary archive.
1961        #[error("error reading from temporary archive at `{archive_path}`")]
1962        TempArchiveRead {
1963            /// The archive path for which there was an error.
1964            archive_path: Utf8PathBuf,
1965
1966            /// The error that occurred.
1967            #[source]
1968            error: std::io::Error,
1969        },
1970
1971        /// A checksum mismatch occurred. (Currently, the SHA-256 checksum is checked.)
1972        #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
1973        ChecksumMismatch {
1974            /// The expected checksum.
1975            expected: String,
1976
1977            /// The actual checksum.
1978            actual: String,
1979        },
1980
1981        /// An error occurred while renaming a file.
1982        #[error("error renaming `{source}` to `{dest}`")]
1983        FsRename {
1984            /// The rename source.
1985            source: Utf8PathBuf,
1986
1987            /// The rename destination.
1988            dest: Utf8PathBuf,
1989
1990            /// The error that occurred.
1991            #[source]
1992            error: std::io::Error,
1993        },
1994
1995        /// An error occurred while running `cargo nextest self setup`.
1996        #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
1997        SelfSetup(#[source] std::io::Error),
1998    }
1999
2000    fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
2001        use std::fmt::Write;
2002
2003        // Take the first few versions here.
2004        const DISPLAY_COUNT: usize = 4;
2005
2006        let display_versions: Vec<_> = versions
2007            .iter()
2008            .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
2009            .map(|(v, _)| v.to_string())
2010            .take(DISPLAY_COUNT)
2011            .collect();
2012        let mut display_str = display_versions.join(", ");
2013        if versions.len() > display_versions.len() {
2014            write!(
2015                display_str,
2016                " and {} others",
2017                versions.len() - display_versions.len()
2018            )
2019            .unwrap();
2020        }
2021
2022        display_str
2023    }
2024
2025    #[cfg(feature = "self-update")]
2026    /// An error occurred while parsing an [`UpdateVersion`](crate::update::UpdateVersion).
2027    #[derive(Debug, Error)]
2028    pub enum UpdateVersionParseError {
2029        /// The version string is empty.
2030        #[error("version string is empty")]
2031        EmptyString,
2032
2033        /// The input is not a valid version requirement.
2034        #[error(
2035            "`{input}` is not a valid semver requirement\n\
2036                (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
2037        )]
2038        InvalidVersionReq {
2039            /// The input that was provided.
2040            input: String,
2041
2042            /// The error.
2043            #[source]
2044            error: semver::Error,
2045        },
2046
2047        /// The version is not a valid semver.
2048        #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
2049        InvalidVersion {
2050            /// The input that was provided.
2051            input: String,
2052
2053            /// The error.
2054            #[source]
2055            error: semver::Error,
2056        },
2057    }
2058
2059    fn extra_semver_output(input: &str) -> String {
2060        // If it is not a valid version but it is a valid version
2061        // requirement, add a note to the warning
2062        if input.parse::<VersionReq>().is_ok() {
2063            format!(
2064                "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
2065            )
2066        } else {
2067            "".to_owned()
2068        }
2069    }
2070}
2071
2072#[cfg(feature = "self-update")]
2073pub use self_update_errors::*;
2074
2075#[cfg(test)]
2076mod tests {
2077    use super::*;
2078
2079    #[test]
2080    fn display_error_chain() {
2081        let err1 = StringError::new("err1", None);
2082
2083        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
2084
2085        let err2 = StringError::new("err2", Some(err1));
2086        let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
2087
2088        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @r"
2089        err3
2090        err3 line 2
2091          caused by:
2092          - err2
2093          - err1
2094        ");
2095    }
2096
2097    #[test]
2098    fn display_error_list() {
2099        let err1 = StringError::new("err1", None);
2100
2101        let error_list =
2102            ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
2103                .expect(">= 1 error");
2104        insta::assert_snapshot!(format!("{}", error_list), @"err1");
2105        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
2106
2107        let err2 = StringError::new("err2", Some(err1));
2108        let err3 = StringError::new("err3", Some(err2));
2109
2110        let error_list =
2111            ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
2112                .expect(">= 1 error");
2113        insta::assert_snapshot!(format!("{}", error_list), @"err3");
2114        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @r"
2115        err3
2116          caused by:
2117          - err2
2118          - err1
2119        ");
2120
2121        let err4 = StringError::new("err4", None);
2122        let err5 = StringError::new("err5", Some(err4));
2123        let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
2124
2125        let error_list = ErrorList::<StringError>::new(
2126            "waiting for the heat death of the universe",
2127            vec![err3, err6],
2128        )
2129        .expect(">= 1 error");
2130
2131        insta::assert_snapshot!(format!("{}", error_list), @r"
2132        2 errors occurred waiting for the heat death of the universe:
2133        * err3
2134            caused by:
2135            - err2
2136            - err1
2137        * err6
2138          err6 line 2
2139            caused by:
2140            - err5
2141            - err4
2142        ");
2143        insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @r"
2144        2 errors occurred waiting for the heat death of the universe:
2145        * err3
2146            caused by:
2147            - err2
2148            - err1
2149        * err6
2150          err6 line 2
2151            caused by:
2152            - err5
2153            - err4
2154        ");
2155    }
2156
2157    #[derive(Clone, Debug, Error)]
2158    struct StringError {
2159        message: String,
2160        #[source]
2161        source: Option<Box<StringError>>,
2162    }
2163
2164    impl StringError {
2165        fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
2166            Self {
2167                message: message.into(),
2168                source: source.map(Box::new),
2169            }
2170        }
2171    }
2172
2173    impl fmt::Display for StringError {
2174        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2175            write!(f, "{}", self.message)
2176        }
2177    }
2178}