Skip to main content

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