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)]
937pub enum ToolConfigFileParseError {
938 #[error(
939 "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
940 )]
941 InvalidFormat {
943 input: String,
945 },
946
947 #[error("tool-config-file has invalid tool name: {input}")]
949 InvalidToolName {
950 input: String,
952
953 #[source]
955 error: InvalidToolName,
956 },
957
958 #[error("tool-config-file has empty config file path: {input}")]
960 EmptyConfigFile {
961 input: String,
963 },
964
965 #[error("tool-config-file is not an absolute path: {config_file}")]
967 ConfigFileNotAbsolute {
968 config_file: Utf8PathBuf,
970 },
971}
972
973#[derive(Debug, Error)]
975#[non_exhaustive]
976pub enum UserConfigError {
977 #[error("user config file not found at {path}")]
980 FileNotFound {
981 path: Utf8PathBuf,
983 },
984
985 #[error("failed to read user config at {path}")]
987 Read {
988 path: Utf8PathBuf,
990 #[source]
992 error: std::io::Error,
993 },
994
995 #[error("failed to parse user config at {path}")]
997 Parse {
998 path: Utf8PathBuf,
1000 #[source]
1002 error: toml::de::Error,
1003 },
1004
1005 #[error("user config path contains non-UTF-8 characters")]
1007 NonUtf8Path {
1008 #[source]
1010 error: FromPathBufError,
1011 },
1012
1013 #[error(
1015 "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
1016 )]
1017 OverridePlatformSpec {
1018 path: Utf8PathBuf,
1020 index: usize,
1022 #[source]
1024 error: Box<target_spec::Error>,
1025 },
1026}
1027
1028#[derive(Clone, Debug, Error)]
1030#[error("unrecognized value for max-fail: {reason}")]
1031pub struct MaxFailParseError {
1032 pub reason: String,
1034}
1035
1036impl MaxFailParseError {
1037 pub(crate) fn new(reason: impl Into<String>) -> Self {
1038 Self {
1039 reason: reason.into(),
1040 }
1041 }
1042}
1043
1044#[derive(Clone, Debug, Error)]
1046#[error(
1047 "unrecognized value for stress-count: {input}\n\
1048 (hint: expected either a positive integer or \"infinite\")"
1049)]
1050pub struct StressCountParseError {
1051 pub input: String,
1053}
1054
1055impl StressCountParseError {
1056 pub(crate) fn new(input: impl Into<String>) -> Self {
1057 Self {
1058 input: input.into(),
1059 }
1060 }
1061}
1062
1063#[derive(Clone, Debug, Error)]
1065#[non_exhaustive]
1066pub enum DebuggerCommandParseError {
1067 #[error(transparent)]
1069 ShellWordsParse(shell_words::ParseError),
1070
1071 #[error("debugger command cannot be empty")]
1073 EmptyCommand,
1074}
1075
1076#[derive(Clone, Debug, Error)]
1078#[non_exhaustive]
1079pub enum TracerCommandParseError {
1080 #[error(transparent)]
1082 ShellWordsParse(shell_words::ParseError),
1083
1084 #[error("tracer command cannot be empty")]
1086 EmptyCommand,
1087}
1088
1089#[derive(Clone, Debug, Error)]
1091#[error(
1092 "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
1093)]
1094pub struct TestThreadsParseError {
1095 pub input: String,
1097}
1098
1099impl TestThreadsParseError {
1100 pub(crate) fn new(input: impl Into<String>) -> Self {
1101 Self {
1102 input: input.into(),
1103 }
1104 }
1105}
1106
1107#[derive(Clone, Debug, Error)]
1110pub struct PartitionerBuilderParseError {
1111 expected_format: Option<&'static str>,
1112 message: Cow<'static, str>,
1113}
1114
1115impl PartitionerBuilderParseError {
1116 pub(crate) fn new(
1117 expected_format: Option<&'static str>,
1118 message: impl Into<Cow<'static, str>>,
1119 ) -> Self {
1120 Self {
1121 expected_format,
1122 message: message.into(),
1123 }
1124 }
1125}
1126
1127impl fmt::Display for PartitionerBuilderParseError {
1128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1129 match self.expected_format {
1130 Some(format) => {
1131 write!(
1132 f,
1133 "partition must be in the format \"{}\":\n{}",
1134 format, self.message
1135 )
1136 }
1137 None => write!(f, "{}", self.message),
1138 }
1139 }
1140}
1141
1142#[derive(Clone, Debug, Error)]
1145pub enum TestFilterBuildError {
1146 #[error("error constructing test filters")]
1148 Construct {
1149 #[from]
1151 error: aho_corasick::BuildError,
1152 },
1153}
1154
1155#[derive(Debug, Error)]
1157pub enum PathMapperConstructError {
1158 #[error("{kind} `{input}` failed to canonicalize")]
1160 Canonicalization {
1161 kind: PathMapperConstructKind,
1163
1164 input: Utf8PathBuf,
1166
1167 #[source]
1169 err: std::io::Error,
1170 },
1171 #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
1173 NonUtf8Path {
1174 kind: PathMapperConstructKind,
1176
1177 input: Utf8PathBuf,
1179
1180 #[source]
1182 err: FromPathBufError,
1183 },
1184 #[error("{kind} `{canonicalized_path}` is not a directory")]
1186 NotADirectory {
1187 kind: PathMapperConstructKind,
1189
1190 input: Utf8PathBuf,
1192
1193 canonicalized_path: Utf8PathBuf,
1195 },
1196}
1197
1198impl PathMapperConstructError {
1199 pub fn kind(&self) -> PathMapperConstructKind {
1201 match self {
1202 Self::Canonicalization { kind, .. }
1203 | Self::NonUtf8Path { kind, .. }
1204 | Self::NotADirectory { kind, .. } => *kind,
1205 }
1206 }
1207
1208 pub fn input(&self) -> &Utf8Path {
1210 match self {
1211 Self::Canonicalization { input, .. }
1212 | Self::NonUtf8Path { input, .. }
1213 | Self::NotADirectory { input, .. } => input,
1214 }
1215 }
1216}
1217
1218#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1223pub enum PathMapperConstructKind {
1224 WorkspaceRoot,
1226
1227 TargetDir,
1229
1230 BuildDir,
1232}
1233
1234impl fmt::Display for PathMapperConstructKind {
1235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1236 match self {
1237 Self::WorkspaceRoot => write!(f, "remapped workspace root"),
1238 Self::TargetDir => write!(f, "remapped target directory"),
1239 Self::BuildDir => write!(f, "remapped build directory"),
1240 }
1241 }
1242}
1243
1244#[derive(Debug, Error)]
1246pub enum RustBuildMetaParseError {
1247 #[error("error deserializing platform from build metadata")]
1249 PlatformDeserializeError(#[from] target_spec::Error),
1250
1251 #[error("the host platform could not be determined")]
1253 DetectBuildTargetError(#[source] target_spec::Error),
1254
1255 #[error("unsupported features in the build metadata: {message}")]
1257 Unsupported {
1258 message: String,
1260 },
1261}
1262
1263#[derive(Clone, Debug, thiserror::Error)]
1266#[error("invalid format version: {input}")]
1267pub struct FormatVersionError {
1268 pub input: String,
1270 #[source]
1272 pub error: FormatVersionErrorInner,
1273}
1274
1275#[derive(Clone, Debug, thiserror::Error)]
1277pub enum FormatVersionErrorInner {
1278 #[error("expected format version in form of `{expected}`")]
1280 InvalidFormat {
1281 expected: &'static str,
1283 },
1284 #[error("version component `{which}` could not be parsed as an integer")]
1286 InvalidInteger {
1287 which: &'static str,
1289 #[source]
1291 err: std::num::ParseIntError,
1292 },
1293 #[error("version component `{which}` value {value} is out of range {range:?}")]
1295 InvalidValue {
1296 which: &'static str,
1298 value: u8,
1300 range: std::ops::Range<u8>,
1302 },
1303}
1304
1305#[derive(Debug, Error)]
1309#[non_exhaustive]
1310pub enum FromMessagesError {
1311 #[error("error reading Cargo JSON messages")]
1313 ReadMessages(#[source] std::io::Error),
1314
1315 #[error("error querying package graph")]
1317 PackageGraph(#[source] guppy::Error),
1318
1319 #[error("missing kind for target {binary_name} in package {package_name}")]
1321 MissingTargetKind {
1322 package_name: String,
1324 binary_name: String,
1326 },
1327}
1328
1329#[derive(Debug, Error)]
1331#[non_exhaustive]
1332pub enum CreateTestListError {
1333 #[error(
1335 "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
1336 (hint: ensure project source is available at this location)"
1337 )]
1338 CwdIsNotDir {
1339 binary_id: RustBinaryId,
1341
1342 cwd: Utf8PathBuf,
1344 },
1345
1346 #[error(
1348 "for `{binary_id}`, running command `{}` failed to execute",
1349 shell_words::join(command)
1350 )]
1351 CommandExecFail {
1352 binary_id: RustBinaryId,
1354
1355 command: Vec<String>,
1357
1358 #[source]
1360 error: std::io::Error,
1361 },
1362
1363 #[error(
1365 "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1366 shell_words::join(command),
1367 display_exited_with(*exit_status),
1368 String::from_utf8_lossy(stdout),
1369 String::from_utf8_lossy(stderr),
1370 )]
1371 CommandFail {
1372 binary_id: RustBinaryId,
1374
1375 command: Vec<String>,
1377
1378 exit_status: ExitStatus,
1380
1381 stdout: Vec<u8>,
1383
1384 stderr: Vec<u8>,
1386 },
1387
1388 #[error(
1390 "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1391 shell_words::join(command),
1392 String::from_utf8_lossy(stdout),
1393 String::from_utf8_lossy(stderr)
1394 )]
1395 CommandNonUtf8 {
1396 binary_id: RustBinaryId,
1398
1399 command: Vec<String>,
1401
1402 stdout: Vec<u8>,
1404
1405 stderr: Vec<u8>,
1407 },
1408
1409 #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1411 ParseLine {
1412 binary_id: RustBinaryId,
1414
1415 message: Cow<'static, str>,
1417
1418 full_output: String,
1420 },
1421
1422 #[error(
1424 "error joining dynamic library paths for {}: [{}]",
1425 dylib_path_envvar(),
1426 itertools::join(.new_paths, ", ")
1427 )]
1428 DylibJoinPaths {
1429 new_paths: Vec<Utf8PathBuf>,
1431
1432 #[source]
1434 error: JoinPathsError,
1435 },
1436
1437 #[error("error creating Tokio runtime")]
1439 TokioRuntimeCreate(#[source] std::io::Error),
1440}
1441
1442impl CreateTestListError {
1443 pub(crate) fn parse_line(
1444 binary_id: RustBinaryId,
1445 message: impl Into<Cow<'static, str>>,
1446 full_output: impl Into<String>,
1447 ) -> Self {
1448 Self::ParseLine {
1449 binary_id,
1450 message: message.into(),
1451 full_output: full_output.into(),
1452 }
1453 }
1454
1455 pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1456 Self::DylibJoinPaths { new_paths, error }
1457 }
1458}
1459
1460#[derive(Debug, Error)]
1462#[non_exhaustive]
1463pub enum WriteTestListError {
1464 #[error("error writing to output")]
1466 Io(#[source] std::io::Error),
1467
1468 #[error("error serializing to JSON")]
1470 Json(#[source] serde_json::Error),
1471}
1472
1473#[derive(Debug, Error)]
1477pub enum ConfigureHandleInheritanceError {
1478 #[cfg(windows)]
1480 #[error("error configuring handle inheritance")]
1481 WindowsError(#[from] std::io::Error),
1482}
1483
1484#[derive(Debug, Error)]
1486#[non_exhaustive]
1487pub enum TestRunnerBuildError {
1488 #[error("error creating Tokio runtime")]
1490 TokioRuntimeCreate(#[source] std::io::Error),
1491
1492 #[error("error setting up signals")]
1494 SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1495}
1496
1497#[derive(Debug, Error)]
1499pub struct TestRunnerExecuteErrors<E> {
1500 pub report_error: Option<E>,
1502
1503 pub join_errors: Vec<tokio::task::JoinError>,
1506}
1507
1508impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1510 if let Some(report_error) = &self.report_error {
1511 write!(f, "error reporting results: {report_error}")?;
1512 }
1513
1514 if !self.join_errors.is_empty() {
1515 if self.report_error.is_some() {
1516 write!(f, "; ")?;
1517 }
1518
1519 write!(f, "errors joining tasks: ")?;
1520
1521 for (i, join_error) in self.join_errors.iter().enumerate() {
1522 if i > 0 {
1523 write!(f, ", ")?;
1524 }
1525
1526 write!(f, "{join_error}")?;
1527 }
1528 }
1529
1530 Ok(())
1531 }
1532}
1533
1534#[derive(Debug, Error)]
1538#[error(
1539 "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1540 supported_extensions()
1541)]
1542pub struct UnknownArchiveFormat {
1543 pub file_name: String,
1545}
1546
1547fn supported_extensions() -> String {
1548 ArchiveFormat::SUPPORTED_FORMATS
1549 .iter()
1550 .map(|(extension, _)| *extension)
1551 .join(", ")
1552}
1553
1554#[derive(Debug, Error)]
1556#[non_exhaustive]
1557pub enum ArchiveCreateError {
1558 #[error("error creating binary list")]
1560 CreateBinaryList(#[source] WriteTestListError),
1561
1562 #[error("extra path `{}` not found", .redactor.redact_path(path))]
1564 MissingExtraPath {
1565 path: Utf8PathBuf,
1567
1568 redactor: Redactor,
1573 },
1574
1575 #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1577 InputFileRead {
1578 step: ArchiveStep,
1580
1581 path: Utf8PathBuf,
1583
1584 is_dir: Option<bool>,
1586
1587 #[source]
1589 error: std::io::Error,
1590 },
1591
1592 #[error("error reading directory entry from `{path}")]
1594 DirEntryRead {
1595 path: Utf8PathBuf,
1597
1598 #[source]
1600 error: std::io::Error,
1601 },
1602
1603 #[error("error writing to archive")]
1605 OutputArchiveIo(#[source] std::io::Error),
1606
1607 #[error("error reporting archive status")]
1609 ReporterIo(#[source] std::io::Error),
1610}
1611
1612fn kind_str(is_dir: Option<bool>) -> &'static str {
1613 match is_dir {
1614 Some(true) => "directory",
1615 Some(false) => "file",
1616 None => "path",
1617 }
1618}
1619
1620#[derive(Debug, Error)]
1622pub enum MetadataMaterializeError {
1623 #[error("I/O error reading metadata file `{path}`")]
1625 Read {
1626 path: Utf8PathBuf,
1628
1629 #[source]
1631 error: std::io::Error,
1632 },
1633
1634 #[error("error deserializing metadata file `{path}`")]
1636 Deserialize {
1637 path: Utf8PathBuf,
1639
1640 #[source]
1642 error: serde_json::Error,
1643 },
1644
1645 #[error("error parsing Rust build metadata from `{path}`")]
1647 RustBuildMeta {
1648 path: Utf8PathBuf,
1650
1651 #[source]
1653 error: Box<RustBuildMetaParseError>,
1654 },
1655
1656 #[error("error building package graph from `{path}`")]
1658 PackageGraphConstruct {
1659 path: Utf8PathBuf,
1661
1662 #[source]
1664 error: Box<guppy::Error>,
1665 },
1666}
1667
1668#[derive(Debug, Error)]
1672#[non_exhaustive]
1673pub enum ArchiveReadError {
1674 #[error("I/O error reading archive")]
1676 Io(#[source] std::io::Error),
1677
1678 #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1680 NonUtf8Path(Vec<u8>),
1681
1682 #[error("path in archive `{0}` doesn't start with `target/`")]
1684 NoTargetPrefix(Utf8PathBuf),
1685
1686 #[error("path in archive `{path}` contains an invalid component `{component}`")]
1688 InvalidComponent {
1689 path: Utf8PathBuf,
1691
1692 component: String,
1694 },
1695
1696 #[error("corrupted archive: checksum read error for path `{path}`")]
1698 ChecksumRead {
1699 path: Utf8PathBuf,
1701
1702 #[source]
1704 error: std::io::Error,
1705 },
1706
1707 #[error("corrupted archive: invalid checksum for path `{path}`")]
1709 InvalidChecksum {
1710 path: Utf8PathBuf,
1712
1713 expected: u32,
1715
1716 actual: u32,
1718 },
1719
1720 #[error("metadata file `{0}` not found in archive")]
1722 MetadataFileNotFound(&'static Utf8Path),
1723
1724 #[error("error deserializing metadata file `{path}` in archive")]
1726 MetadataDeserializeError {
1727 path: &'static Utf8Path,
1729
1730 #[source]
1732 error: serde_json::Error,
1733 },
1734
1735 #[error("error building package graph from `{path}` in archive")]
1737 PackageGraphConstructError {
1738 path: &'static Utf8Path,
1740
1741 #[source]
1743 error: Box<guppy::Error>,
1744 },
1745}
1746
1747#[derive(Debug, Error)]
1751#[non_exhaustive]
1752pub enum ArchiveExtractError {
1753 #[error("error creating temporary directory")]
1755 TempDirCreate(#[source] std::io::Error),
1756
1757 #[error("error canonicalizing destination directory `{dir}`")]
1759 DestDirCanonicalization {
1760 dir: Utf8PathBuf,
1762
1763 #[source]
1765 error: std::io::Error,
1766 },
1767
1768 #[error("destination `{0}` already exists")]
1770 DestinationExists(Utf8PathBuf),
1771
1772 #[error("error reading archive")]
1774 Read(#[source] ArchiveReadError),
1775
1776 #[error("error deserializing Rust build metadata")]
1778 RustBuildMeta(#[from] RustBuildMetaParseError),
1779
1780 #[error("error writing file `{path}` to disk")]
1782 WriteFile {
1783 path: Utf8PathBuf,
1785
1786 #[source]
1788 error: std::io::Error,
1789 },
1790
1791 #[error("error reporting extract status")]
1793 ReporterIo(std::io::Error),
1794}
1795
1796#[derive(Debug, Error)]
1798#[non_exhaustive]
1799pub enum WriteEventError {
1800 #[error("error writing to output")]
1802 Io(#[source] std::io::Error),
1803
1804 #[error("error operating on path {file}")]
1806 Fs {
1807 file: Utf8PathBuf,
1809
1810 #[source]
1812 error: std::io::Error,
1813 },
1814
1815 #[error("error writing JUnit output to {file}")]
1817 Junit {
1818 file: Utf8PathBuf,
1820
1821 #[source]
1823 error: quick_junit::SerializeError,
1824 },
1825}
1826
1827#[derive(Debug, Error)]
1830#[non_exhaustive]
1831pub enum CargoConfigError {
1832 #[error("failed to retrieve current directory")]
1834 GetCurrentDir(#[source] std::io::Error),
1835
1836 #[error("current directory is invalid UTF-8")]
1838 CurrentDirInvalidUtf8(#[source] FromPathBufError),
1839
1840 #[error("failed to parse --config argument `{config_str}` as TOML")]
1842 CliConfigParseError {
1843 config_str: String,
1845
1846 #[source]
1848 error: toml_edit::TomlError,
1849 },
1850
1851 #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1853 CliConfigDeError {
1854 config_str: String,
1856
1857 #[source]
1859 error: toml_edit::de::Error,
1860 },
1861
1862 #[error(
1864 "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1865 )]
1866 InvalidCliConfig {
1867 config_str: String,
1869
1870 #[source]
1872 reason: InvalidCargoCliConfigReason,
1873 },
1874
1875 #[error("non-UTF-8 path encountered")]
1877 NonUtf8Path(#[source] FromPathBufError),
1878
1879 #[error("failed to retrieve the Cargo home directory")]
1881 GetCargoHome(#[source] std::io::Error),
1882
1883 #[error("failed to canonicalize path `{path}")]
1885 FailedPathCanonicalization {
1886 path: Utf8PathBuf,
1888
1889 #[source]
1891 error: std::io::Error,
1892 },
1893
1894 #[error("failed to read config at `{path}`")]
1896 ConfigReadError {
1897 path: Utf8PathBuf,
1899
1900 #[source]
1902 error: std::io::Error,
1903 },
1904
1905 #[error(transparent)]
1907 ConfigParseError(#[from] Box<CargoConfigParseError>),
1908}
1909
1910#[derive(Debug, Error)]
1914#[error("failed to parse config at `{path}`")]
1915pub struct CargoConfigParseError {
1916 pub path: Utf8PathBuf,
1918
1919 #[source]
1921 pub error: toml::de::Error,
1922}
1923
1924#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1928#[non_exhaustive]
1929pub enum InvalidCargoCliConfigReason {
1930 #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1932 NotDottedKv,
1933
1934 #[error("includes non-whitespace decoration")]
1936 IncludesNonWhitespaceDecoration,
1937
1938 #[error("sets a value to an inline table, which is not accepted")]
1940 SetsValueToInlineTable,
1941
1942 #[error("sets a value to an array of tables, which is not accepted")]
1944 SetsValueToArrayOfTables,
1945
1946 #[error("doesn't provide a value")]
1948 DoesntProvideValue,
1949}
1950
1951#[derive(Debug, Error)]
1953pub enum HostPlatformDetectError {
1954 #[error(
1957 "error spawning `rustc -vV`, and detecting the build \
1958 target failed as well\n\
1959 - rustc spawn error: {}\n\
1960 - build target error: {}\n",
1961 DisplayErrorChain::new_with_initial_indent(" ", error),
1962 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1963 )]
1964 RustcVvSpawnError {
1965 error: std::io::Error,
1967
1968 build_target_error: Box<target_spec::Error>,
1970 },
1971
1972 #[error(
1975 "`rustc -vV` failed with {}, and detecting the \
1976 build target failed as well\n\
1977 - `rustc -vV` stdout:\n{}\n\
1978 - `rustc -vV` stderr:\n{}\n\
1979 - build target error:\n{}\n",
1980 status,
1981 DisplayIndented { item: String::from_utf8_lossy(stdout), indent: " " },
1982 DisplayIndented { item: String::from_utf8_lossy(stderr), indent: " " },
1983 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1984 )]
1985 RustcVvFailed {
1986 status: ExitStatus,
1988
1989 stdout: Vec<u8>,
1991
1992 stderr: Vec<u8>,
1994
1995 build_target_error: Box<target_spec::Error>,
1997 },
1998
1999 #[error(
2002 "parsing `rustc -vV` output failed, and detecting the build target \
2003 failed as well\n\
2004 - host platform error:\n{}\n\
2005 - build target error:\n{}\n",
2006 DisplayErrorChain::new_with_initial_indent(" ", host_platform_error),
2007 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
2008 )]
2009 HostPlatformParseError {
2010 host_platform_error: Box<target_spec::Error>,
2012
2013 build_target_error: Box<target_spec::Error>,
2015 },
2016
2017 #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
2020 BuildTargetError {
2021 #[source]
2023 build_target_error: Box<target_spec::Error>,
2024 },
2025}
2026
2027#[derive(Debug, Error)]
2029pub enum TargetTripleError {
2030 #[error(
2032 "environment variable '{}' contained non-UTF-8 data",
2033 TargetTriple::CARGO_BUILD_TARGET_ENV
2034 )]
2035 InvalidEnvironmentVar,
2036
2037 #[error("error deserializing target triple from {source}")]
2039 TargetSpecError {
2040 source: TargetTripleSource,
2042
2043 #[source]
2045 error: target_spec::Error,
2046 },
2047
2048 #[error("target path `{path}` is not a valid file")]
2050 TargetPathReadError {
2051 source: TargetTripleSource,
2053
2054 path: Utf8PathBuf,
2056
2057 #[source]
2059 error: std::io::Error,
2060 },
2061
2062 #[error(
2064 "for custom platform obtained from {source}, \
2065 failed to create temporary directory for custom platform"
2066 )]
2067 CustomPlatformTempDirError {
2068 source: TargetTripleSource,
2070
2071 #[source]
2073 error: std::io::Error,
2074 },
2075
2076 #[error(
2078 "for custom platform obtained from {source}, \
2079 failed to write JSON to temporary path `{path}`"
2080 )]
2081 CustomPlatformWriteError {
2082 source: TargetTripleSource,
2084
2085 path: Utf8PathBuf,
2087
2088 #[source]
2090 error: std::io::Error,
2091 },
2092
2093 #[error(
2095 "for custom platform obtained from {source}, \
2096 failed to close temporary directory `{dir_path}`"
2097 )]
2098 CustomPlatformCloseError {
2099 source: TargetTripleSource,
2101
2102 dir_path: Utf8PathBuf,
2104
2105 #[source]
2107 error: std::io::Error,
2108 },
2109}
2110
2111impl TargetTripleError {
2112 pub fn source_report(&self) -> Option<miette::Report> {
2117 match self {
2118 Self::TargetSpecError { error, .. } => {
2119 Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
2120 }
2121 TargetTripleError::InvalidEnvironmentVar
2123 | TargetTripleError::TargetPathReadError { .. }
2124 | TargetTripleError::CustomPlatformTempDirError { .. }
2125 | TargetTripleError::CustomPlatformWriteError { .. }
2126 | TargetTripleError::CustomPlatformCloseError { .. } => None,
2127 }
2128 }
2129}
2130
2131#[derive(Debug, Error)]
2133pub enum TargetRunnerError {
2134 #[error("environment variable '{0}' contained non-UTF-8 data")]
2136 InvalidEnvironmentVar(String),
2137
2138 #[error("runner '{key}' = '{value}' did not contain a runner binary")]
2141 BinaryNotSpecified {
2142 key: PlatformRunnerSource,
2144
2145 value: String,
2147 },
2148}
2149
2150#[derive(Debug, Error)]
2152#[error("error setting up signal handler")]
2153pub struct SignalHandlerSetupError(#[from] std::io::Error);
2154
2155#[derive(Debug, Error)]
2157pub enum ShowTestGroupsError {
2158 #[error(
2160 "unknown test groups specified: {}\n(known groups: {})",
2161 unknown_groups.iter().join(", "),
2162 known_groups.iter().join(", "),
2163 )]
2164 UnknownGroups {
2165 unknown_groups: BTreeSet<TestGroup>,
2167
2168 known_groups: BTreeSet<TestGroup>,
2170 },
2171}
2172
2173#[derive(Debug, Error, PartialEq, Eq, Hash)]
2175pub enum InheritsError {
2176 #[error("the {} profile should not inherit from other profiles", .0)]
2178 DefaultProfileInheritance(String),
2179 #[error("profile {} inherits from an unknown profile {}", .0, .1)]
2181 UnknownInheritance(String, String),
2182 #[error("a self referential inheritance is detected from profile: {}", .0)]
2184 SelfReferentialInheritance(String),
2185 #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
2187 format!("[{}]", scc.iter().join(", "))
2188 }).join(", "))]
2189 InheritanceCycle(Vec<Vec<String>>),
2190}
2191
2192#[derive(Debug, Error)]
2198pub enum RunStoreError {
2199 #[error("error creating run directory `{run_dir}`")]
2201 RunDirCreate {
2202 run_dir: Utf8PathBuf,
2204
2205 #[source]
2207 error: std::io::Error,
2208 },
2209
2210 #[error("error acquiring lock on `{path}`")]
2212 FileLock {
2213 path: Utf8PathBuf,
2215
2216 #[source]
2218 error: std::io::Error,
2219 },
2220
2221 #[error(
2223 "timed out acquiring lock on `{path}` after {timeout_secs}s (is the state directory \
2224 on a networked filesystem?)"
2225 )]
2226 FileLockTimeout {
2227 path: Utf8PathBuf,
2229
2230 timeout_secs: u64,
2232 },
2233
2234 #[error("error reading run list from `{path}`")]
2236 RunListRead {
2237 path: Utf8PathBuf,
2239
2240 #[source]
2242 error: std::io::Error,
2243 },
2244
2245 #[error("error deserializing run list from `{path}`")]
2247 RunListDeserialize {
2248 path: Utf8PathBuf,
2250
2251 #[source]
2253 error: serde_json::Error,
2254 },
2255
2256 #[error("error serializing run list to `{path}`")]
2258 RunListSerialize {
2259 path: Utf8PathBuf,
2261
2262 #[source]
2264 error: serde_json::Error,
2265 },
2266
2267 #[error("error serializing rerun info")]
2269 RerunInfoSerialize {
2270 #[source]
2272 error: serde_json::Error,
2273 },
2274
2275 #[error("error serializing test list")]
2277 TestListSerialize {
2278 #[source]
2280 error: serde_json::Error,
2281 },
2282
2283 #[error("error serializing record options")]
2285 RecordOptionsSerialize {
2286 #[source]
2288 error: serde_json::Error,
2289 },
2290
2291 #[error("error serializing test event")]
2293 TestEventSerialize {
2294 #[source]
2296 error: serde_json::Error,
2297 },
2298
2299 #[error("error writing run list to `{path}`")]
2301 RunListWrite {
2302 path: Utf8PathBuf,
2304
2305 #[source]
2307 error: atomicwrites::Error<std::io::Error>,
2308 },
2309
2310 #[error("error writing to store at `{store_path}`")]
2312 StoreWrite {
2313 store_path: Utf8PathBuf,
2315
2316 #[source]
2318 error: StoreWriterError,
2319 },
2320
2321 #[error("error creating run log at `{path}`")]
2323 RunLogCreate {
2324 path: Utf8PathBuf,
2326
2327 #[source]
2329 error: std::io::Error,
2330 },
2331
2332 #[error("error writing to run log at `{path}`")]
2334 RunLogWrite {
2335 path: Utf8PathBuf,
2337
2338 #[source]
2340 error: std::io::Error,
2341 },
2342
2343 #[error("error flushing run log at `{path}`")]
2345 RunLogFlush {
2346 path: Utf8PathBuf,
2348
2349 #[source]
2351 error: std::io::Error,
2352 },
2353
2354 #[error(
2356 "cannot write to record store: runs.json.zst format version {file_version} is newer than \
2357 supported version {max_supported_version}"
2358 )]
2359 FormatVersionTooNew {
2360 file_version: RunsJsonFormatVersion,
2362 max_supported_version: RunsJsonFormatVersion,
2364 },
2365}
2366
2367#[derive(Debug, Error)]
2369#[non_exhaustive]
2370pub enum StoreWriterError {
2371 #[error("error creating store")]
2373 Create {
2374 #[source]
2376 error: std::io::Error,
2377 },
2378
2379 #[error("error writing to path `{path}` in store")]
2381 Write {
2382 path: Utf8PathBuf,
2384
2385 #[source]
2387 error: std::io::Error,
2388 },
2389
2390 #[error("error compressing data")]
2392 Compress {
2393 #[source]
2395 error: std::io::Error,
2396 },
2397
2398 #[error("error finalizing store")]
2400 Finish {
2401 #[source]
2403 error: std::io::Error,
2404 },
2405
2406 #[error("error flushing store")]
2408 Flush {
2409 #[source]
2411 error: std::io::Error,
2412 },
2413}
2414
2415#[derive(Debug, Error)]
2417pub enum RecordReporterError {
2418 #[error(transparent)]
2420 RunStore(RunStoreError),
2421
2422 #[error("record writer thread panicked: {message}")]
2424 WriterPanic {
2425 message: String,
2427 },
2428}
2429
2430#[derive(Debug, Error)]
2432pub enum StateDirError {
2433 #[error("could not determine platform base directory strategy")]
2437 BaseDirStrategy(#[source] HomeDirError),
2438
2439 #[error("platform state directory is not valid UTF-8: {path:?}")]
2441 StateDirNotUtf8 {
2442 path: PathBuf,
2444 },
2445
2446 #[error("could not canonicalize workspace path `{workspace_root}`")]
2448 Canonicalize {
2449 workspace_root: Utf8PathBuf,
2451 #[source]
2453 error: std::io::Error,
2454 },
2455}
2456
2457#[derive(Debug, Error)]
2459pub enum RecordSetupError {
2460 #[error("could not determine platform state directory for recording")]
2462 StateDirNotFound(#[source] StateDirError),
2463
2464 #[error("failed to create run store")]
2466 StoreCreate(#[source] RunStoreError),
2467
2468 #[error("failed to lock run store")]
2470 StoreLock(#[source] RunStoreError),
2471
2472 #[error("failed to create run recorder")]
2474 RecorderCreate(#[source] RunStoreError),
2475}
2476
2477#[derive(Debug, Error)]
2479pub enum RecordPruneError {
2480 #[error("error deleting run `{run_id}` at `{path}`")]
2482 DeleteRun {
2483 run_id: ReportUuid,
2485
2486 path: Utf8PathBuf,
2488
2489 #[source]
2491 error: std::io::Error,
2492 },
2493
2494 #[error("error calculating size of `{path}`")]
2496 CalculateSize {
2497 path: Utf8PathBuf,
2499
2500 #[source]
2502 error: std::io::Error,
2503 },
2504
2505 #[error("error deleting orphaned directory `{path}`")]
2507 DeleteOrphan {
2508 path: Utf8PathBuf,
2510
2511 #[source]
2513 error: std::io::Error,
2514 },
2515
2516 #[error("error reading runs directory `{path}`")]
2518 ReadRunsDir {
2519 path: Utf8PathBuf,
2521
2522 #[source]
2524 error: std::io::Error,
2525 },
2526
2527 #[error("error reading directory entry in `{dir}`")]
2529 ReadDirEntry {
2530 dir: Utf8PathBuf,
2532
2533 #[source]
2535 error: std::io::Error,
2536 },
2537
2538 #[error("error reading file type for `{path}`")]
2540 ReadFileType {
2541 path: Utf8PathBuf,
2543
2544 #[source]
2546 error: std::io::Error,
2547 },
2548}
2549
2550#[derive(Clone, Debug, PartialEq, Eq, Error)]
2555#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2556pub struct InvalidRunIdSelector {
2557 pub input: String,
2559}
2560
2561#[derive(Clone, Debug, PartialEq, Eq, Error)]
2567#[error(
2568 "invalid run ID selector `{input}`: expected `latest`, hex digits, \
2569 or a file path (ending in `.zip` or containing path separators)"
2570)]
2571pub struct InvalidRunIdOrRecordingSelector {
2572 pub input: String,
2574}
2575
2576#[derive(Debug, Error)]
2578pub enum RunIdResolutionError {
2579 #[error("no recorded run found matching `{prefix}`")]
2581 NotFound {
2582 prefix: String,
2584 },
2585
2586 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2588 Ambiguous {
2589 prefix: String,
2591
2592 count: usize,
2594
2595 candidates: Vec<RecordedRunInfo>,
2597
2598 run_id_index: RunIdIndex,
2600 },
2601
2602 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2604 InvalidPrefix {
2605 prefix: String,
2607 },
2608
2609 #[error("no recorded runs exist")]
2611 NoRuns,
2612}
2613
2614#[derive(Debug, Error)]
2616pub enum RecordReadError {
2617 #[error("run not found at `{path}`")]
2619 RunNotFound {
2620 path: Utf8PathBuf,
2622 },
2623
2624 #[error("error opening archive at `{path}`")]
2626 OpenArchive {
2627 path: Utf8PathBuf,
2629
2630 #[source]
2632 error: std::io::Error,
2633 },
2634
2635 #[error("error parsing archive at `{path}`")]
2637 ParseArchive {
2638 path: Utf8PathBuf,
2640
2641 #[source]
2643 error: std::io::Error,
2644 },
2645
2646 #[error("error reading `{file_name}` from archive")]
2648 ReadArchiveFile {
2649 file_name: String,
2651
2652 #[source]
2654 error: std::io::Error,
2655 },
2656
2657 #[error("error opening run log at `{path}`")]
2659 OpenRunLog {
2660 path: Utf8PathBuf,
2662
2663 #[source]
2665 error: std::io::Error,
2666 },
2667
2668 #[error("error reading line {line_number} from run log")]
2670 ReadRunLog {
2671 line_number: usize,
2673
2674 #[source]
2676 error: std::io::Error,
2677 },
2678
2679 #[error("error parsing event at line {line_number}")]
2681 ParseEvent {
2682 line_number: usize,
2684
2685 #[source]
2687 error: serde_json::Error,
2688 },
2689
2690 #[error("required file `{file_name}` not found in archive")]
2692 FileNotFound {
2693 file_name: String,
2695 },
2696
2697 #[error("error decompressing data from `{file_name}`")]
2699 Decompress {
2700 file_name: String,
2702
2703 #[source]
2705 error: std::io::Error,
2706 },
2707
2708 #[error(
2713 "unknown output file type `{file_name}` in archive \
2714 (archive may have been created by a newer version of nextest)"
2715 )]
2716 UnknownOutputType {
2717 file_name: String,
2719 },
2720
2721 #[error(
2723 "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2724 )]
2725 FileTooLarge {
2726 file_name: String,
2728
2729 size: u64,
2731
2732 limit: u64,
2734 },
2735
2736 #[error(
2741 "file `{file_name}` size mismatch: header claims {claimed_size} bytes, \
2742 but read {actual_size} bytes (archive may be corrupt or tampered)"
2743 )]
2744 SizeMismatch {
2745 file_name: String,
2747
2748 claimed_size: u64,
2750
2751 actual_size: u64,
2753 },
2754
2755 #[error("error deserializing `{file_name}`")]
2757 DeserializeMetadata {
2758 file_name: String,
2760
2761 #[source]
2763 error: serde_json::Error,
2764 },
2765
2766 #[error("failed to extract `{store_path}` to `{output_path}`")]
2768 ExtractFile {
2769 store_path: String,
2771
2772 output_path: Utf8PathBuf,
2774
2775 #[source]
2777 error: std::io::Error,
2778 },
2779
2780 #[error("error reading portable recording")]
2782 PortableRecording(#[source] PortableRecordingReadError),
2783}
2784
2785#[derive(Debug, Error)]
2787#[non_exhaustive]
2788pub enum PortableRecordingError {
2789 #[error("run directory does not exist: {path}")]
2791 RunDirNotFound {
2792 path: Utf8PathBuf,
2794 },
2795
2796 #[error("required file missing from run directory `{run_dir}`: `{file_name}`")]
2798 RequiredFileMissing {
2799 run_dir: Utf8PathBuf,
2801 file_name: &'static str,
2803 },
2804
2805 #[error("failed to serialize manifest")]
2807 SerializeManifest(#[source] serde_json::Error),
2808
2809 #[error("failed to start file {file_name} in archive")]
2811 ZipStartFile {
2812 file_name: &'static str,
2814 #[source]
2816 source: std::io::Error,
2817 },
2818
2819 #[error("failed to write {file_name} to archive")]
2821 ZipWrite {
2822 file_name: &'static str,
2824 #[source]
2826 source: std::io::Error,
2827 },
2828
2829 #[error("failed to read {file_name}")]
2831 ReadFile {
2832 file_name: &'static str,
2834 #[source]
2836 source: std::io::Error,
2837 },
2838
2839 #[error("failed to finalize archive")]
2841 ZipFinalize(#[source] std::io::Error),
2842
2843 #[error("failed to write archive atomically to {path}")]
2845 AtomicWrite {
2846 path: Utf8PathBuf,
2848 #[source]
2850 source: std::io::Error,
2851 },
2852}
2853
2854#[derive(Debug, Error)]
2856#[non_exhaustive]
2857pub enum PortableRecordingReadError {
2858 #[error("failed to open archive at `{path}`")]
2860 OpenArchive {
2861 path: Utf8PathBuf,
2863 #[source]
2865 error: std::io::Error,
2866 },
2867
2868 #[error("failed to read archive at `{path}`")]
2870 ReadArchive {
2871 path: Utf8PathBuf,
2873 #[source]
2875 error: std::io::Error,
2876 },
2877
2878 #[error("required file `{file_name}` missing from archive at `{path}`")]
2880 MissingFile {
2881 path: Utf8PathBuf,
2883 file_name: Cow<'static, str>,
2885 },
2886
2887 #[error("failed to parse manifest from archive at `{path}`")]
2889 ParseManifest {
2890 path: Utf8PathBuf,
2892 #[source]
2894 error: serde_json::Error,
2895 },
2896
2897 #[error(
2899 "portable recording format version {found} in `{path}` is incompatible: {incompatibility} \
2900 (this nextest supports version {supported})"
2901 )]
2902 UnsupportedFormatVersion {
2903 path: Utf8PathBuf,
2905 found: PortableRecordingFormatVersion,
2907 supported: PortableRecordingFormatVersion,
2909 incompatibility: PortableRecordingVersionIncompatibility,
2911 },
2912
2913 #[error(
2915 "store format version {found} in `{path}` is incompatible: {incompatibility} \
2916 (this nextest supports version {supported})"
2917 )]
2918 UnsupportedStoreFormatVersion {
2919 path: Utf8PathBuf,
2921 found: StoreFormatVersion,
2923 supported: StoreFormatVersion,
2925 incompatibility: StoreVersionIncompatibility,
2927 },
2928
2929 #[error(
2931 "file `{file_name}` in archive `{path}` is too large \
2932 ({size} bytes, limit is {limit} bytes)"
2933 )]
2934 FileTooLarge {
2935 path: Utf8PathBuf,
2937 file_name: Cow<'static, str>,
2939 size: u64,
2941 limit: u64,
2943 },
2944
2945 #[error("failed to extract `{file_name}` from archive `{archive_path}` to `{output_path}`")]
2947 ExtractFile {
2948 archive_path: Utf8PathBuf,
2950 file_name: &'static str,
2952 output_path: Utf8PathBuf,
2954 #[source]
2956 error: std::io::Error,
2957 },
2958
2959 #[error(
2963 "for portable recording `{archive_path}`, the inner archive is stored \
2964 with {:?} compression -- it must be stored uncompressed",
2965 compression
2966 )]
2967 CompressedInnerArchive {
2968 archive_path: Utf8PathBuf,
2970 compression: CompressionMethod,
2972 },
2973
2974 #[error(
2978 "archive at `{path}` has no manifest and is not a wrapper archive \
2979 (contains {file_count} {}, {zip_count} of which {} in .zip)",
2980 plural::files_str(*file_count),
2981 plural::end_str(*zip_count)
2982 )]
2983 NotAWrapperArchive {
2984 path: Utf8PathBuf,
2986 file_count: usize,
2988 zip_count: usize,
2990 },
2991
2992 #[error("unexpected I/O error while probing seekability of `{path}`")]
2999 SeekProbe {
3000 path: Utf8PathBuf,
3002 #[source]
3004 error: std::io::Error,
3005 },
3006
3007 #[error("failed to spool non-seekable input `{path}` to a temporary file")]
3013 SpoolTempFile {
3014 path: Utf8PathBuf,
3016 #[source]
3018 error: std::io::Error,
3019 },
3020
3021 #[error(
3023 "recording at `{path}` exceeds the spool size limit \
3024 ({}); use a file path instead of process substitution",
3025 SizeDisplay(.limit.0)
3026 )]
3027 SpoolTooLarge {
3028 path: Utf8PathBuf,
3030 limit: ByteSize,
3032 },
3033}
3034
3035#[derive(Debug, Error)]
3037pub enum ChromeTraceError {
3038 #[error("error reading recorded events")]
3040 ReadError(#[source] RecordReadError),
3041
3042 #[error(
3044 "event for test `{test_name}` in binary `{binary_id}` \
3045 has no prior TestStarted event (corrupt or truncated log?)"
3046 )]
3047 MissingTestStart {
3048 test_name: TestCaseName,
3050
3051 binary_id: RustBinaryId,
3053 },
3054
3055 #[error(
3057 "SetupScriptSlow for script `{script_id}` \
3058 has no prior SetupScriptStarted event (corrupt or truncated log?)"
3059 )]
3060 MissingScriptStart {
3061 script_id: ScriptId,
3063 },
3064
3065 #[error(
3068 "StressSubRunFinished has no prior StressSubRunStarted event \
3069 (corrupt or truncated log?)"
3070 )]
3071 MissingStressSubRunStart,
3072
3073 #[error("error serializing Chrome trace JSON")]
3075 SerializeError(#[source] serde_json::Error),
3076}
3077
3078#[derive(Debug, Error)]
3082pub enum TestListFromSummaryError {
3083 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
3085 PackageNotFound {
3086 name: String,
3088
3089 package_id: String,
3091 },
3092
3093 #[error("error parsing rust build metadata")]
3095 RustBuildMeta(#[source] RustBuildMetaParseError),
3096}
3097
3098#[cfg(feature = "self-update")]
3099mod self_update_errors {
3100 use super::*;
3101 use crate::update::PrereleaseKind;
3102 use mukti_metadata::ReleaseStatus;
3103 use semver::{Version, VersionReq};
3104
3105 #[derive(Debug, Error)]
3109 #[non_exhaustive]
3110 pub enum UpdateError {
3111 #[error("failed to read release metadata from `{path}`")]
3113 ReadLocalMetadata {
3114 path: Utf8PathBuf,
3116
3117 #[source]
3119 error: std::io::Error,
3120 },
3121
3122 #[error("self-update failed")]
3124 SelfUpdate(#[source] self_update::errors::Error),
3125
3126 #[error("error performing HTTP request")]
3128 Http(#[source] ureq::Error),
3129
3130 #[error("error reading HTTP response body")]
3132 HttpBody(#[source] std::io::Error),
3133
3134 #[error("Content-Length header present but could not be parsed as an integer: {value:?}")]
3137 ContentLengthInvalid {
3138 value: String,
3140 },
3141
3142 #[error("content length mismatch: expected {expected} bytes, received {actual} bytes")]
3145 ContentLengthMismatch {
3146 expected: u64,
3148 actual: u64,
3150 },
3151
3152 #[error("deserializing release metadata failed")]
3154 ReleaseMetadataDe(#[source] serde_json::Error),
3155
3156 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
3158 VersionNotFound {
3159 version: Version,
3161
3162 known: Vec<(Version, ReleaseStatus)>,
3164 },
3165
3166 #[error("no version found matching requirement `{req}`")]
3168 NoMatchForVersionReq {
3169 req: VersionReq,
3171 },
3172
3173 #[error("no stable version found")]
3175 NoStableVersion,
3176
3177 #[error("no version found matching {} channel", kind.description())]
3179 NoVersionForPrereleaseKind {
3180 kind: PrereleaseKind,
3182 },
3183
3184 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
3186 MuktiProjectNotFound {
3187 not_found: String,
3189
3190 known: Vec<String>,
3192 },
3193
3194 #[error(
3196 "for version {version}, no release information found for target `{triple}` \
3197 (known targets: {})",
3198 known_triples.iter().join(", ")
3199 )]
3200 NoTargetData {
3201 version: Version,
3203
3204 triple: String,
3206
3207 known_triples: BTreeSet<String>,
3209 },
3210
3211 #[error("the current executable's path could not be determined")]
3213 CurrentExe(#[source] std::io::Error),
3214
3215 #[error("temporary directory could not be created at `{location}`")]
3217 TempDirCreate {
3218 location: Utf8PathBuf,
3220
3221 #[source]
3223 error: std::io::Error,
3224 },
3225
3226 #[error("temporary archive could not be created at `{archive_path}`")]
3228 TempArchiveCreate {
3229 archive_path: Utf8PathBuf,
3231
3232 #[source]
3234 error: std::io::Error,
3235 },
3236
3237 #[error("error writing to temporary archive at `{archive_path}`")]
3239 TempArchiveWrite {
3240 archive_path: Utf8PathBuf,
3242
3243 #[source]
3245 error: std::io::Error,
3246 },
3247
3248 #[error("error reading from temporary archive at `{archive_path}`")]
3250 TempArchiveRead {
3251 archive_path: Utf8PathBuf,
3253
3254 #[source]
3256 error: std::io::Error,
3257 },
3258
3259 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
3261 ChecksumMismatch {
3262 expected: String,
3264
3265 actual: String,
3267 },
3268
3269 #[error("error renaming `{source}` to `{dest}`")]
3271 FsRename {
3272 source: Utf8PathBuf,
3274
3275 dest: Utf8PathBuf,
3277
3278 #[source]
3280 error: std::io::Error,
3281 },
3282
3283 #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
3285 SelfSetup(#[source] std::io::Error),
3286 }
3287
3288 fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
3289 use std::fmt::Write;
3290
3291 const DISPLAY_COUNT: usize = 4;
3293
3294 let display_versions: Vec<_> = versions
3295 .iter()
3296 .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
3297 .map(|(v, _)| v.to_string())
3298 .take(DISPLAY_COUNT)
3299 .collect();
3300 let mut display_str = display_versions.join(", ");
3301 if versions.len() > display_versions.len() {
3302 write!(
3303 display_str,
3304 " and {} others",
3305 versions.len() - display_versions.len()
3306 )
3307 .unwrap();
3308 }
3309
3310 display_str
3311 }
3312
3313 #[derive(Debug, Error)]
3315 pub enum UpdateVersionParseError {
3316 #[error("version string is empty")]
3318 EmptyString,
3319
3320 #[error(
3322 "`{input}` is not a valid semver requirement\n\
3323 (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
3324 )]
3325 InvalidVersionReq {
3326 input: String,
3328
3329 #[source]
3331 error: semver::Error,
3332 },
3333
3334 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3336 InvalidVersion {
3337 input: String,
3339
3340 #[source]
3342 error: semver::Error,
3343 },
3344 }
3345
3346 fn extra_semver_output(input: &str) -> String {
3347 if input.parse::<VersionReq>().is_ok() {
3350 format!(
3351 "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
3352 )
3353 } else {
3354 "".to_owned()
3355 }
3356 }
3357}
3358
3359#[cfg(feature = "self-update")]
3360pub use self_update_errors::*;
3361
3362#[cfg(test)]
3363mod tests {
3364 use super::*;
3365
3366 #[test]
3367 fn display_error_chain() {
3368 let err1 = StringError::new("err1", None);
3369
3370 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
3371
3372 let err2 = StringError::new("err2", Some(err1));
3373 let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
3374
3375 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
3376 err3
3377 err3 line 2
3378 caused by:
3379 - err2
3380 - err1
3381 ");
3382 }
3383
3384 #[test]
3385 fn display_error_list() {
3386 let err1 = StringError::new("err1", None);
3387
3388 let error_list =
3389 ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
3390 .expect(">= 1 error");
3391 insta::assert_snapshot!(format!("{}", error_list), @"err1");
3392 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
3393
3394 let err2 = StringError::new("err2", Some(err1));
3395 let err3 = StringError::new("err3", Some(err2));
3396
3397 let error_list =
3398 ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
3399 .expect(">= 1 error");
3400 insta::assert_snapshot!(format!("{}", error_list), @"err3");
3401 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3402 err3
3403 caused by:
3404 - err2
3405 - err1
3406 ");
3407
3408 let err4 = StringError::new("err4", None);
3409 let err5 = StringError::new("err5", Some(err4));
3410 let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
3411
3412 let error_list = ErrorList::<StringError>::new(
3413 "waiting for the heat death of the universe",
3414 vec![err3, err6],
3415 )
3416 .expect(">= 1 error");
3417
3418 insta::assert_snapshot!(format!("{}", error_list), @"
3419 2 errors occurred waiting for the heat death of the universe:
3420 * err3
3421 caused by:
3422 - err2
3423 - err1
3424 * err6
3425 err6 line 2
3426 caused by:
3427 - err5
3428 - err4
3429 ");
3430 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3431 2 errors occurred waiting for the heat death of the universe:
3432 * err3
3433 caused by:
3434 - err2
3435 - err1
3436 * err6
3437 err6 line 2
3438 caused by:
3439 - err5
3440 - err4
3441 ");
3442 }
3443
3444 #[derive(Clone, Debug, Error)]
3445 struct StringError {
3446 message: String,
3447 #[source]
3448 source: Option<Box<StringError>>,
3449 }
3450
3451 impl StringError {
3452 fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
3453 Self {
3454 message: message.into(),
3455 source: source.map(Box::new),
3456 }
3457 }
3458 }
3459
3460 impl fmt::Display for StringError {
3461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3462 write!(f, "{}", self.message)
3463 }
3464 }
3465}