1use 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#[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 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 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 pub fn display_config_file(&self) -> impl fmt::Display + '_ {
107 &self.config_file
108 }
109
110 pub fn display_file(&self, styles: ConfigStyles) -> impl fmt::Display + '_ {
112 DisplayConfigFile {
113 error: self,
114 styles,
115 }
116 }
117
118 pub fn display_header(&self, styles: ConfigStyles) -> impl fmt::Display + '_ {
122 DisplayConfigHeader {
123 error: self,
124 styles,
125 }
126 }
127
128 pub fn tool(&self) -> Option<&ToolName> {
130 self.tool.as_ref()
131 }
132
133 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#[derive(Debug, Error)]
198pub enum ConfigPathsCaptureError {
199 #[error("failed to determine the current directory, which config paths are resolved against")]
201 CurrentDir(#[source] CurrentDirError),
202
203 #[error("failed to resolve workspace root `{}`", .0.input())]
205 WorkspaceRoot(#[source] ResolvePathError),
206}
207
208pub 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#[derive(Debug, Error)]
232#[non_exhaustive]
233pub enum ConfigParseErrorKind {
234 #[error("error resolving the configuration path")]
236 PathResolveError(#[source] Box<ResolvePathError>),
237 #[error(transparent)]
239 PathsCaptureError(Box<ConfigPathsCaptureError>),
240 #[error(transparent)]
242 BuildError(Box<ConfigError>),
243 #[error(transparent)]
245 TomlParseError(Box<toml::de::Error>),
246 #[error(transparent)]
247 DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
249 #[error(transparent)]
251 ReadError(std::io::Error),
252 #[error(transparent)]
254 VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
255 #[error("error parsing compiled data (destructure this variant for more details)")]
257 CompileErrors(Vec<ConfigCompileError>),
258 #[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 #[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 #[error("unknown test groups specified by config (destructure this variant for more details)")]
267 UnknownTestGroups {
268 errors: Vec<UnknownTestGroupError>,
270
271 known_groups: BTreeSet<TestGroup>,
273 },
274 #[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 #[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 #[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 #[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 #[error(
295 "errors in profile-specific config scripts (destructure this variant for more details)"
296 )]
297 ProfileScriptErrors {
298 errors: Box<ProfileScriptErrors>,
300
301 known_scripts: BTreeSet<ScriptId>,
303 },
304 #[error("unknown experimental features defined (destructure this variant for more details)")]
306 UnknownExperimentalFeatures {
307 unknown: BTreeSet<String>,
309
310 known: BTreeSet<ConfigExperimental>,
312 },
313 #[error(
317 "tool config file specifies experimental features `{}` \
318 -- only repository config files can do so",
319 .features.iter().join(", "),
320 )]
321 ExperimentalFeaturesInToolConfig {
322 features: BTreeSet<String>,
324 },
325 #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
327 ExperimentalFeaturesNotEnabled {
328 missing_features: BTreeSet<ConfigExperimental>,
330 },
331 #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
333 InheritanceErrors(Vec<InheritsError>),
334 #[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 tool: ToolName,
344 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#[derive(Debug)]
358#[non_exhaustive]
359pub struct ConfigCompileError {
360 pub profile_name: String,
362
363 pub section: ConfigCompileSection,
365
366 pub kind: ConfigCompileErrorKind,
368}
369
370#[derive(Debug)]
373pub enum ConfigCompileSection {
374 DefaultFilter,
376
377 Override(usize),
379
380 Script(usize),
382}
383
384#[derive(Debug)]
386#[non_exhaustive]
387pub enum ConfigCompileErrorKind {
388 ConstraintsNotSpecified {
390 default_filter_specified: bool,
395 },
396
397 FilterAndDefaultFilterSpecified,
401
402 Parse {
404 host_parse_error: Option<target_spec::Error>,
406
407 target_parse_error: Option<target_spec::Error>,
409
410 filter_parse_errors: Vec<FiltersetParseErrors>,
412 },
413}
414
415impl ConfigCompileErrorKind {
416 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#[derive(Clone, Debug, Error)]
466#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
467pub struct TestPriorityOutOfRange {
468 pub priority: i8,
470}
471
472#[derive(Clone, Debug, Error)]
474pub enum ChildStartError {
475 #[error("error creating temporary path for setup script")]
477 TempPath(#[source] Arc<std::io::Error>),
478
479 #[error("error spawning child process")]
481 Spawn(#[source] Arc<std::io::Error>),
482}
483
484#[derive(Clone, Debug, Error)]
486pub enum SetupScriptOutputError {
487 #[error("error opening environment file `{path}`")]
489 EnvFileOpen {
490 path: Utf8PathBuf,
492
493 #[source]
495 error: Arc<std::io::Error>,
496 },
497
498 #[error("error reading environment file `{path}`")]
500 EnvFileRead {
501 path: Utf8PathBuf,
503
504 #[source]
506 error: Arc<std::io::Error>,
507 },
508
509 #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
511 EnvFileParse {
512 path: Utf8PathBuf,
514 line: String,
516 },
517
518 #[error("error in environment file `{path}`")]
520 EnvFileInvalidKey {
521 path: Utf8PathBuf,
523
524 #[source]
526 error: EnvVarError,
527 },
528}
529
530#[derive(Clone, Debug, Error)]
532pub enum EnvVarError {
533 #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
535 ReservedKey {
536 key: String,
538 },
539
540 #[error("key `{key}` does not consist solely of letters, digits, and underscores")]
543 InvalidKey {
544 key: String,
546 },
547
548 #[error("key `{key}` does not start with a letter or underscore")]
550 InvalidKeyStartChar {
551 key: String,
553 },
554}
555
556impl 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
578pub struct ErrorList<T> {
579 description: Cow<'static, str>,
581 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 pub(crate) fn short_message(&self) -> String {
602 let string = self.to_string();
603 match string.lines().next() {
604 Some(first_line) => first_line.trim_end_matches(':').to_string(),
606 None => String::new(),
607 }
608 }
609
610 pub fn description(&self) -> &str {
612 &self.description
613 }
614
615 pub fn iter(&self) -> impl Iterator<Item = &T> {
617 self.inner.iter()
618 }
619
620 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 self.inner.len() == 1 {
646 return write!(f, "{}", self.inner[0]);
647 }
648
649 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 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 None
693 }
694 }
695}
696
697pub struct DisplayErrorChain<E> {
702 error: E,
703 initial_indent: &'static str,
704}
705
706impl<E: std::error::Error> DisplayErrorChain<E> {
707 pub fn new(error: E) -> Self {
709 Self {
710 error,
711 initial_indent: "",
712 }
713 }
714
715 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 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#[derive(Clone, Debug, Error)]
755pub enum ChildError {
756 #[error(transparent)]
758 Fd(#[from] ChildFdError),
759
760 #[error(transparent)]
762 SetupScriptOutput(#[from] SetupScriptOutputError),
763}
764
765#[derive(Clone, Debug, Error)]
767pub enum ChildFdError {
768 #[error("error reading standard output")]
770 ReadStdout(#[source] Arc<std::io::Error>),
771
772 #[error("error reading standard error")]
774 ReadStderr(#[source] Arc<std::io::Error>),
775
776 #[error("error reading combined stream")]
778 ReadCombined(#[source] Arc<std::io::Error>),
779
780 #[error("error waiting for child process to exit")]
782 Wait(#[source] Arc<std::io::Error>),
783}
784
785#[derive(Clone, Debug, Eq, PartialEq)]
787#[non_exhaustive]
788pub struct UnknownTestGroupError {
789 pub profile_name: String,
791
792 pub name: TestGroup,
794}
795
796#[derive(Clone, Debug, Eq, PartialEq)]
799pub struct ProfileUnknownScriptError {
800 pub profile_name: String,
802
803 pub name: ScriptId,
805}
806
807#[derive(Clone, Debug, Eq, PartialEq)]
810pub struct ProfileWrongConfigScriptTypeError {
811 pub profile_name: String,
813
814 pub name: ScriptId,
816
817 pub attempted: ProfileScriptType,
819
820 pub actual: ScriptType,
822}
823
824#[derive(Clone, Debug, Eq, PartialEq)]
827pub struct ProfileListScriptUsesRunFiltersError {
828 pub profile_name: String,
830
831 pub name: ScriptId,
833
834 pub script_type: ProfileScriptType,
836
837 pub filters: BTreeSet<String>,
839}
840
841#[derive(Clone, Debug, Default)]
843pub struct ProfileScriptErrors {
844 pub unknown_scripts: Vec<ProfileUnknownScriptError>,
846
847 pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
849
850 pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
852}
853
854impl ProfileScriptErrors {
855 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#[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#[derive(Clone, Debug, Error, Eq, PartialEq)]
887pub enum InvalidIdentifier {
888 #[error("identifier is empty")]
890 Empty,
891
892 #[error("invalid identifier `{0}`")]
894 InvalidXid(SmolStr),
895
896 #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
898 ToolIdentifierInvalidFormat(SmolStr),
899
900 #[error("tool identifier has empty component: `{0}`")]
902 ToolComponentEmpty(SmolStr),
903
904 #[error("invalid tool identifier `{0}`")]
906 ToolIdentifierInvalidXid(SmolStr),
907}
908
909#[derive(Clone, Debug, Error, Eq, PartialEq)]
911pub enum InvalidToolName {
912 #[error("tool name is empty")]
914 Empty,
915
916 #[error("invalid tool name `{0}`")]
918 InvalidXid(SmolStr),
919
920 #[error("tool name cannot start with \"@tool\": `{0}`")]
922 StartsWithToolPrefix(SmolStr),
923}
924
925#[derive(Clone, Debug, Error)]
927#[error("invalid custom test group name: {0}")]
928pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
929
930#[derive(Clone, Debug, Error)]
932#[error("invalid configuration script name: {0}")]
933pub struct InvalidConfigScriptName(pub InvalidIdentifier);
934
935#[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 pub fn spec(&self) -> &str {
947 &self.spec
948 }
949
950 pub fn kind(&self) -> &PackageSpecErrorKind {
952 &self.kind
953 }
954}
955
956#[derive(Clone, Debug, Error, PartialEq, Eq)]
963pub enum PackageSpecErrorKind {}
964
965#[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 InvalidFormat {
973 input: String,
975 },
976
977 #[error("tool-config-file has invalid tool name: {input}")]
979 InvalidToolName {
980 input: String,
982
983 #[source]
985 error: InvalidToolName,
986 },
987
988 #[error("tool-config-file has empty config file path: {input}")]
990 EmptyConfigFile {
991 input: String,
993 },
994
995 #[error("tool-config-file is not an absolute path: {config_file}")]
997 ConfigFileNotAbsolute {
998 config_file: Utf8PathBuf,
1000 },
1001}
1002
1003#[derive(Debug, Error)]
1005#[non_exhaustive]
1006pub enum UserConfigError {
1007 #[error("user config file not found at {path}")]
1010 FileNotFound {
1011 path: Utf8PathBuf,
1013 },
1014
1015 #[error("failed to read user config at {path}")]
1017 Read {
1018 path: Utf8PathBuf,
1020 #[source]
1022 error: std::io::Error,
1023 },
1024
1025 #[error("failed to parse user config at {path}")]
1027 Parse {
1028 path: Utf8PathBuf,
1030 #[source]
1032 error: toml::de::Error,
1033 },
1034
1035 #[error("user config path contains non-UTF-8 characters")]
1037 NonUtf8Path {
1038 #[source]
1040 error: FromPathBufError,
1041 },
1042
1043 #[error(
1045 "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
1046 )]
1047 OverridePlatformSpec {
1048 path: Utf8PathBuf,
1050 index: usize,
1052 #[source]
1054 error: Box<target_spec::Error>,
1055 },
1056}
1057
1058#[derive(Clone, Debug, Error)]
1060#[error("unrecognized value for max-fail: {reason}")]
1061pub struct MaxFailParseError {
1062 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#[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 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#[derive(Clone, Debug, Error)]
1095#[non_exhaustive]
1096pub enum DebuggerCommandParseError {
1097 #[error(transparent)]
1099 ShellWordsParse(shell_words::ParseError),
1100
1101 #[error("debugger command cannot be empty")]
1103 EmptyCommand,
1104}
1105
1106#[derive(Clone, Debug, Error)]
1108#[non_exhaustive]
1109pub enum TracerCommandParseError {
1110 #[error(transparent)]
1112 ShellWordsParse(shell_words::ParseError),
1113
1114 #[error("tracer command cannot be empty")]
1116 EmptyCommand,
1117}
1118
1119#[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 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#[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#[derive(Clone, Debug, Error)]
1175pub enum TestFilterBuildError {
1176 #[error("error constructing test filters")]
1178 Construct {
1179 #[from]
1181 error: aho_corasick::BuildError,
1182 },
1183}
1184
1185#[derive(Debug, Error)]
1187pub enum PathMapperConstructError {
1188 #[error("{kind} `{input}` failed to canonicalize")]
1190 Canonicalization {
1191 kind: PathMapperConstructKind,
1193
1194 input: Utf8PathBuf,
1196
1197 #[source]
1199 err: std::io::Error,
1200 },
1201 #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
1203 NonUtf8Path {
1204 kind: PathMapperConstructKind,
1206
1207 input: Utf8PathBuf,
1209
1210 #[source]
1212 err: FromPathBufError,
1213 },
1214 #[error("{kind} `{canonicalized_path}` is not a directory")]
1216 NotADirectory {
1217 kind: PathMapperConstructKind,
1219
1220 input: Utf8PathBuf,
1222
1223 canonicalized_path: Utf8PathBuf,
1225 },
1226}
1227
1228impl PathMapperConstructError {
1229 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 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1253pub enum PathMapperConstructKind {
1254 WorkspaceRoot,
1256
1257 TargetDir,
1259
1260 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#[derive(Debug, Error)]
1276pub enum RustBuildMetaParseError {
1277 #[error("error deserializing platform from build metadata")]
1279 PlatformDeserializeError(#[from] target_spec::Error),
1280
1281 #[error("the host platform could not be determined")]
1283 DetectBuildTargetError(#[source] target_spec::Error),
1284
1285 #[error("unsupported features in the build metadata: {message}")]
1287 Unsupported {
1288 message: String,
1290 },
1291}
1292
1293#[derive(Clone, Debug, thiserror::Error)]
1296#[error("invalid format version: {input}")]
1297pub struct FormatVersionError {
1298 pub input: String,
1300 #[source]
1302 pub error: FormatVersionErrorInner,
1303}
1304
1305#[derive(Clone, Debug, thiserror::Error)]
1307pub enum FormatVersionErrorInner {
1308 #[error("expected format version in form of `{expected}`")]
1310 InvalidFormat {
1311 expected: &'static str,
1313 },
1314 #[error("version component `{which}` could not be parsed as an integer")]
1316 InvalidInteger {
1317 which: &'static str,
1319 #[source]
1321 err: std::num::ParseIntError,
1322 },
1323 #[error("version component `{which}` value {value} is out of range {range:?}")]
1325 InvalidValue {
1326 which: &'static str,
1328 value: u8,
1330 range: std::ops::Range<u8>,
1332 },
1333}
1334
1335#[derive(Debug, Error)]
1339#[non_exhaustive]
1340pub enum FromMessagesError {
1341 #[error("error reading Cargo JSON messages")]
1343 ReadMessages(#[source] std::io::Error),
1344
1345 #[error("error querying package graph")]
1347 PackageGraph(#[source] guppy::Error),
1348
1349 #[error("missing kind for target {binary_name} in package {package_name}")]
1351 MissingTargetKind {
1352 package_name: String,
1354 binary_name: String,
1356 },
1357}
1358
1359#[derive(Debug, Error)]
1361#[non_exhaustive]
1362pub enum CreateTestListError {
1363 #[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 binary_id: RustBinaryId,
1371
1372 cwd: Utf8PathBuf,
1374 },
1375
1376 #[error(
1378 "for `{binary_id}`, running command `{}` failed to execute",
1379 shell_words::join(command)
1380 )]
1381 CommandExecFail {
1382 binary_id: RustBinaryId,
1384
1385 command: Vec<String>,
1387
1388 #[source]
1390 error: std::io::Error,
1391 },
1392
1393 #[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 binary_id: RustBinaryId,
1404
1405 command: Vec<String>,
1407
1408 exit_status: ExitStatus,
1410
1411 stdout: Vec<u8>,
1413
1414 stderr: Vec<u8>,
1416 },
1417
1418 #[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 binary_id: RustBinaryId,
1428
1429 command: Vec<String>,
1431
1432 stdout: Vec<u8>,
1434
1435 stderr: Vec<u8>,
1437 },
1438
1439 #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1441 ParseLine {
1442 binary_id: RustBinaryId,
1444
1445 message: Cow<'static, str>,
1447
1448 full_output: String,
1450 },
1451
1452 #[error(
1454 "error joining dynamic library paths for {}: [{}]",
1455 dylib_path_envvar(),
1456 itertools::join(.new_paths, ", ")
1457 )]
1458 DylibJoinPaths {
1459 new_paths: Vec<Utf8PathBuf>,
1461
1462 #[source]
1464 error: JoinPathsError,
1465 },
1466
1467 #[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#[derive(Debug, Error)]
1492#[non_exhaustive]
1493pub enum WriteTestListError {
1494 #[error("error writing to output")]
1496 Io(#[source] std::io::Error),
1497
1498 #[error("error serializing to JSON")]
1500 Json(#[source] serde_json::Error),
1501}
1502
1503#[derive(Debug, Error)]
1507pub enum ConfigureHandleInheritanceError {
1508 #[cfg(windows)]
1510 #[error("error configuring handle inheritance")]
1511 WindowsError(#[from] std::io::Error),
1512}
1513
1514#[derive(Debug, Error)]
1516#[non_exhaustive]
1517pub enum TestRunnerBuildError {
1518 #[error("error creating Tokio runtime")]
1520 TokioRuntimeCreate(#[source] std::io::Error),
1521
1522 #[error("error setting up signals")]
1524 SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1525}
1526
1527#[derive(Debug, Error)]
1529pub struct TestRunnerExecuteErrors<E> {
1530 pub report_error: Option<E>,
1532
1533 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#[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 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#[derive(Debug, Error)]
1586#[non_exhaustive]
1587pub enum ArchiveCreateError {
1588 #[error("error creating binary list")]
1590 CreateBinaryList(#[source] WriteTestListError),
1591
1592 #[error("extra path `{}` not found", .redactor.redact_path(path))]
1594 MissingExtraPath {
1595 path: Utf8PathBuf,
1597
1598 redactor: Redactor,
1603 },
1604
1605 #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1607 InputFileRead {
1608 step: ArchiveStep,
1610
1611 path: Utf8PathBuf,
1613
1614 is_dir: Option<bool>,
1616
1617 #[source]
1619 error: std::io::Error,
1620 },
1621
1622 #[error("error reading directory entry from `{path}")]
1624 DirEntryRead {
1625 path: Utf8PathBuf,
1627
1628 #[source]
1630 error: std::io::Error,
1631 },
1632
1633 #[error("error writing to archive")]
1635 OutputArchiveIo(#[source] std::io::Error),
1636
1637 #[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#[derive(Debug, Error)]
1652pub enum MetadataMaterializeError {
1653 #[error("I/O error reading metadata file `{path}`")]
1655 Read {
1656 path: Utf8PathBuf,
1658
1659 #[source]
1661 error: std::io::Error,
1662 },
1663
1664 #[error("error deserializing metadata file `{path}`")]
1666 Deserialize {
1667 path: Utf8PathBuf,
1669
1670 #[source]
1672 error: serde_json::Error,
1673 },
1674
1675 #[error("error parsing Rust build metadata from `{path}`")]
1677 RustBuildMeta {
1678 path: Utf8PathBuf,
1680
1681 #[source]
1683 error: Box<RustBuildMetaParseError>,
1684 },
1685
1686 #[error("error building package graph from `{path}`")]
1688 PackageGraphConstruct {
1689 path: Utf8PathBuf,
1691
1692 #[source]
1694 error: Box<guppy::Error>,
1695 },
1696}
1697
1698#[derive(Debug, Error)]
1702#[non_exhaustive]
1703pub enum ArchiveReadError {
1704 #[error("I/O error reading archive")]
1706 Io(#[source] std::io::Error),
1707
1708 #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1710 NonUtf8Path(Vec<u8>),
1711
1712 #[error("path in archive `{0}` doesn't start with `target/`")]
1714 NoTargetPrefix(Utf8PathBuf),
1715
1716 #[error("path in archive `{path}` contains an invalid component `{component}`")]
1718 InvalidComponent {
1719 path: Utf8PathBuf,
1721
1722 component: String,
1724 },
1725
1726 #[error("corrupted archive: checksum read error for path `{path}`")]
1728 ChecksumRead {
1729 path: Utf8PathBuf,
1731
1732 #[source]
1734 error: std::io::Error,
1735 },
1736
1737 #[error("corrupted archive: invalid checksum for path `{path}`")]
1739 InvalidChecksum {
1740 path: Utf8PathBuf,
1742
1743 expected: u32,
1745
1746 actual: u32,
1748 },
1749
1750 #[error("metadata file `{0}` not found in archive")]
1752 MetadataFileNotFound(&'static Utf8Path),
1753
1754 #[error("error deserializing metadata file `{path}` in archive")]
1756 MetadataDeserializeError {
1757 path: &'static Utf8Path,
1759
1760 #[source]
1762 error: serde_json::Error,
1763 },
1764
1765 #[error("error building package graph from `{path}` in archive")]
1767 PackageGraphConstructError {
1768 path: &'static Utf8Path,
1770
1771 #[source]
1773 error: Box<guppy::Error>,
1774 },
1775}
1776
1777#[derive(Debug, Error)]
1781#[non_exhaustive]
1782pub enum ArchiveExtractError {
1783 #[error("error creating temporary directory")]
1785 TempDirCreate(#[source] std::io::Error),
1786
1787 #[error("error canonicalizing destination directory `{dir}`")]
1789 DestDirCanonicalization {
1790 dir: Utf8PathBuf,
1792
1793 #[source]
1795 error: std::io::Error,
1796 },
1797
1798 #[error("destination `{0}` already exists")]
1800 DestinationExists(Utf8PathBuf),
1801
1802 #[error("error reading archive")]
1804 Read(#[source] ArchiveReadError),
1805
1806 #[error("error deserializing Rust build metadata")]
1808 RustBuildMeta(#[from] RustBuildMetaParseError),
1809
1810 #[error("error writing file `{path}` to disk")]
1812 WriteFile {
1813 path: Utf8PathBuf,
1815
1816 #[source]
1818 error: std::io::Error,
1819 },
1820
1821 #[error("error reporting extract status")]
1823 ReporterIo(std::io::Error),
1824}
1825
1826#[derive(Debug, Error)]
1828#[non_exhaustive]
1829pub enum WriteEventError {
1830 #[error("error writing to output")]
1832 Io(#[source] std::io::Error),
1833
1834 #[error("error operating on path {file}")]
1836 Fs {
1837 file: Utf8PathBuf,
1839
1840 #[source]
1842 error: std::io::Error,
1843 },
1844
1845 #[error("error writing JUnit output to {file}")]
1847 Junit {
1848 file: Utf8PathBuf,
1850
1851 #[source]
1853 error: quick_junit::SerializeError,
1854 },
1855}
1856
1857#[derive(Debug, Error)]
1860#[non_exhaustive]
1861pub enum CargoConfigError {
1862 #[error("failed to retrieve current directory")]
1864 GetCurrentDir(#[source] std::io::Error),
1865
1866 #[error("current directory is invalid UTF-8")]
1868 CurrentDirInvalidUtf8(#[source] FromPathBufError),
1869
1870 #[error("failed to parse --config argument `{config_str}` as TOML")]
1872 CliConfigParseError {
1873 config_str: String,
1875
1876 #[source]
1878 error: toml_edit::TomlError,
1879 },
1880
1881 #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1883 CliConfigDeError {
1884 config_str: String,
1886
1887 #[source]
1889 error: toml_edit::de::Error,
1890 },
1891
1892 #[error(
1894 "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1895 )]
1896 InvalidCliConfig {
1897 config_str: String,
1899
1900 #[source]
1902 reason: InvalidCargoCliConfigReason,
1903 },
1904
1905 #[error("non-UTF-8 path encountered")]
1907 NonUtf8Path(#[source] FromPathBufError),
1908
1909 #[error("failed to retrieve the Cargo home directory")]
1911 GetCargoHome(#[source] std::io::Error),
1912
1913 #[error("failed to canonicalize path `{path}")]
1915 FailedPathCanonicalization {
1916 path: Utf8PathBuf,
1918
1919 #[source]
1921 error: std::io::Error,
1922 },
1923
1924 #[error("failed to read config at `{path}`")]
1926 ConfigReadError {
1927 path: Utf8PathBuf,
1929
1930 #[source]
1932 error: std::io::Error,
1933 },
1934
1935 #[error(transparent)]
1937 ConfigParseError(#[from] Box<CargoConfigParseError>),
1938}
1939
1940#[derive(Debug, Error)]
1944#[error("failed to parse config at `{path}`")]
1945pub struct CargoConfigParseError {
1946 pub path: Utf8PathBuf,
1948
1949 #[source]
1951 pub error: toml::de::Error,
1952}
1953
1954#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1958#[non_exhaustive]
1959pub enum InvalidCargoCliConfigReason {
1960 #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1962 NotDottedKv,
1963
1964 #[error("includes non-whitespace decoration")]
1966 IncludesNonWhitespaceDecoration,
1967
1968 #[error("sets a value to an inline table, which is not accepted")]
1970 SetsValueToInlineTable,
1971
1972 #[error("sets a value to an array of tables, which is not accepted")]
1974 SetsValueToArrayOfTables,
1975
1976 #[error("doesn't provide a value")]
1978 DoesntProvideValue,
1979}
1980
1981#[derive(Debug, Error)]
1983pub enum HostPlatformDetectError {
1984 #[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 error: std::io::Error,
1997
1998 build_target_error: Box<target_spec::Error>,
2000 },
2001
2002 #[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 status: ExitStatus,
2018
2019 stdout: Vec<u8>,
2021
2022 stderr: Vec<u8>,
2024
2025 build_target_error: Box<target_spec::Error>,
2027 },
2028
2029 #[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 host_platform_error: Box<target_spec::Error>,
2042
2043 build_target_error: Box<target_spec::Error>,
2045 },
2046
2047 #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
2050 BuildTargetError {
2051 #[source]
2053 build_target_error: Box<target_spec::Error>,
2054 },
2055}
2056
2057#[derive(Debug, Error)]
2059pub enum TargetTripleError {
2060 #[error(
2062 "environment variable '{}' contained non-UTF-8 data",
2063 TargetTriple::CARGO_BUILD_TARGET_ENV
2064 )]
2065 InvalidEnvironmentVar,
2066
2067 #[error("error deserializing target triple from {source}")]
2069 TargetSpecError {
2070 source: TargetTripleSource,
2072
2073 #[source]
2075 error: target_spec::Error,
2076 },
2077
2078 #[error("target path `{path}` is not a valid file")]
2080 TargetPathReadError {
2081 source: TargetTripleSource,
2083
2084 path: Utf8PathBuf,
2086
2087 #[source]
2089 error: std::io::Error,
2090 },
2091
2092 #[error(
2094 "for custom platform obtained from {source}, \
2095 failed to create temporary directory for custom platform"
2096 )]
2097 CustomPlatformTempDirError {
2098 source: TargetTripleSource,
2100
2101 #[source]
2103 error: std::io::Error,
2104 },
2105
2106 #[error(
2108 "for custom platform obtained from {source}, \
2109 failed to write JSON to temporary path `{path}`"
2110 )]
2111 CustomPlatformWriteError {
2112 source: TargetTripleSource,
2114
2115 path: Utf8PathBuf,
2117
2118 #[source]
2120 error: std::io::Error,
2121 },
2122
2123 #[error(
2125 "for custom platform obtained from {source}, \
2126 failed to close temporary directory `{dir_path}`"
2127 )]
2128 CustomPlatformCloseError {
2129 source: TargetTripleSource,
2131
2132 dir_path: Utf8PathBuf,
2134
2135 #[source]
2137 error: std::io::Error,
2138 },
2139}
2140
2141impl TargetTripleError {
2142 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 TargetTripleError::InvalidEnvironmentVar
2153 | TargetTripleError::TargetPathReadError { .. }
2154 | TargetTripleError::CustomPlatformTempDirError { .. }
2155 | TargetTripleError::CustomPlatformWriteError { .. }
2156 | TargetTripleError::CustomPlatformCloseError { .. } => None,
2157 }
2158 }
2159}
2160
2161#[derive(Debug, Error)]
2163pub enum TargetRunnerError {
2164 #[error("environment variable '{0}' contained non-UTF-8 data")]
2166 InvalidEnvironmentVar(String),
2167
2168 #[error("runner '{key}' = '{value}' did not contain a runner binary")]
2171 BinaryNotSpecified {
2172 key: PlatformRunnerSource,
2174
2175 value: String,
2177 },
2178}
2179
2180#[derive(Debug, Error)]
2182#[error("error setting up signal handler")]
2183pub struct SignalHandlerSetupError(#[from] std::io::Error);
2184
2185#[derive(Debug, Error)]
2187pub enum ShowTestGroupsError {
2188 #[error(
2190 "unknown test groups specified: {}\n(known groups: {})",
2191 unknown_groups.iter().join(", "),
2192 known_groups.iter().join(", "),
2193 )]
2194 UnknownGroups {
2195 unknown_groups: BTreeSet<TestGroup>,
2197
2198 known_groups: BTreeSet<TestGroup>,
2200 },
2201}
2202
2203#[derive(Debug, Error, PartialEq, Eq, Hash)]
2205pub enum InheritsError {
2206 #[error("the {} profile should not inherit from other profiles", .0)]
2208 DefaultProfileInheritance(String),
2209 #[error("profile {} inherits from an unknown profile {}", .0, .1)]
2211 UnknownInheritance(String, String),
2212 #[error("a self referential inheritance is detected from profile: {}", .0)]
2214 SelfReferentialInheritance(String),
2215 #[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#[derive(Debug, Error)]
2228pub enum RunStoreError {
2229 #[error("error creating run directory `{run_dir}`")]
2231 RunDirCreate {
2232 run_dir: Utf8PathBuf,
2234
2235 #[source]
2237 error: std::io::Error,
2238 },
2239
2240 #[error("error acquiring lock on `{path}`")]
2242 FileLock {
2243 path: Utf8PathBuf,
2245
2246 #[source]
2248 error: std::io::Error,
2249 },
2250
2251 #[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 path: Utf8PathBuf,
2259
2260 timeout_secs: u64,
2262 },
2263
2264 #[error("error reading run list from `{path}`")]
2266 RunListRead {
2267 path: Utf8PathBuf,
2269
2270 #[source]
2272 error: std::io::Error,
2273 },
2274
2275 #[error("error deserializing run list from `{path}`")]
2277 RunListDeserialize {
2278 path: Utf8PathBuf,
2280
2281 #[source]
2283 error: serde_json::Error,
2284 },
2285
2286 #[error("error serializing run list to `{path}`")]
2288 RunListSerialize {
2289 path: Utf8PathBuf,
2291
2292 #[source]
2294 error: serde_json::Error,
2295 },
2296
2297 #[error("error serializing rerun info")]
2299 RerunInfoSerialize {
2300 #[source]
2302 error: serde_json::Error,
2303 },
2304
2305 #[error("error serializing build scope")]
2307 BuildScopeSerialize {
2308 #[source]
2310 error: serde_json::Error,
2311 },
2312
2313 #[error("error serializing test list")]
2315 TestListSerialize {
2316 #[source]
2318 error: serde_json::Error,
2319 },
2320
2321 #[error("error serializing record options")]
2323 RecordOptionsSerialize {
2324 #[source]
2326 error: serde_json::Error,
2327 },
2328
2329 #[error("error serializing test event")]
2331 TestEventSerialize {
2332 #[source]
2334 error: serde_json::Error,
2335 },
2336
2337 #[error("error writing run list to `{path}`")]
2339 RunListWrite {
2340 path: Utf8PathBuf,
2342
2343 #[source]
2345 error: atomicwrites::Error<std::io::Error>,
2346 },
2347
2348 #[error("error writing to store at `{store_path}`")]
2350 StoreWrite {
2351 store_path: Utf8PathBuf,
2353
2354 #[source]
2356 error: StoreWriterError,
2357 },
2358
2359 #[error("error creating run log at `{path}`")]
2361 RunLogCreate {
2362 path: Utf8PathBuf,
2364
2365 #[source]
2367 error: std::io::Error,
2368 },
2369
2370 #[error("error writing to run log at `{path}`")]
2372 RunLogWrite {
2373 path: Utf8PathBuf,
2375
2376 #[source]
2378 error: std::io::Error,
2379 },
2380
2381 #[error("error flushing run log at `{path}`")]
2383 RunLogFlush {
2384 path: Utf8PathBuf,
2386
2387 #[source]
2389 error: std::io::Error,
2390 },
2391
2392 #[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 file_version: RunsJsonFormatVersion,
2400 max_supported_version: RunsJsonFormatVersion,
2402 },
2403}
2404
2405#[derive(Debug, Error)]
2407#[non_exhaustive]
2408pub enum StoreWriterError {
2409 #[error("error creating store")]
2411 Create {
2412 #[source]
2414 error: std::io::Error,
2415 },
2416
2417 #[error("error writing to path `{path}` in store")]
2419 Write {
2420 path: Utf8PathBuf,
2422
2423 #[source]
2425 error: std::io::Error,
2426 },
2427
2428 #[error("error compressing data")]
2430 Compress {
2431 #[source]
2433 error: std::io::Error,
2434 },
2435
2436 #[error("error finalizing store")]
2438 Finish {
2439 #[source]
2441 error: std::io::Error,
2442 },
2443
2444 #[error("error flushing store")]
2446 Flush {
2447 #[source]
2449 error: std::io::Error,
2450 },
2451}
2452
2453#[derive(Debug, Error)]
2455pub enum RecordReporterError {
2456 #[error(transparent)]
2458 RunStore(RunStoreError),
2459
2460 #[error("record writer thread panicked: {message}")]
2462 WriterPanic {
2463 message: String,
2465 },
2466}
2467
2468#[derive(Debug, Error)]
2470pub enum StateDirError {
2471 #[error("could not determine platform base directory strategy")]
2475 BaseDirStrategy(#[source] HomeDirError),
2476
2477 #[error("platform state directory is not valid UTF-8: {path:?}")]
2479 StateDirNotUtf8 {
2480 path: PathBuf,
2482 },
2483
2484 #[error("could not canonicalize workspace path `{workspace_root}`")]
2486 Canonicalize {
2487 workspace_root: Utf8PathBuf,
2489 #[source]
2491 error: std::io::Error,
2492 },
2493}
2494
2495#[derive(Debug, Error)]
2497pub enum RecordSetupError {
2498 #[error("could not determine platform state directory for recording")]
2500 StateDirNotFound(#[source] StateDirError),
2501
2502 #[error("failed to create run store")]
2504 StoreCreate(#[source] RunStoreError),
2505
2506 #[error("failed to lock run store")]
2508 StoreLock(#[source] RunStoreError),
2509
2510 #[error("failed to create run recorder")]
2512 RecorderCreate(#[source] RunStoreError),
2513}
2514
2515#[derive(Debug, Error)]
2517pub enum RecordPruneError {
2518 #[error("error deleting run `{run_id}` at `{path}`")]
2520 DeleteRun {
2521 run_id: ReportUuid,
2523
2524 path: Utf8PathBuf,
2526
2527 #[source]
2529 error: std::io::Error,
2530 },
2531
2532 #[error("error calculating size of `{path}`")]
2534 CalculateSize {
2535 path: Utf8PathBuf,
2537
2538 #[source]
2540 error: std::io::Error,
2541 },
2542
2543 #[error("error deleting orphaned directory `{path}`")]
2545 DeleteOrphan {
2546 path: Utf8PathBuf,
2548
2549 #[source]
2551 error: std::io::Error,
2552 },
2553
2554 #[error("error reading runs directory `{path}`")]
2556 ReadRunsDir {
2557 path: Utf8PathBuf,
2559
2560 #[source]
2562 error: std::io::Error,
2563 },
2564
2565 #[error("error reading directory entry in `{dir}`")]
2567 ReadDirEntry {
2568 dir: Utf8PathBuf,
2570
2571 #[source]
2573 error: std::io::Error,
2574 },
2575
2576 #[error("error reading file type for `{path}`")]
2578 ReadFileType {
2579 path: Utf8PathBuf,
2581
2582 #[source]
2584 error: std::io::Error,
2585 },
2586}
2587
2588#[derive(Clone, Debug, PartialEq, Eq, Error)]
2593#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2594pub struct InvalidRunIdSelector {
2595 pub input: String,
2597}
2598
2599#[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 pub input: String,
2612}
2613
2614#[derive(Debug, Error)]
2616pub enum RunIdResolutionError {
2617 #[error("no recorded run found matching `{prefix}`")]
2619 NotFound {
2620 prefix: String,
2622 },
2623
2624 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2626 Ambiguous {
2627 prefix: String,
2629
2630 count: usize,
2632
2633 candidates: Vec<RecordedRunInfo>,
2635
2636 run_id_index: RunIdIndex,
2638 },
2639
2640 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2642 InvalidPrefix {
2643 prefix: String,
2645 },
2646
2647 #[error("no recorded runs exist")]
2649 NoRuns,
2650}
2651
2652#[derive(Debug, Error)]
2654pub enum RecordReadError {
2655 #[error("run not found at `{path}`")]
2657 RunNotFound {
2658 path: Utf8PathBuf,
2660 },
2661
2662 #[error("error opening archive at `{path}`")]
2664 OpenArchive {
2665 path: Utf8PathBuf,
2667
2668 #[source]
2670 error: std::io::Error,
2671 },
2672
2673 #[error("error parsing archive at `{path}`")]
2675 ParseArchive {
2676 path: Utf8PathBuf,
2678
2679 #[source]
2681 error: std::io::Error,
2682 },
2683
2684 #[error("error reading `{file_name}` from archive")]
2686 ReadArchiveFile {
2687 file_name: String,
2689
2690 #[source]
2692 error: std::io::Error,
2693 },
2694
2695 #[error("error opening run log at `{path}`")]
2697 OpenRunLog {
2698 path: Utf8PathBuf,
2700
2701 #[source]
2703 error: std::io::Error,
2704 },
2705
2706 #[error("error reading line {line_number} from run log")]
2708 ReadRunLog {
2709 line_number: usize,
2711
2712 #[source]
2714 error: std::io::Error,
2715 },
2716
2717 #[error("error parsing event at line {line_number}")]
2719 ParseEvent {
2720 line_number: usize,
2722
2723 #[source]
2725 error: serde_json::Error,
2726 },
2727
2728 #[error("required file `{file_name}` not found in archive")]
2730 FileNotFound {
2731 file_name: String,
2733 },
2734
2735 #[error("error decompressing data from `{file_name}`")]
2737 Decompress {
2738 file_name: String,
2740
2741 #[source]
2743 error: std::io::Error,
2744 },
2745
2746 #[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 file_name: String,
2757 },
2758
2759 #[error(
2761 "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2762 )]
2763 FileTooLarge {
2764 file_name: String,
2766
2767 size: u64,
2769
2770 limit: u64,
2772 },
2773
2774 #[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 file_name: String,
2785
2786 claimed_size: u64,
2788
2789 actual_size: u64,
2791 },
2792
2793 #[error("error deserializing `{file_name}`")]
2795 DeserializeMetadata {
2796 file_name: String,
2798
2799 #[source]
2801 error: serde_json::Error,
2802 },
2803
2804 #[error("failed to extract `{store_path}` to `{output_path}`")]
2806 ExtractFile {
2807 store_path: String,
2809
2810 output_path: Utf8PathBuf,
2812
2813 #[source]
2815 error: std::io::Error,
2816 },
2817
2818 #[error("error reading portable recording")]
2820 PortableRecording(#[source] PortableRecordingReadError),
2821}
2822
2823#[derive(Debug, Error)]
2825#[non_exhaustive]
2826pub enum PortableRecordingError {
2827 #[error("run directory does not exist: {path}")]
2829 RunDirNotFound {
2830 path: Utf8PathBuf,
2832 },
2833
2834 #[error("required file missing from run directory `{run_dir}`: `{file_name}`")]
2836 RequiredFileMissing {
2837 run_dir: Utf8PathBuf,
2839 file_name: &'static str,
2841 },
2842
2843 #[error("failed to serialize manifest")]
2845 SerializeManifest(#[source] serde_json::Error),
2846
2847 #[error("failed to start file {file_name} in archive")]
2849 ZipStartFile {
2850 file_name: &'static str,
2852 #[source]
2854 source: std::io::Error,
2855 },
2856
2857 #[error("failed to write {file_name} to archive")]
2859 ZipWrite {
2860 file_name: &'static str,
2862 #[source]
2864 source: std::io::Error,
2865 },
2866
2867 #[error("failed to read {file_name}")]
2869 ReadFile {
2870 file_name: &'static str,
2872 #[source]
2874 source: std::io::Error,
2875 },
2876
2877 #[error("failed to finalize archive")]
2879 ZipFinalize(#[source] std::io::Error),
2880
2881 #[error("failed to write archive atomically to {path}")]
2883 AtomicWrite {
2884 path: Utf8PathBuf,
2886 #[source]
2888 source: std::io::Error,
2889 },
2890}
2891
2892#[derive(Debug, Error)]
2894#[non_exhaustive]
2895pub enum PortableRecordingReadError {
2896 #[error("failed to open archive at `{path}`")]
2898 OpenArchive {
2899 path: Utf8PathBuf,
2901 #[source]
2903 error: std::io::Error,
2904 },
2905
2906 #[error("failed to read archive at `{path}`")]
2908 ReadArchive {
2909 path: Utf8PathBuf,
2911 #[source]
2913 error: std::io::Error,
2914 },
2915
2916 #[error("required file `{file_name}` missing from archive at `{path}`")]
2918 MissingFile {
2919 path: Utf8PathBuf,
2921 file_name: Cow<'static, str>,
2923 },
2924
2925 #[error("failed to parse manifest from archive at `{path}`")]
2927 ParseManifest {
2928 path: Utf8PathBuf,
2930 #[source]
2932 error: serde_json::Error,
2933 },
2934
2935 #[error(
2937 "portable recording format version {found} in `{path}` is incompatible: {incompatibility} \
2938 (this nextest supports version {supported})"
2939 )]
2940 UnsupportedFormatVersion {
2941 path: Utf8PathBuf,
2943 found: PortableRecordingFormatVersion,
2945 supported: PortableRecordingFormatVersion,
2947 incompatibility: PortableRecordingVersionIncompatibility,
2949 },
2950
2951 #[error("store format version {found} in `{path}` is incompatible: {incompatibility}")]
2953 UnsupportedStoreFormatVersion {
2954 path: Utf8PathBuf,
2956 found: StoreFormatVersion,
2958 supported: StoreFormatVersion,
2960 incompatibility: StoreVersionIncompatibility,
2962 },
2963
2964 #[error(
2966 "file `{file_name}` in archive `{path}` is too large \
2967 ({size} bytes, limit is {limit} bytes)"
2968 )]
2969 FileTooLarge {
2970 path: Utf8PathBuf,
2972 file_name: Cow<'static, str>,
2974 size: u64,
2976 limit: u64,
2978 },
2979
2980 #[error("failed to extract `{file_name}` from archive `{archive_path}` to `{output_path}`")]
2982 ExtractFile {
2983 archive_path: Utf8PathBuf,
2985 file_name: &'static str,
2987 output_path: Utf8PathBuf,
2989 #[source]
2991 error: std::io::Error,
2992 },
2993
2994 #[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 archive_path: Utf8PathBuf,
3005 compression: CompressionMethod,
3007 },
3008
3009 #[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 path: Utf8PathBuf,
3021 file_count: usize,
3023 zip_count: usize,
3025 },
3026
3027 #[error("unexpected I/O error while probing seekability of `{path}`")]
3034 SeekProbe {
3035 path: Utf8PathBuf,
3037 #[source]
3039 error: std::io::Error,
3040 },
3041
3042 #[error("failed to spool non-seekable input `{path}` to a temporary file")]
3048 SpoolTempFile {
3049 path: Utf8PathBuf,
3051 #[source]
3053 error: std::io::Error,
3054 },
3055
3056 #[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 path: Utf8PathBuf,
3065 limit: ByteSize,
3067 },
3068}
3069
3070#[derive(Debug, Error)]
3072pub enum ChromeTraceError {
3073 #[error("error reading recorded events")]
3075 ReadError(#[source] RecordReadError),
3076
3077 #[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 test_name: TestCaseName,
3085
3086 binary_id: RustBinaryId,
3088 },
3089
3090 #[error(
3092 "SetupScriptSlow for script `{script_id}` \
3093 has no prior SetupScriptStarted event (corrupt or truncated log?)"
3094 )]
3095 MissingScriptStart {
3096 script_id: ScriptId,
3098 },
3099
3100 #[error(
3103 "StressSubRunFinished has no prior StressSubRunStarted event \
3104 (corrupt or truncated log?)"
3105 )]
3106 MissingStressSubRunStart,
3107
3108 #[error("error serializing Chrome trace JSON")]
3110 SerializeError(#[source] serde_json::Error),
3111}
3112
3113#[derive(Debug, Error)]
3117pub enum TestListFromSummaryError {
3118 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
3120 PackageNotFound {
3121 name: String,
3123
3124 package_id: String,
3126 },
3127
3128 #[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 #[derive(Debug, Error)]
3144 #[non_exhaustive]
3145 pub enum UpdateError {
3146 #[error("failed to read release metadata from `{path}`")]
3148 ReadLocalMetadata {
3149 path: Utf8PathBuf,
3151
3152 #[source]
3154 error: std::io::Error,
3155 },
3156
3157 #[error("self-update failed")]
3159 SelfUpdate(#[source] self_update::errors::Error),
3160
3161 #[error("error performing HTTP request")]
3163 Http(#[source] ureq::Error),
3164
3165 #[error("error reading HTTP response body")]
3167 HttpBody(#[source] std::io::Error),
3168
3169 #[error("Content-Length header present but could not be parsed as an integer: {value:?}")]
3172 ContentLengthInvalid {
3173 value: String,
3175 },
3176
3177 #[error("content length mismatch: expected {expected} bytes, received {actual} bytes")]
3180 ContentLengthMismatch {
3181 expected: u64,
3183 actual: u64,
3185 },
3186
3187 #[error("deserializing release metadata failed")]
3189 ReleaseMetadataDe(#[source] serde_json::Error),
3190
3191 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
3193 VersionNotFound {
3194 version: Version,
3196
3197 known: Vec<(Version, ReleaseStatus)>,
3199 },
3200
3201 #[error("no version found matching requirement `{req}`")]
3203 NoMatchForVersionReq {
3204 req: VersionReq,
3206 },
3207
3208 #[error("no stable version found")]
3210 NoStableVersion,
3211
3212 #[error("no version found matching {} channel", kind.description())]
3214 NoVersionForPrereleaseKind {
3215 kind: PrereleaseKind,
3217 },
3218
3219 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
3221 MuktiProjectNotFound {
3222 not_found: String,
3224
3225 known: Vec<String>,
3227 },
3228
3229 #[error(
3231 "for version {version}, no release information found for target `{triple}` \
3232 (known targets: {})",
3233 known_triples.iter().join(", ")
3234 )]
3235 NoTargetData {
3236 version: Version,
3238
3239 triple: String,
3241
3242 known_triples: BTreeSet<String>,
3244 },
3245
3246 #[error("the current executable's path could not be determined")]
3248 CurrentExe(#[source] std::io::Error),
3249
3250 #[error("temporary directory could not be created at `{location}`")]
3252 TempDirCreate {
3253 location: Utf8PathBuf,
3255
3256 #[source]
3258 error: std::io::Error,
3259 },
3260
3261 #[error("temporary archive could not be created at `{archive_path}`")]
3263 TempArchiveCreate {
3264 archive_path: Utf8PathBuf,
3266
3267 #[source]
3269 error: std::io::Error,
3270 },
3271
3272 #[error("error writing to temporary archive at `{archive_path}`")]
3274 TempArchiveWrite {
3275 archive_path: Utf8PathBuf,
3277
3278 #[source]
3280 error: std::io::Error,
3281 },
3282
3283 #[error("error reading from temporary archive at `{archive_path}`")]
3285 TempArchiveRead {
3286 archive_path: Utf8PathBuf,
3288
3289 #[source]
3291 error: std::io::Error,
3292 },
3293
3294 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
3296 ChecksumMismatch {
3297 expected: String,
3299
3300 actual: String,
3302 },
3303
3304 #[error("error renaming `{source}` to `{dest}`")]
3306 FsRename {
3307 source: Utf8PathBuf,
3309
3310 dest: Utf8PathBuf,
3312
3313 #[source]
3315 error: std::io::Error,
3316 },
3317
3318 #[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 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 #[derive(Debug, Error)]
3350 pub enum UpdateVersionParseError {
3351 #[error("version string is empty")]
3353 EmptyString,
3354
3355 #[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 input: String,
3363
3364 #[source]
3366 error: semver::Error,
3367 },
3368
3369 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3371 InvalidVersion {
3372 input: String,
3374
3375 #[source]
3377 error: semver::Error,
3378 },
3379 }
3380
3381 fn extra_semver_output(input: &str) -> String {
3382 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}