1use crate::{
7 cargo_config::{TargetTriple, TargetTripleSource},
8 config::{
9 core::{ConfigExperimental, ToolName},
10 elements::{CustomTestGroup, TestGroup},
11 scripts::{ProfileScriptType, ScriptId, ScriptType},
12 },
13 helpers::{display_exited_with, dylib_path_envvar, plural},
14 indenter::{DisplayIndented, indented},
15 record::{
16 PortableRecordingFormatVersion, PortableRecordingVersionIncompatibility, RecordedRunInfo,
17 RunIdIndex, RunsJsonFormatVersion, StoreFormatVersion, StoreVersionIncompatibility,
18 },
19 redact::{Redactor, SizeDisplay},
20 reuse_build::{ArchiveFormat, ArchiveStep},
21 target_runner::PlatformRunnerSource,
22};
23use bytesize::ByteSize;
24use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
25use config::ConfigError;
26use etcetera::HomeDirError;
27use itertools::{Either, Itertools};
28use nextest_filtering::errors::FiltersetParseErrors;
29use nextest_metadata::RustBinaryId;
30use quick_junit::ReportUuid;
31use serde::{Deserialize, Serialize};
32use smol_str::SmolStr;
33use std::{
34 borrow::Cow,
35 collections::BTreeSet,
36 env::JoinPathsError,
37 fmt::{self, Write as _},
38 path::PathBuf,
39 process::ExitStatus,
40 sync::Arc,
41};
42use target_spec_miette::IntoMietteDiagnostic;
43use thiserror::Error;
44pub use zip::result::ZipError;
46
47#[derive(Debug, Error)]
49#[error(
50 "failed to parse nextest config at `{config_file}`{}",
51 provided_by_tool(tool.as_ref())
52)]
53#[non_exhaustive]
54pub struct ConfigParseError {
55 config_file: Utf8PathBuf,
56 tool: Option<ToolName>,
57 #[source]
58 kind: ConfigParseErrorKind,
59}
60
61impl ConfigParseError {
62 pub(crate) fn new(
63 config_file: impl Into<Utf8PathBuf>,
64 tool: Option<&ToolName>,
65 kind: ConfigParseErrorKind,
66 ) -> Self {
67 Self {
68 config_file: config_file.into(),
69 tool: tool.cloned(),
70 kind,
71 }
72 }
73
74 pub fn config_file(&self) -> &Utf8Path {
76 &self.config_file
77 }
78
79 pub fn tool(&self) -> Option<&ToolName> {
81 self.tool.as_ref()
82 }
83
84 pub fn kind(&self) -> &ConfigParseErrorKind {
86 &self.kind
87 }
88}
89
90pub fn provided_by_tool(tool: Option<&ToolName>) -> String {
92 match tool {
93 Some(tool) => format!(" provided by tool `{tool}`"),
94 None => String::new(),
95 }
96}
97
98#[derive(Debug, Error)]
102#[non_exhaustive]
103pub enum ConfigParseErrorKind {
104 #[error(transparent)]
106 BuildError(Box<ConfigError>),
107 #[error(transparent)]
109 TomlParseError(Box<toml::de::Error>),
110 #[error(transparent)]
111 DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
113 #[error(transparent)]
115 VersionOnlyReadError(std::io::Error),
116 #[error(transparent)]
118 VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
119 #[error("error parsing compiled data (destructure this variant for more details)")]
121 CompileErrors(Vec<ConfigCompileError>),
122 #[error("invalid test groups defined: {}\n(test groups cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
124 InvalidTestGroupsDefined(BTreeSet<CustomTestGroup>),
125 #[error(
127 "invalid test groups defined by tool: {}\n(test groups must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
128 InvalidTestGroupsDefinedByTool(BTreeSet<CustomTestGroup>),
129 #[error("unknown test groups specified by config (destructure this variant for more details)")]
131 UnknownTestGroups {
132 errors: Vec<UnknownTestGroupError>,
134
135 known_groups: BTreeSet<TestGroup>,
137 },
138 #[error(
140 "both `[script.*]` and `[scripts.*]` defined\n\
141 (hint: [script.*] will be removed in the future: switch to [scripts.setup.*])"
142 )]
143 BothScriptAndScriptsDefined,
144 #[error("invalid config scripts defined: {}\n(config scripts cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
146 InvalidConfigScriptsDefined(BTreeSet<ScriptId>),
147 #[error(
149 "invalid config scripts defined by tool: {}\n(config scripts must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
150 InvalidConfigScriptsDefinedByTool(BTreeSet<ScriptId>),
151 #[error(
153 "config script names used more than once: {}\n\
154 (config script names must be unique across all script types)", .0.iter().join(", ")
155 )]
156 DuplicateConfigScriptNames(BTreeSet<ScriptId>),
157 #[error(
159 "errors in profile-specific config scripts (destructure this variant for more details)"
160 )]
161 ProfileScriptErrors {
162 errors: Box<ProfileScriptErrors>,
164
165 known_scripts: BTreeSet<ScriptId>,
167 },
168 #[error("unknown experimental features defined (destructure this variant for more details)")]
170 UnknownExperimentalFeatures {
171 unknown: BTreeSet<String>,
173
174 known: BTreeSet<ConfigExperimental>,
176 },
177 #[error(
181 "tool config file specifies experimental features `{}` \
182 -- only repository config files can do so",
183 .features.iter().join(", "),
184 )]
185 ExperimentalFeaturesInToolConfig {
186 features: BTreeSet<String>,
188 },
189 #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
191 ExperimentalFeaturesNotEnabled {
192 missing_features: BTreeSet<ConfigExperimental>,
194 },
195 #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
197 InheritanceErrors(Vec<InheritsError>),
198}
199
200#[derive(Debug)]
203#[non_exhaustive]
204pub struct ConfigCompileError {
205 pub profile_name: String,
207
208 pub section: ConfigCompileSection,
210
211 pub kind: ConfigCompileErrorKind,
213}
214
215#[derive(Debug)]
218pub enum ConfigCompileSection {
219 DefaultFilter,
221
222 Override(usize),
224
225 Script(usize),
227}
228
229#[derive(Debug)]
231#[non_exhaustive]
232pub enum ConfigCompileErrorKind {
233 ConstraintsNotSpecified {
235 default_filter_specified: bool,
240 },
241
242 FilterAndDefaultFilterSpecified,
246
247 Parse {
249 host_parse_error: Option<target_spec::Error>,
251
252 target_parse_error: Option<target_spec::Error>,
254
255 filter_parse_errors: Vec<FiltersetParseErrors>,
257 },
258}
259
260impl ConfigCompileErrorKind {
261 pub fn reports(&self) -> impl Iterator<Item = miette::Report> + '_ {
263 match self {
264 Self::ConstraintsNotSpecified {
265 default_filter_specified,
266 } => {
267 let message = if *default_filter_specified {
268 "for override with `default-filter`, `platform` must also be specified"
269 } else {
270 "at least one of `platform` and `filter` must be specified"
271 };
272 Either::Left(std::iter::once(miette::Report::msg(message)))
273 }
274 Self::FilterAndDefaultFilterSpecified => {
275 Either::Left(std::iter::once(miette::Report::msg(
276 "at most one of `filter` and `default-filter` must be specified",
277 )))
278 }
279 Self::Parse {
280 host_parse_error,
281 target_parse_error,
282 filter_parse_errors,
283 } => {
284 let host_parse_report = host_parse_error
285 .as_ref()
286 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
287 let target_parse_report = target_parse_error
288 .as_ref()
289 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
290 let filter_parse_reports =
291 filter_parse_errors.iter().flat_map(|filter_parse_errors| {
292 filter_parse_errors.errors.iter().map(|single_error| {
293 miette::Report::new(single_error.clone())
294 .with_source_code(filter_parse_errors.input.to_owned())
295 })
296 });
297
298 Either::Right(
299 host_parse_report
300 .into_iter()
301 .chain(target_parse_report)
302 .chain(filter_parse_reports),
303 )
304 }
305 }
306 }
307}
308
309#[derive(Clone, Debug, Error)]
311#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
312pub struct TestPriorityOutOfRange {
313 pub priority: i8,
315}
316
317#[derive(Clone, Debug, Error)]
319pub enum ChildStartError {
320 #[error("error creating temporary path for setup script")]
322 TempPath(#[source] Arc<std::io::Error>),
323
324 #[error("error spawning child process")]
326 Spawn(#[source] Arc<std::io::Error>),
327}
328
329#[derive(Clone, Debug, Error)]
331pub enum SetupScriptOutputError {
332 #[error("error opening environment file `{path}`")]
334 EnvFileOpen {
335 path: Utf8PathBuf,
337
338 #[source]
340 error: Arc<std::io::Error>,
341 },
342
343 #[error("error reading environment file `{path}`")]
345 EnvFileRead {
346 path: Utf8PathBuf,
348
349 #[source]
351 error: Arc<std::io::Error>,
352 },
353
354 #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
356 EnvFileParse {
357 path: Utf8PathBuf,
359 line: String,
361 },
362
363 #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
365 EnvFileReservedKey {
366 key: String,
368 },
369}
370
371#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
376pub struct ErrorList<T> {
377 description: Cow<'static, str>,
379 inner: Vec<T>,
381}
382
383impl<T: std::error::Error> ErrorList<T> {
384 pub(crate) fn new<U>(description: &'static str, errors: Vec<U>) -> Option<Self>
385 where
386 T: From<U>,
387 {
388 if errors.is_empty() {
389 None
390 } else {
391 Some(Self {
392 description: Cow::Borrowed(description),
393 inner: errors.into_iter().map(T::from).collect(),
394 })
395 }
396 }
397
398 pub(crate) fn short_message(&self) -> String {
400 let string = self.to_string();
401 match string.lines().next() {
402 Some(first_line) => first_line.trim_end_matches(':').to_string(),
404 None => String::new(),
405 }
406 }
407
408 pub fn description(&self) -> &str {
410 &self.description
411 }
412
413 pub fn iter(&self) -> impl Iterator<Item = &T> {
415 self.inner.iter()
416 }
417
418 pub fn map<U, F>(self, f: F) -> ErrorList<U>
420 where
421 U: std::error::Error,
422 F: FnMut(T) -> U,
423 {
424 ErrorList {
425 description: self.description,
426 inner: self.inner.into_iter().map(f).collect(),
427 }
428 }
429}
430
431impl<T: std::error::Error> IntoIterator for ErrorList<T> {
432 type Item = T;
433 type IntoIter = std::vec::IntoIter<T>;
434
435 fn into_iter(self) -> Self::IntoIter {
436 self.inner.into_iter()
437 }
438}
439
440impl<T: std::error::Error> fmt::Display for ErrorList<T> {
441 fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
442 if self.inner.len() == 1 {
444 return write!(f, "{}", self.inner[0]);
445 }
446
447 writeln!(
449 f,
450 "{} errors occurred {}:",
451 self.inner.len(),
452 self.description,
453 )?;
454 for error in &self.inner {
455 let mut indent = indented(f).with_str(" ").skip_initial();
456 writeln!(indent, "* {}", DisplayErrorChain::new(error))?;
457 f = indent.into_inner();
458 }
459 Ok(())
460 }
461}
462
463#[cfg(test)]
464impl<T: proptest::arbitrary::Arbitrary + std::fmt::Debug + 'static> proptest::arbitrary::Arbitrary
465 for ErrorList<T>
466{
467 type Parameters = ();
468 type Strategy = proptest::strategy::BoxedStrategy<Self>;
469
470 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
471 use proptest::prelude::*;
472
473 proptest::collection::vec(any::<T>(), 1..=5)
475 .prop_map(|inner| ErrorList {
476 description: Cow::Borrowed("test errors"),
477 inner,
478 })
479 .boxed()
480 }
481}
482
483impl<T: std::error::Error> std::error::Error for ErrorList<T> {
484 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
485 if self.inner.len() == 1 {
486 self.inner[0].source()
487 } else {
488 None
491 }
492 }
493}
494
495pub struct DisplayErrorChain<E> {
500 error: E,
501 initial_indent: &'static str,
502}
503
504impl<E: std::error::Error> DisplayErrorChain<E> {
505 pub fn new(error: E) -> Self {
507 Self {
508 error,
509 initial_indent: "",
510 }
511 }
512
513 pub fn new_with_initial_indent(initial_indent: &'static str, error: E) -> Self {
515 Self {
516 error,
517 initial_indent,
518 }
519 }
520}
521
522impl<E> fmt::Display for DisplayErrorChain<E>
523where
524 E: std::error::Error,
525{
526 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
527 let mut writer = indented(f).with_str(self.initial_indent);
528 write!(writer, "{}", self.error)?;
529
530 let Some(mut cause) = self.error.source() else {
531 return Ok(());
532 };
533
534 write!(writer, "\n caused by:")?;
535
536 loop {
537 writeln!(writer)?;
538 let mut indent = indented(&mut writer).with_str(" ").skip_initial();
540 write!(indent, " - {cause}")?;
541
542 let Some(next_cause) = cause.source() else {
543 break Ok(());
544 };
545
546 cause = next_cause;
547 }
548 }
549}
550
551#[derive(Clone, Debug, Error)]
553pub enum ChildError {
554 #[error(transparent)]
556 Fd(#[from] ChildFdError),
557
558 #[error(transparent)]
560 SetupScriptOutput(#[from] SetupScriptOutputError),
561}
562
563#[derive(Clone, Debug, Error)]
565pub enum ChildFdError {
566 #[error("error reading standard output")]
568 ReadStdout(#[source] Arc<std::io::Error>),
569
570 #[error("error reading standard error")]
572 ReadStderr(#[source] Arc<std::io::Error>),
573
574 #[error("error reading combined stream")]
576 ReadCombined(#[source] Arc<std::io::Error>),
577
578 #[error("error waiting for child process to exit")]
580 Wait(#[source] Arc<std::io::Error>),
581}
582
583#[derive(Clone, Debug, Eq, PartialEq)]
585#[non_exhaustive]
586pub struct UnknownTestGroupError {
587 pub profile_name: String,
589
590 pub name: TestGroup,
592}
593
594#[derive(Clone, Debug, Eq, PartialEq)]
597pub struct ProfileUnknownScriptError {
598 pub profile_name: String,
600
601 pub name: ScriptId,
603}
604
605#[derive(Clone, Debug, Eq, PartialEq)]
608pub struct ProfileWrongConfigScriptTypeError {
609 pub profile_name: String,
611
612 pub name: ScriptId,
614
615 pub attempted: ProfileScriptType,
617
618 pub actual: ScriptType,
620}
621
622#[derive(Clone, Debug, Eq, PartialEq)]
625pub struct ProfileListScriptUsesRunFiltersError {
626 pub profile_name: String,
628
629 pub name: ScriptId,
631
632 pub script_type: ProfileScriptType,
634
635 pub filters: BTreeSet<String>,
637}
638
639#[derive(Clone, Debug, Default)]
641pub struct ProfileScriptErrors {
642 pub unknown_scripts: Vec<ProfileUnknownScriptError>,
644
645 pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
647
648 pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
650}
651
652impl ProfileScriptErrors {
653 pub fn is_empty(&self) -> bool {
655 self.unknown_scripts.is_empty()
656 && self.wrong_script_types.is_empty()
657 && self.list_scripts_using_run_filters.is_empty()
658 }
659}
660
661#[derive(Clone, Debug, Error)]
663#[error("profile `{profile}` not found (known profiles: {})", .all_profiles.join(", "))]
664pub struct ProfileNotFound {
665 profile: String,
666 all_profiles: Vec<String>,
667}
668
669impl ProfileNotFound {
670 pub(crate) fn new(
671 profile: impl Into<String>,
672 all_profiles: impl IntoIterator<Item = impl Into<String>>,
673 ) -> Self {
674 let mut all_profiles: Vec<_> = all_profiles.into_iter().map(|s| s.into()).collect();
675 all_profiles.sort_unstable();
676 Self {
677 profile: profile.into(),
678 all_profiles,
679 }
680 }
681}
682
683#[derive(Clone, Debug, Error, Eq, PartialEq)]
685pub enum InvalidIdentifier {
686 #[error("identifier is empty")]
688 Empty,
689
690 #[error("invalid identifier `{0}`")]
692 InvalidXid(SmolStr),
693
694 #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
696 ToolIdentifierInvalidFormat(SmolStr),
697
698 #[error("tool identifier has empty component: `{0}`")]
700 ToolComponentEmpty(SmolStr),
701
702 #[error("invalid tool identifier `{0}`")]
704 ToolIdentifierInvalidXid(SmolStr),
705}
706
707#[derive(Clone, Debug, Error, Eq, PartialEq)]
709pub enum InvalidToolName {
710 #[error("tool name is empty")]
712 Empty,
713
714 #[error("invalid tool name `{0}`")]
716 InvalidXid(SmolStr),
717
718 #[error("tool name cannot start with \"@tool\": `{0}`")]
720 StartsWithToolPrefix(SmolStr),
721}
722
723#[derive(Clone, Debug, Error)]
725#[error("invalid custom test group name: {0}")]
726pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
727
728#[derive(Clone, Debug, Error)]
730#[error("invalid configuration script name: {0}")]
731pub struct InvalidConfigScriptName(pub InvalidIdentifier);
732
733#[derive(Clone, Debug, Error, PartialEq, Eq)]
735pub enum ToolConfigFileParseError {
736 #[error(
737 "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
738 )]
739 InvalidFormat {
741 input: String,
743 },
744
745 #[error("tool-config-file has invalid tool name: {input}")]
747 InvalidToolName {
748 input: String,
750
751 #[source]
753 error: InvalidToolName,
754 },
755
756 #[error("tool-config-file has empty config file path: {input}")]
758 EmptyConfigFile {
759 input: String,
761 },
762
763 #[error("tool-config-file is not an absolute path: {config_file}")]
765 ConfigFileNotAbsolute {
766 config_file: Utf8PathBuf,
768 },
769}
770
771#[derive(Debug, Error)]
773#[non_exhaustive]
774pub enum UserConfigError {
775 #[error("user config file not found at {path}")]
778 FileNotFound {
779 path: Utf8PathBuf,
781 },
782
783 #[error("failed to read user config at {path}")]
785 Read {
786 path: Utf8PathBuf,
788 #[source]
790 error: std::io::Error,
791 },
792
793 #[error("failed to parse user config at {path}")]
795 Parse {
796 path: Utf8PathBuf,
798 #[source]
800 error: toml::de::Error,
801 },
802
803 #[error("user config path contains non-UTF-8 characters")]
805 NonUtf8Path {
806 #[source]
808 error: FromPathBufError,
809 },
810
811 #[error(
813 "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
814 )]
815 OverridePlatformSpec {
816 path: Utf8PathBuf,
818 index: usize,
820 #[source]
822 error: target_spec::Error,
823 },
824}
825
826#[derive(Clone, Debug, Error)]
828#[error("unrecognized value for max-fail: {reason}")]
829pub struct MaxFailParseError {
830 pub reason: String,
832}
833
834impl MaxFailParseError {
835 pub(crate) fn new(reason: impl Into<String>) -> Self {
836 Self {
837 reason: reason.into(),
838 }
839 }
840}
841
842#[derive(Clone, Debug, Error)]
844#[error(
845 "unrecognized value for stress-count: {input}\n\
846 (hint: expected either a positive integer or \"infinite\")"
847)]
848pub struct StressCountParseError {
849 pub input: String,
851}
852
853impl StressCountParseError {
854 pub(crate) fn new(input: impl Into<String>) -> Self {
855 Self {
856 input: input.into(),
857 }
858 }
859}
860
861#[derive(Clone, Debug, Error)]
863#[non_exhaustive]
864pub enum DebuggerCommandParseError {
865 #[error(transparent)]
867 ShellWordsParse(shell_words::ParseError),
868
869 #[error("debugger command cannot be empty")]
871 EmptyCommand,
872}
873
874#[derive(Clone, Debug, Error)]
876#[non_exhaustive]
877pub enum TracerCommandParseError {
878 #[error(transparent)]
880 ShellWordsParse(shell_words::ParseError),
881
882 #[error("tracer command cannot be empty")]
884 EmptyCommand,
885}
886
887#[derive(Clone, Debug, Error)]
889#[error(
890 "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
891)]
892pub struct TestThreadsParseError {
893 pub input: String,
895}
896
897impl TestThreadsParseError {
898 pub(crate) fn new(input: impl Into<String>) -> Self {
899 Self {
900 input: input.into(),
901 }
902 }
903}
904
905#[derive(Clone, Debug, Error)]
908pub struct PartitionerBuilderParseError {
909 expected_format: Option<&'static str>,
910 message: Cow<'static, str>,
911}
912
913impl PartitionerBuilderParseError {
914 pub(crate) fn new(
915 expected_format: Option<&'static str>,
916 message: impl Into<Cow<'static, str>>,
917 ) -> Self {
918 Self {
919 expected_format,
920 message: message.into(),
921 }
922 }
923}
924
925impl fmt::Display for PartitionerBuilderParseError {
926 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
927 match self.expected_format {
928 Some(format) => {
929 write!(
930 f,
931 "partition must be in the format \"{}\":\n{}",
932 format, self.message
933 )
934 }
935 None => write!(f, "{}", self.message),
936 }
937 }
938}
939
940#[derive(Clone, Debug, Error)]
943pub enum TestFilterBuildError {
944 #[error("error constructing test filters")]
946 Construct {
947 #[from]
949 error: aho_corasick::BuildError,
950 },
951}
952
953#[derive(Debug, Error)]
955pub enum PathMapperConstructError {
956 #[error("{kind} `{input}` failed to canonicalize")]
958 Canonicalization {
959 kind: PathMapperConstructKind,
961
962 input: Utf8PathBuf,
964
965 #[source]
967 err: std::io::Error,
968 },
969 #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
971 NonUtf8Path {
972 kind: PathMapperConstructKind,
974
975 input: Utf8PathBuf,
977
978 #[source]
980 err: FromPathBufError,
981 },
982 #[error("{kind} `{canonicalized_path}` is not a directory")]
984 NotADirectory {
985 kind: PathMapperConstructKind,
987
988 input: Utf8PathBuf,
990
991 canonicalized_path: Utf8PathBuf,
993 },
994}
995
996impl PathMapperConstructError {
997 pub fn kind(&self) -> PathMapperConstructKind {
999 match self {
1000 Self::Canonicalization { kind, .. }
1001 | Self::NonUtf8Path { kind, .. }
1002 | Self::NotADirectory { kind, .. } => *kind,
1003 }
1004 }
1005
1006 pub fn input(&self) -> &Utf8Path {
1008 match self {
1009 Self::Canonicalization { input, .. }
1010 | Self::NonUtf8Path { input, .. }
1011 | Self::NotADirectory { input, .. } => input,
1012 }
1013 }
1014}
1015
1016#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1021pub enum PathMapperConstructKind {
1022 WorkspaceRoot,
1024
1025 TargetDir,
1027}
1028
1029impl fmt::Display for PathMapperConstructKind {
1030 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1031 match self {
1032 Self::WorkspaceRoot => write!(f, "remapped workspace root"),
1033 Self::TargetDir => write!(f, "remapped target directory"),
1034 }
1035 }
1036}
1037
1038#[derive(Debug, Error)]
1040pub enum RustBuildMetaParseError {
1041 #[error("error deserializing platform from build metadata")]
1043 PlatformDeserializeError(#[from] target_spec::Error),
1044
1045 #[error("the host platform could not be determined")]
1047 DetectBuildTargetError(#[source] target_spec::Error),
1048
1049 #[error("unsupported features in the build metadata: {message}")]
1051 Unsupported {
1052 message: String,
1054 },
1055}
1056
1057#[derive(Clone, Debug, thiserror::Error)]
1060#[error("invalid format version: {input}")]
1061pub struct FormatVersionError {
1062 pub input: String,
1064 #[source]
1066 pub error: FormatVersionErrorInner,
1067}
1068
1069#[derive(Clone, Debug, thiserror::Error)]
1071pub enum FormatVersionErrorInner {
1072 #[error("expected format version in form of `{expected}`")]
1074 InvalidFormat {
1075 expected: &'static str,
1077 },
1078 #[error("version component `{which}` could not be parsed as an integer")]
1080 InvalidInteger {
1081 which: &'static str,
1083 #[source]
1085 err: std::num::ParseIntError,
1086 },
1087 #[error("version component `{which}` value {value} is out of range {range:?}")]
1089 InvalidValue {
1090 which: &'static str,
1092 value: u8,
1094 range: std::ops::Range<u8>,
1096 },
1097}
1098
1099#[derive(Debug, Error)]
1102#[non_exhaustive]
1103pub enum FromMessagesError {
1104 #[error("error reading Cargo JSON messages")]
1106 ReadMessages(#[source] std::io::Error),
1107
1108 #[error("error querying package graph")]
1110 PackageGraph(#[source] guppy::Error),
1111
1112 #[error("missing kind for target {binary_name} in package {package_name}")]
1114 MissingTargetKind {
1115 package_name: String,
1117 binary_name: String,
1119 },
1120}
1121
1122#[derive(Debug, Error)]
1124#[non_exhaustive]
1125pub enum CreateTestListError {
1126 #[error(
1128 "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
1129 (hint: ensure project source is available at this location)"
1130 )]
1131 CwdIsNotDir {
1132 binary_id: RustBinaryId,
1134
1135 cwd: Utf8PathBuf,
1137 },
1138
1139 #[error(
1141 "for `{binary_id}`, running command `{}` failed to execute",
1142 shell_words::join(command)
1143 )]
1144 CommandExecFail {
1145 binary_id: RustBinaryId,
1147
1148 command: Vec<String>,
1150
1151 #[source]
1153 error: std::io::Error,
1154 },
1155
1156 #[error(
1158 "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1159 shell_words::join(command),
1160 display_exited_with(*exit_status),
1161 String::from_utf8_lossy(stdout),
1162 String::from_utf8_lossy(stderr),
1163 )]
1164 CommandFail {
1165 binary_id: RustBinaryId,
1167
1168 command: Vec<String>,
1170
1171 exit_status: ExitStatus,
1173
1174 stdout: Vec<u8>,
1176
1177 stderr: Vec<u8>,
1179 },
1180
1181 #[error(
1183 "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1184 shell_words::join(command),
1185 String::from_utf8_lossy(stdout),
1186 String::from_utf8_lossy(stderr)
1187 )]
1188 CommandNonUtf8 {
1189 binary_id: RustBinaryId,
1191
1192 command: Vec<String>,
1194
1195 stdout: Vec<u8>,
1197
1198 stderr: Vec<u8>,
1200 },
1201
1202 #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1204 ParseLine {
1205 binary_id: RustBinaryId,
1207
1208 message: Cow<'static, str>,
1210
1211 full_output: String,
1213 },
1214
1215 #[error(
1217 "error joining dynamic library paths for {}: [{}]",
1218 dylib_path_envvar(),
1219 itertools::join(.new_paths, ", ")
1220 )]
1221 DylibJoinPaths {
1222 new_paths: Vec<Utf8PathBuf>,
1224
1225 #[source]
1227 error: JoinPathsError,
1228 },
1229
1230 #[error("error creating Tokio runtime")]
1232 TokioRuntimeCreate(#[source] std::io::Error),
1233}
1234
1235impl CreateTestListError {
1236 pub(crate) fn parse_line(
1237 binary_id: RustBinaryId,
1238 message: impl Into<Cow<'static, str>>,
1239 full_output: impl Into<String>,
1240 ) -> Self {
1241 Self::ParseLine {
1242 binary_id,
1243 message: message.into(),
1244 full_output: full_output.into(),
1245 }
1246 }
1247
1248 pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1249 Self::DylibJoinPaths { new_paths, error }
1250 }
1251}
1252
1253#[derive(Debug, Error)]
1255#[non_exhaustive]
1256pub enum WriteTestListError {
1257 #[error("error writing to output")]
1259 Io(#[source] std::io::Error),
1260
1261 #[error("error serializing to JSON")]
1263 Json(#[source] serde_json::Error),
1264}
1265
1266#[derive(Debug, Error)]
1270pub enum ConfigureHandleInheritanceError {
1271 #[cfg(windows)]
1273 #[error("error configuring handle inheritance")]
1274 WindowsError(#[from] std::io::Error),
1275}
1276
1277#[derive(Debug, Error)]
1279#[non_exhaustive]
1280pub enum TestRunnerBuildError {
1281 #[error("error creating Tokio runtime")]
1283 TokioRuntimeCreate(#[source] std::io::Error),
1284
1285 #[error("error setting up signals")]
1287 SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1288}
1289
1290#[derive(Debug, Error)]
1292pub struct TestRunnerExecuteErrors<E> {
1293 pub report_error: Option<E>,
1295
1296 pub join_errors: Vec<tokio::task::JoinError>,
1299}
1300
1301impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1303 if let Some(report_error) = &self.report_error {
1304 write!(f, "error reporting results: {report_error}")?;
1305 }
1306
1307 if !self.join_errors.is_empty() {
1308 if self.report_error.is_some() {
1309 write!(f, "; ")?;
1310 }
1311
1312 write!(f, "errors joining tasks: ")?;
1313
1314 for (i, join_error) in self.join_errors.iter().enumerate() {
1315 if i > 0 {
1316 write!(f, ", ")?;
1317 }
1318
1319 write!(f, "{join_error}")?;
1320 }
1321 }
1322
1323 Ok(())
1324 }
1325}
1326
1327#[derive(Debug, Error)]
1331#[error(
1332 "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1333 supported_extensions()
1334)]
1335pub struct UnknownArchiveFormat {
1336 pub file_name: String,
1338}
1339
1340fn supported_extensions() -> String {
1341 ArchiveFormat::SUPPORTED_FORMATS
1342 .iter()
1343 .map(|(extension, _)| *extension)
1344 .join(", ")
1345}
1346
1347#[derive(Debug, Error)]
1349#[non_exhaustive]
1350pub enum ArchiveCreateError {
1351 #[error("error creating binary list")]
1353 CreateBinaryList(#[source] WriteTestListError),
1354
1355 #[error("extra path `{}` not found", .redactor.redact_path(path))]
1357 MissingExtraPath {
1358 path: Utf8PathBuf,
1360
1361 redactor: Redactor,
1366 },
1367
1368 #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1370 InputFileRead {
1371 step: ArchiveStep,
1373
1374 path: Utf8PathBuf,
1376
1377 is_dir: Option<bool>,
1379
1380 #[source]
1382 error: std::io::Error,
1383 },
1384
1385 #[error("error reading directory entry from `{path}")]
1387 DirEntryRead {
1388 path: Utf8PathBuf,
1390
1391 #[source]
1393 error: std::io::Error,
1394 },
1395
1396 #[error("error writing to archive")]
1398 OutputArchiveIo(#[source] std::io::Error),
1399
1400 #[error("error reporting archive status")]
1402 ReporterIo(#[source] std::io::Error),
1403}
1404
1405fn kind_str(is_dir: Option<bool>) -> &'static str {
1406 match is_dir {
1407 Some(true) => "directory",
1408 Some(false) => "file",
1409 None => "path",
1410 }
1411}
1412
1413#[derive(Debug, Error)]
1415pub enum MetadataMaterializeError {
1416 #[error("I/O error reading metadata file `{path}`")]
1418 Read {
1419 path: Utf8PathBuf,
1421
1422 #[source]
1424 error: std::io::Error,
1425 },
1426
1427 #[error("error deserializing metadata file `{path}`")]
1429 Deserialize {
1430 path: Utf8PathBuf,
1432
1433 #[source]
1435 error: serde_json::Error,
1436 },
1437
1438 #[error("error parsing Rust build metadata from `{path}`")]
1440 RustBuildMeta {
1441 path: Utf8PathBuf,
1443
1444 #[source]
1446 error: RustBuildMetaParseError,
1447 },
1448
1449 #[error("error building package graph from `{path}`")]
1451 PackageGraphConstruct {
1452 path: Utf8PathBuf,
1454
1455 #[source]
1457 error: guppy::Error,
1458 },
1459}
1460
1461#[derive(Debug, Error)]
1465#[non_exhaustive]
1466pub enum ArchiveReadError {
1467 #[error("I/O error reading archive")]
1469 Io(#[source] std::io::Error),
1470
1471 #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1473 NonUtf8Path(Vec<u8>),
1474
1475 #[error("path in archive `{0}` doesn't start with `target/`")]
1477 NoTargetPrefix(Utf8PathBuf),
1478
1479 #[error("path in archive `{path}` contains an invalid component `{component}`")]
1481 InvalidComponent {
1482 path: Utf8PathBuf,
1484
1485 component: String,
1487 },
1488
1489 #[error("corrupted archive: checksum read error for path `{path}`")]
1491 ChecksumRead {
1492 path: Utf8PathBuf,
1494
1495 #[source]
1497 error: std::io::Error,
1498 },
1499
1500 #[error("corrupted archive: invalid checksum for path `{path}`")]
1502 InvalidChecksum {
1503 path: Utf8PathBuf,
1505
1506 expected: u32,
1508
1509 actual: u32,
1511 },
1512
1513 #[error("metadata file `{0}` not found in archive")]
1515 MetadataFileNotFound(&'static Utf8Path),
1516
1517 #[error("error deserializing metadata file `{path}` in archive")]
1519 MetadataDeserializeError {
1520 path: &'static Utf8Path,
1522
1523 #[source]
1525 error: serde_json::Error,
1526 },
1527
1528 #[error("error building package graph from `{path}` in archive")]
1530 PackageGraphConstructError {
1531 path: &'static Utf8Path,
1533
1534 #[source]
1536 error: guppy::Error,
1537 },
1538}
1539
1540#[derive(Debug, Error)]
1544#[non_exhaustive]
1545pub enum ArchiveExtractError {
1546 #[error("error creating temporary directory")]
1548 TempDirCreate(#[source] std::io::Error),
1549
1550 #[error("error canonicalizing destination directory `{dir}`")]
1552 DestDirCanonicalization {
1553 dir: Utf8PathBuf,
1555
1556 #[source]
1558 error: std::io::Error,
1559 },
1560
1561 #[error("destination `{0}` already exists")]
1563 DestinationExists(Utf8PathBuf),
1564
1565 #[error("error reading archive")]
1567 Read(#[source] ArchiveReadError),
1568
1569 #[error("error deserializing Rust build metadata")]
1571 RustBuildMeta(#[from] RustBuildMetaParseError),
1572
1573 #[error("error writing file `{path}` to disk")]
1575 WriteFile {
1576 path: Utf8PathBuf,
1578
1579 #[source]
1581 error: std::io::Error,
1582 },
1583
1584 #[error("error reporting extract status")]
1586 ReporterIo(std::io::Error),
1587}
1588
1589#[derive(Debug, Error)]
1591#[non_exhaustive]
1592pub enum WriteEventError {
1593 #[error("error writing to output")]
1595 Io(#[source] std::io::Error),
1596
1597 #[error("error operating on path {file}")]
1599 Fs {
1600 file: Utf8PathBuf,
1602
1603 #[source]
1605 error: std::io::Error,
1606 },
1607
1608 #[error("error writing JUnit output to {file}")]
1610 Junit {
1611 file: Utf8PathBuf,
1613
1614 #[source]
1616 error: quick_junit::SerializeError,
1617 },
1618}
1619
1620#[derive(Debug, Error)]
1623#[non_exhaustive]
1624pub enum CargoConfigError {
1625 #[error("failed to retrieve current directory")]
1627 GetCurrentDir(#[source] std::io::Error),
1628
1629 #[error("current directory is invalid UTF-8")]
1631 CurrentDirInvalidUtf8(#[source] FromPathBufError),
1632
1633 #[error("failed to parse --config argument `{config_str}` as TOML")]
1635 CliConfigParseError {
1636 config_str: String,
1638
1639 #[source]
1641 error: toml_edit::TomlError,
1642 },
1643
1644 #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1646 CliConfigDeError {
1647 config_str: String,
1649
1650 #[source]
1652 error: toml_edit::de::Error,
1653 },
1654
1655 #[error(
1657 "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1658 )]
1659 InvalidCliConfig {
1660 config_str: String,
1662
1663 #[source]
1665 reason: InvalidCargoCliConfigReason,
1666 },
1667
1668 #[error("non-UTF-8 path encountered")]
1670 NonUtf8Path(#[source] FromPathBufError),
1671
1672 #[error("failed to retrieve the Cargo home directory")]
1674 GetCargoHome(#[source] std::io::Error),
1675
1676 #[error("failed to canonicalize path `{path}")]
1678 FailedPathCanonicalization {
1679 path: Utf8PathBuf,
1681
1682 #[source]
1684 error: std::io::Error,
1685 },
1686
1687 #[error("failed to read config at `{path}`")]
1689 ConfigReadError {
1690 path: Utf8PathBuf,
1692
1693 #[source]
1695 error: std::io::Error,
1696 },
1697
1698 #[error(transparent)]
1700 ConfigParseError(#[from] Box<CargoConfigParseError>),
1701}
1702
1703#[derive(Debug, Error)]
1707#[error("failed to parse config at `{path}`")]
1708pub struct CargoConfigParseError {
1709 pub path: Utf8PathBuf,
1711
1712 #[source]
1714 pub error: toml::de::Error,
1715}
1716
1717#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1721#[non_exhaustive]
1722pub enum InvalidCargoCliConfigReason {
1723 #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1725 NotDottedKv,
1726
1727 #[error("includes non-whitespace decoration")]
1729 IncludesNonWhitespaceDecoration,
1730
1731 #[error("sets a value to an inline table, which is not accepted")]
1733 SetsValueToInlineTable,
1734
1735 #[error("sets a value to an array of tables, which is not accepted")]
1737 SetsValueToArrayOfTables,
1738
1739 #[error("doesn't provide a value")]
1741 DoesntProvideValue,
1742}
1743
1744#[derive(Debug, Error)]
1746pub enum HostPlatformDetectError {
1747 #[error(
1750 "error spawning `rustc -vV`, and detecting the build \
1751 target failed as well\n\
1752 - rustc spawn error: {}\n\
1753 - build target error: {}\n",
1754 DisplayErrorChain::new_with_initial_indent(" ", error),
1755 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1756 )]
1757 RustcVvSpawnError {
1758 error: std::io::Error,
1760
1761 build_target_error: Box<target_spec::Error>,
1763 },
1764
1765 #[error(
1768 "`rustc -vV` failed with {}, and detecting the \
1769 build target failed as well\n\
1770 - `rustc -vV` stdout:\n{}\n\
1771 - `rustc -vV` stderr:\n{}\n\
1772 - build target error:\n{}\n",
1773 status,
1774 DisplayIndented { item: String::from_utf8_lossy(stdout), indent: " " },
1775 DisplayIndented { item: String::from_utf8_lossy(stderr), indent: " " },
1776 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1777 )]
1778 RustcVvFailed {
1779 status: ExitStatus,
1781
1782 stdout: Vec<u8>,
1784
1785 stderr: Vec<u8>,
1787
1788 build_target_error: Box<target_spec::Error>,
1790 },
1791
1792 #[error(
1795 "parsing `rustc -vV` output failed, and detecting the build target \
1796 failed as well\n\
1797 - host platform error:\n{}\n\
1798 - build target error:\n{}\n",
1799 DisplayErrorChain::new_with_initial_indent(" ", host_platform_error),
1800 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1801 )]
1802 HostPlatformParseError {
1803 host_platform_error: Box<target_spec::Error>,
1805
1806 build_target_error: Box<target_spec::Error>,
1808 },
1809
1810 #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
1813 BuildTargetError {
1814 #[source]
1816 build_target_error: Box<target_spec::Error>,
1817 },
1818}
1819
1820#[derive(Debug, Error)]
1822pub enum TargetTripleError {
1823 #[error(
1825 "environment variable '{}' contained non-UTF-8 data",
1826 TargetTriple::CARGO_BUILD_TARGET_ENV
1827 )]
1828 InvalidEnvironmentVar,
1829
1830 #[error("error deserializing target triple from {source}")]
1832 TargetSpecError {
1833 source: TargetTripleSource,
1835
1836 #[source]
1838 error: target_spec::Error,
1839 },
1840
1841 #[error("target path `{path}` is not a valid file")]
1843 TargetPathReadError {
1844 source: TargetTripleSource,
1846
1847 path: Utf8PathBuf,
1849
1850 #[source]
1852 error: std::io::Error,
1853 },
1854
1855 #[error(
1857 "for custom platform obtained from {source}, \
1858 failed to create temporary directory for custom platform"
1859 )]
1860 CustomPlatformTempDirError {
1861 source: TargetTripleSource,
1863
1864 #[source]
1866 error: std::io::Error,
1867 },
1868
1869 #[error(
1871 "for custom platform obtained from {source}, \
1872 failed to write JSON to temporary path `{path}`"
1873 )]
1874 CustomPlatformWriteError {
1875 source: TargetTripleSource,
1877
1878 path: Utf8PathBuf,
1880
1881 #[source]
1883 error: std::io::Error,
1884 },
1885
1886 #[error(
1888 "for custom platform obtained from {source}, \
1889 failed to close temporary directory `{dir_path}`"
1890 )]
1891 CustomPlatformCloseError {
1892 source: TargetTripleSource,
1894
1895 dir_path: Utf8PathBuf,
1897
1898 #[source]
1900 error: std::io::Error,
1901 },
1902}
1903
1904impl TargetTripleError {
1905 pub fn source_report(&self) -> Option<miette::Report> {
1910 match self {
1911 Self::TargetSpecError { error, .. } => {
1912 Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
1913 }
1914 TargetTripleError::InvalidEnvironmentVar
1916 | TargetTripleError::TargetPathReadError { .. }
1917 | TargetTripleError::CustomPlatformTempDirError { .. }
1918 | TargetTripleError::CustomPlatformWriteError { .. }
1919 | TargetTripleError::CustomPlatformCloseError { .. } => None,
1920 }
1921 }
1922}
1923
1924#[derive(Debug, Error)]
1926pub enum TargetRunnerError {
1927 #[error("environment variable '{0}' contained non-UTF-8 data")]
1929 InvalidEnvironmentVar(String),
1930
1931 #[error("runner '{key}' = '{value}' did not contain a runner binary")]
1934 BinaryNotSpecified {
1935 key: PlatformRunnerSource,
1937
1938 value: String,
1940 },
1941}
1942
1943#[derive(Debug, Error)]
1945#[error("error setting up signal handler")]
1946pub struct SignalHandlerSetupError(#[from] std::io::Error);
1947
1948#[derive(Debug, Error)]
1950pub enum ShowTestGroupsError {
1951 #[error(
1953 "unknown test groups specified: {}\n(known groups: {})",
1954 unknown_groups.iter().join(", "),
1955 known_groups.iter().join(", "),
1956 )]
1957 UnknownGroups {
1958 unknown_groups: BTreeSet<TestGroup>,
1960
1961 known_groups: BTreeSet<TestGroup>,
1963 },
1964}
1965
1966#[derive(Debug, Error, PartialEq, Eq, Hash)]
1968pub enum InheritsError {
1969 #[error("the {} profile should not inherit from other profiles", .0)]
1971 DefaultProfileInheritance(String),
1972 #[error("profile {} inherits from an unknown profile {}", .0, .1)]
1974 UnknownInheritance(String, String),
1975 #[error("a self referential inheritance is detected from profile: {}", .0)]
1977 SelfReferentialInheritance(String),
1978 #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
1980 format!("[{}]", scc.iter().join(", "))
1981 }).join(", "))]
1982 InheritanceCycle(Vec<Vec<String>>),
1983}
1984
1985#[derive(Debug, Error)]
1991pub enum RunStoreError {
1992 #[error("error creating run directory `{run_dir}`")]
1994 RunDirCreate {
1995 run_dir: Utf8PathBuf,
1997
1998 #[source]
2000 error: std::io::Error,
2001 },
2002
2003 #[error("error acquiring lock on `{path}`")]
2005 FileLock {
2006 path: Utf8PathBuf,
2008
2009 #[source]
2011 error: std::io::Error,
2012 },
2013
2014 #[error(
2016 "timed out acquiring lock on `{path}` after {timeout_secs}s (is the state directory \
2017 on a networked filesystem?)"
2018 )]
2019 FileLockTimeout {
2020 path: Utf8PathBuf,
2022
2023 timeout_secs: u64,
2025 },
2026
2027 #[error("error reading run list from `{path}`")]
2029 RunListRead {
2030 path: Utf8PathBuf,
2032
2033 #[source]
2035 error: std::io::Error,
2036 },
2037
2038 #[error("error deserializing run list from `{path}`")]
2040 RunListDeserialize {
2041 path: Utf8PathBuf,
2043
2044 #[source]
2046 error: serde_json::Error,
2047 },
2048
2049 #[error("error serializing run list to `{path}`")]
2051 RunListSerialize {
2052 path: Utf8PathBuf,
2054
2055 #[source]
2057 error: serde_json::Error,
2058 },
2059
2060 #[error("error serializing rerun info")]
2062 RerunInfoSerialize {
2063 #[source]
2065 error: serde_json::Error,
2066 },
2067
2068 #[error("error serializing test list")]
2070 TestListSerialize {
2071 #[source]
2073 error: serde_json::Error,
2074 },
2075
2076 #[error("error serializing record options")]
2078 RecordOptionsSerialize {
2079 #[source]
2081 error: serde_json::Error,
2082 },
2083
2084 #[error("error serializing test event")]
2086 TestEventSerialize {
2087 #[source]
2089 error: serde_json::Error,
2090 },
2091
2092 #[error("error writing run list to `{path}`")]
2094 RunListWrite {
2095 path: Utf8PathBuf,
2097
2098 #[source]
2100 error: atomicwrites::Error<std::io::Error>,
2101 },
2102
2103 #[error("error writing to store at `{store_path}`")]
2105 StoreWrite {
2106 store_path: Utf8PathBuf,
2108
2109 #[source]
2111 error: StoreWriterError,
2112 },
2113
2114 #[error("error creating run log at `{path}`")]
2116 RunLogCreate {
2117 path: Utf8PathBuf,
2119
2120 #[source]
2122 error: std::io::Error,
2123 },
2124
2125 #[error("error writing to run log at `{path}`")]
2127 RunLogWrite {
2128 path: Utf8PathBuf,
2130
2131 #[source]
2133 error: std::io::Error,
2134 },
2135
2136 #[error("error flushing run log at `{path}`")]
2138 RunLogFlush {
2139 path: Utf8PathBuf,
2141
2142 #[source]
2144 error: std::io::Error,
2145 },
2146
2147 #[error(
2149 "cannot write to record store: runs.json.zst format version {file_version} is newer than \
2150 supported version {max_supported_version}"
2151 )]
2152 FormatVersionTooNew {
2153 file_version: RunsJsonFormatVersion,
2155 max_supported_version: RunsJsonFormatVersion,
2157 },
2158}
2159
2160#[derive(Debug, Error)]
2162#[non_exhaustive]
2163pub enum StoreWriterError {
2164 #[error("error creating store")]
2166 Create {
2167 #[source]
2169 error: std::io::Error,
2170 },
2171
2172 #[error("error creating path `{path}` in store")]
2174 StartFile {
2175 path: Utf8PathBuf,
2177
2178 #[source]
2180 error: zip::result::ZipError,
2181 },
2182
2183 #[error("error writing to path `{path}` in store")]
2185 Write {
2186 path: Utf8PathBuf,
2188
2189 #[source]
2191 error: std::io::Error,
2192 },
2193
2194 #[error("error compressing data")]
2196 Compress {
2197 #[source]
2199 error: std::io::Error,
2200 },
2201
2202 #[error("error finalizing store")]
2204 Finish {
2205 #[source]
2207 error: zip::result::ZipError,
2208 },
2209
2210 #[error("error flushing store")]
2212 Flush {
2213 #[source]
2215 error: std::io::Error,
2216 },
2217}
2218
2219#[derive(Debug, Error)]
2221pub enum RecordReporterError {
2222 #[error(transparent)]
2224 RunStore(RunStoreError),
2225
2226 #[error("record writer thread panicked: {message}")]
2228 WriterPanic {
2229 message: String,
2231 },
2232}
2233
2234#[derive(Debug, Error)]
2236pub enum StateDirError {
2237 #[error("could not determine platform base directory strategy")]
2241 BaseDirStrategy(#[source] HomeDirError),
2242
2243 #[error("platform state directory is not valid UTF-8: {path:?}")]
2245 StateDirNotUtf8 {
2246 path: PathBuf,
2248 },
2249
2250 #[error("could not canonicalize workspace path `{workspace_root}`")]
2252 Canonicalize {
2253 workspace_root: Utf8PathBuf,
2255 #[source]
2257 error: std::io::Error,
2258 },
2259}
2260
2261#[derive(Debug, Error)]
2263pub enum RecordSetupError {
2264 #[error("could not determine platform state directory for recording")]
2266 StateDirNotFound(#[source] StateDirError),
2267
2268 #[error("failed to create run store")]
2270 StoreCreate(#[source] RunStoreError),
2271
2272 #[error("failed to lock run store")]
2274 StoreLock(#[source] RunStoreError),
2275
2276 #[error("failed to create run recorder")]
2278 RecorderCreate(#[source] RunStoreError),
2279}
2280
2281impl RecordSetupError {
2282 pub fn disabled_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
2289 match self {
2290 RecordSetupError::RecorderCreate(err @ RunStoreError::FormatVersionTooNew { .. }) => {
2291 Some(err)
2292 }
2293 _ => None,
2294 }
2295 }
2296}
2297
2298#[derive(Debug, Error)]
2300pub enum RecordPruneError {
2301 #[error("error deleting run `{run_id}` at `{path}`")]
2303 DeleteRun {
2304 run_id: ReportUuid,
2306
2307 path: Utf8PathBuf,
2309
2310 #[source]
2312 error: std::io::Error,
2313 },
2314
2315 #[error("error calculating size of `{path}`")]
2317 CalculateSize {
2318 path: Utf8PathBuf,
2320
2321 #[source]
2323 error: std::io::Error,
2324 },
2325
2326 #[error("error deleting orphaned directory `{path}`")]
2328 DeleteOrphan {
2329 path: Utf8PathBuf,
2331
2332 #[source]
2334 error: std::io::Error,
2335 },
2336
2337 #[error("error reading runs directory `{path}`")]
2339 ReadRunsDir {
2340 path: Utf8PathBuf,
2342
2343 #[source]
2345 error: std::io::Error,
2346 },
2347
2348 #[error("error reading directory entry in `{dir}`")]
2350 ReadDirEntry {
2351 dir: Utf8PathBuf,
2353
2354 #[source]
2356 error: std::io::Error,
2357 },
2358
2359 #[error("error reading file type for `{path}`")]
2361 ReadFileType {
2362 path: Utf8PathBuf,
2364
2365 #[source]
2367 error: std::io::Error,
2368 },
2369}
2370
2371#[derive(Clone, Debug, PartialEq, Eq, Error)]
2376#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2377pub struct InvalidRunIdSelector {
2378 pub input: String,
2380}
2381
2382#[derive(Clone, Debug, PartialEq, Eq, Error)]
2388#[error(
2389 "invalid run ID selector `{input}`: expected `latest`, hex digits, \
2390 or a file path (ending in `.zip` or containing path separators)"
2391)]
2392pub struct InvalidRunIdOrRecordingSelector {
2393 pub input: String,
2395}
2396
2397#[derive(Debug, Error)]
2399pub enum RunIdResolutionError {
2400 #[error("no recorded run found matching `{prefix}`")]
2402 NotFound {
2403 prefix: String,
2405 },
2406
2407 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2409 Ambiguous {
2410 prefix: String,
2412
2413 count: usize,
2415
2416 candidates: Vec<RecordedRunInfo>,
2418
2419 run_id_index: RunIdIndex,
2421 },
2422
2423 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2425 InvalidPrefix {
2426 prefix: String,
2428 },
2429
2430 #[error("no recorded runs exist")]
2432 NoRuns,
2433}
2434
2435#[derive(Debug, Error)]
2437pub enum RecordReadError {
2438 #[error("run not found at `{path}`")]
2440 RunNotFound {
2441 path: Utf8PathBuf,
2443 },
2444
2445 #[error("error opening archive at `{path}`")]
2447 OpenArchive {
2448 path: Utf8PathBuf,
2450
2451 #[source]
2453 error: std::io::Error,
2454 },
2455
2456 #[error("error reading `{file_name}` from archive")]
2458 ReadArchiveFile {
2459 file_name: String,
2461
2462 #[source]
2464 error: zip::result::ZipError,
2465 },
2466
2467 #[error("error opening run log at `{path}`")]
2469 OpenRunLog {
2470 path: Utf8PathBuf,
2472
2473 #[source]
2475 error: std::io::Error,
2476 },
2477
2478 #[error("error reading line {line_number} from run log")]
2480 ReadRunLog {
2481 line_number: usize,
2483
2484 #[source]
2486 error: std::io::Error,
2487 },
2488
2489 #[error("error parsing event at line {line_number}")]
2491 ParseEvent {
2492 line_number: usize,
2494
2495 #[source]
2497 error: serde_json::Error,
2498 },
2499
2500 #[error("required file `{file_name}` not found in archive")]
2502 FileNotFound {
2503 file_name: String,
2505 },
2506
2507 #[error("error decompressing data from `{file_name}`")]
2509 Decompress {
2510 file_name: String,
2512
2513 #[source]
2515 error: std::io::Error,
2516 },
2517
2518 #[error(
2523 "unknown output file type `{file_name}` in archive \
2524 (archive may have been created by a newer version of nextest)"
2525 )]
2526 UnknownOutputType {
2527 file_name: String,
2529 },
2530
2531 #[error(
2533 "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2534 )]
2535 FileTooLarge {
2536 file_name: String,
2538
2539 size: u64,
2541
2542 limit: u64,
2544 },
2545
2546 #[error(
2551 "file `{file_name}` size mismatch: header claims {claimed_size} bytes, \
2552 but read {actual_size} bytes (archive may be corrupt or tampered)"
2553 )]
2554 SizeMismatch {
2555 file_name: String,
2557
2558 claimed_size: u64,
2560
2561 actual_size: u64,
2563 },
2564
2565 #[error("error deserializing `{file_name}`")]
2567 DeserializeMetadata {
2568 file_name: String,
2570
2571 #[source]
2573 error: serde_json::Error,
2574 },
2575
2576 #[error("failed to extract `{store_path}` to `{output_path}`")]
2578 ExtractFile {
2579 store_path: String,
2581
2582 output_path: Utf8PathBuf,
2584
2585 #[source]
2587 error: std::io::Error,
2588 },
2589
2590 #[error("error reading portable recording")]
2592 PortableRecording(#[source] PortableRecordingReadError),
2593}
2594
2595#[derive(Debug, Error)]
2597#[non_exhaustive]
2598pub enum PortableRecordingError {
2599 #[error("run directory does not exist: {path}")]
2601 RunDirNotFound {
2602 path: Utf8PathBuf,
2604 },
2605
2606 #[error("required file missing from run directory `{run_dir}`: `{file_name}`")]
2608 RequiredFileMissing {
2609 run_dir: Utf8PathBuf,
2611 file_name: &'static str,
2613 },
2614
2615 #[error("failed to serialize manifest")]
2617 SerializeManifest(#[source] serde_json::Error),
2618
2619 #[error("failed to start file {file_name} in archive")]
2621 ZipStartFile {
2622 file_name: &'static str,
2624 #[source]
2626 source: zip::result::ZipError,
2627 },
2628
2629 #[error("failed to write {file_name} to archive")]
2631 ZipWrite {
2632 file_name: &'static str,
2634 #[source]
2636 source: std::io::Error,
2637 },
2638
2639 #[error("failed to read {file_name}")]
2641 ReadFile {
2642 file_name: &'static str,
2644 #[source]
2646 source: std::io::Error,
2647 },
2648
2649 #[error("failed to finalize archive")]
2651 ZipFinalize(#[source] zip::result::ZipError),
2652
2653 #[error("failed to write archive atomically to {path}")]
2655 AtomicWrite {
2656 path: Utf8PathBuf,
2658 #[source]
2660 source: std::io::Error,
2661 },
2662}
2663
2664#[derive(Debug, Error)]
2666#[non_exhaustive]
2667pub enum PortableRecordingReadError {
2668 #[error("failed to open archive at `{path}`")]
2670 OpenArchive {
2671 path: Utf8PathBuf,
2673 #[source]
2675 error: std::io::Error,
2676 },
2677
2678 #[error("failed to read archive at `{path}`")]
2680 ReadArchive {
2681 path: Utf8PathBuf,
2683 #[source]
2685 error: zip::result::ZipError,
2686 },
2687
2688 #[error("required file `{file_name}` missing from archive at `{path}`")]
2690 MissingFile {
2691 path: Utf8PathBuf,
2693 file_name: Cow<'static, str>,
2695 },
2696
2697 #[error("failed to parse manifest from archive at `{path}`")]
2699 ParseManifest {
2700 path: Utf8PathBuf,
2702 #[source]
2704 error: serde_json::Error,
2705 },
2706
2707 #[error(
2709 "portable recording format version {found} in `{path}` is incompatible: {incompatibility} \
2710 (this nextest supports version {supported})"
2711 )]
2712 UnsupportedFormatVersion {
2713 path: Utf8PathBuf,
2715 found: PortableRecordingFormatVersion,
2717 supported: PortableRecordingFormatVersion,
2719 incompatibility: PortableRecordingVersionIncompatibility,
2721 },
2722
2723 #[error(
2725 "store format version {found} in `{path}` is incompatible: {incompatibility} \
2726 (this nextest supports version {supported})"
2727 )]
2728 UnsupportedStoreFormatVersion {
2729 path: Utf8PathBuf,
2731 found: StoreFormatVersion,
2733 supported: StoreFormatVersion,
2735 incompatibility: StoreVersionIncompatibility,
2737 },
2738
2739 #[error(
2741 "file `{file_name}` in archive `{path}` is too large \
2742 ({size} bytes, limit is {limit} bytes)"
2743 )]
2744 FileTooLarge {
2745 path: Utf8PathBuf,
2747 file_name: Cow<'static, str>,
2749 size: u64,
2751 limit: u64,
2753 },
2754
2755 #[error("failed to extract `{file_name}` from archive `{archive_path}` to `{output_path}`")]
2757 ExtractFile {
2758 archive_path: Utf8PathBuf,
2760 file_name: &'static str,
2762 output_path: Utf8PathBuf,
2764 #[source]
2766 error: std::io::Error,
2767 },
2768
2769 #[error(
2773 "archive at `{path}` has no manifest and is not a wrapper archive \
2774 (contains {file_count} {}, {zip_count} of which {} in .zip)",
2775 plural::files_str(*file_count),
2776 plural::end_str(*zip_count)
2777 )]
2778 NotAWrapperArchive {
2779 path: Utf8PathBuf,
2781 file_count: usize,
2783 zip_count: usize,
2785 },
2786
2787 #[error("unexpected I/O error while probing seekability of `{path}`")]
2794 SeekProbe {
2795 path: Utf8PathBuf,
2797 #[source]
2799 error: std::io::Error,
2800 },
2801
2802 #[error("failed to spool non-seekable input `{path}` to a temporary file")]
2808 SpoolTempFile {
2809 path: Utf8PathBuf,
2811 #[source]
2813 error: std::io::Error,
2814 },
2815
2816 #[error(
2818 "recording at `{path}` exceeds the spool size limit \
2819 ({}); use a file path instead of process substitution",
2820 SizeDisplay(.limit.0)
2821 )]
2822 SpoolTooLarge {
2823 path: Utf8PathBuf,
2825 limit: ByteSize,
2827 },
2828}
2829
2830#[derive(Debug, Error)]
2834pub enum TestListFromSummaryError {
2835 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
2837 PackageNotFound {
2838 name: String,
2840
2841 package_id: String,
2843 },
2844
2845 #[error("error parsing rust build metadata")]
2847 RustBuildMeta(#[source] RustBuildMetaParseError),
2848}
2849
2850#[cfg(feature = "self-update")]
2851mod self_update_errors {
2852 use super::*;
2853 use crate::update::PrereleaseKind;
2854 use mukti_metadata::ReleaseStatus;
2855 use semver::{Version, VersionReq};
2856
2857 #[derive(Debug, Error)]
2861 #[non_exhaustive]
2862 pub enum UpdateError {
2863 #[error("failed to read release metadata from `{path}`")]
2865 ReadLocalMetadata {
2866 path: Utf8PathBuf,
2868
2869 #[source]
2871 error: std::io::Error,
2872 },
2873
2874 #[error("self-update failed")]
2876 SelfUpdate(#[source] self_update::errors::Error),
2877
2878 #[error("deserializing release metadata failed")]
2880 ReleaseMetadataDe(#[source] serde_json::Error),
2881
2882 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
2884 VersionNotFound {
2885 version: Version,
2887
2888 known: Vec<(Version, ReleaseStatus)>,
2890 },
2891
2892 #[error("no version found matching requirement `{req}`")]
2894 NoMatchForVersionReq {
2895 req: VersionReq,
2897 },
2898
2899 #[error("no stable version found")]
2901 NoStableVersion,
2902
2903 #[error("no version found matching {} channel", kind.description())]
2905 NoVersionForPrereleaseKind {
2906 kind: PrereleaseKind,
2908 },
2909
2910 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
2912 MuktiProjectNotFound {
2913 not_found: String,
2915
2916 known: Vec<String>,
2918 },
2919
2920 #[error(
2922 "for version {version}, no release information found for target `{triple}` \
2923 (known targets: {})",
2924 known_triples.iter().join(", ")
2925 )]
2926 NoTargetData {
2927 version: Version,
2929
2930 triple: String,
2932
2933 known_triples: BTreeSet<String>,
2935 },
2936
2937 #[error("the current executable's path could not be determined")]
2939 CurrentExe(#[source] std::io::Error),
2940
2941 #[error("temporary directory could not be created at `{location}`")]
2943 TempDirCreate {
2944 location: Utf8PathBuf,
2946
2947 #[source]
2949 error: std::io::Error,
2950 },
2951
2952 #[error("temporary archive could not be created at `{archive_path}`")]
2954 TempArchiveCreate {
2955 archive_path: Utf8PathBuf,
2957
2958 #[source]
2960 error: std::io::Error,
2961 },
2962
2963 #[error("error writing to temporary archive at `{archive_path}`")]
2965 TempArchiveWrite {
2966 archive_path: Utf8PathBuf,
2968
2969 #[source]
2971 error: std::io::Error,
2972 },
2973
2974 #[error("error reading from temporary archive at `{archive_path}`")]
2976 TempArchiveRead {
2977 archive_path: Utf8PathBuf,
2979
2980 #[source]
2982 error: std::io::Error,
2983 },
2984
2985 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
2987 ChecksumMismatch {
2988 expected: String,
2990
2991 actual: String,
2993 },
2994
2995 #[error("error renaming `{source}` to `{dest}`")]
2997 FsRename {
2998 source: Utf8PathBuf,
3000
3001 dest: Utf8PathBuf,
3003
3004 #[source]
3006 error: std::io::Error,
3007 },
3008
3009 #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
3011 SelfSetup(#[source] std::io::Error),
3012 }
3013
3014 fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
3015 use std::fmt::Write;
3016
3017 const DISPLAY_COUNT: usize = 4;
3019
3020 let display_versions: Vec<_> = versions
3021 .iter()
3022 .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
3023 .map(|(v, _)| v.to_string())
3024 .take(DISPLAY_COUNT)
3025 .collect();
3026 let mut display_str = display_versions.join(", ");
3027 if versions.len() > display_versions.len() {
3028 write!(
3029 display_str,
3030 " and {} others",
3031 versions.len() - display_versions.len()
3032 )
3033 .unwrap();
3034 }
3035
3036 display_str
3037 }
3038
3039 #[derive(Debug, Error)]
3041 pub enum UpdateVersionParseError {
3042 #[error("version string is empty")]
3044 EmptyString,
3045
3046 #[error(
3048 "`{input}` is not a valid semver requirement\n\
3049 (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
3050 )]
3051 InvalidVersionReq {
3052 input: String,
3054
3055 #[source]
3057 error: semver::Error,
3058 },
3059
3060 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3062 InvalidVersion {
3063 input: String,
3065
3066 #[source]
3068 error: semver::Error,
3069 },
3070 }
3071
3072 fn extra_semver_output(input: &str) -> String {
3073 if input.parse::<VersionReq>().is_ok() {
3076 format!(
3077 "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
3078 )
3079 } else {
3080 "".to_owned()
3081 }
3082 }
3083}
3084
3085#[cfg(feature = "self-update")]
3086pub use self_update_errors::*;
3087
3088#[cfg(test)]
3089mod tests {
3090 use super::*;
3091
3092 #[test]
3093 fn display_error_chain() {
3094 let err1 = StringError::new("err1", None);
3095
3096 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
3097
3098 let err2 = StringError::new("err2", Some(err1));
3099 let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
3100
3101 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
3102 err3
3103 err3 line 2
3104 caused by:
3105 - err2
3106 - err1
3107 ");
3108 }
3109
3110 #[test]
3111 fn display_error_list() {
3112 let err1 = StringError::new("err1", None);
3113
3114 let error_list =
3115 ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
3116 .expect(">= 1 error");
3117 insta::assert_snapshot!(format!("{}", error_list), @"err1");
3118 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
3119
3120 let err2 = StringError::new("err2", Some(err1));
3121 let err3 = StringError::new("err3", Some(err2));
3122
3123 let error_list =
3124 ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
3125 .expect(">= 1 error");
3126 insta::assert_snapshot!(format!("{}", error_list), @"err3");
3127 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3128 err3
3129 caused by:
3130 - err2
3131 - err1
3132 ");
3133
3134 let err4 = StringError::new("err4", None);
3135 let err5 = StringError::new("err5", Some(err4));
3136 let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
3137
3138 let error_list = ErrorList::<StringError>::new(
3139 "waiting for the heat death of the universe",
3140 vec![err3, err6],
3141 )
3142 .expect(">= 1 error");
3143
3144 insta::assert_snapshot!(format!("{}", error_list), @"
3145 2 errors occurred waiting for the heat death of the universe:
3146 * err3
3147 caused by:
3148 - err2
3149 - err1
3150 * err6
3151 err6 line 2
3152 caused by:
3153 - err5
3154 - err4
3155 ");
3156 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3157 2 errors occurred waiting for the heat death of the universe:
3158 * err3
3159 caused by:
3160 - err2
3161 - err1
3162 * err6
3163 err6 line 2
3164 caused by:
3165 - err5
3166 - err4
3167 ");
3168 }
3169
3170 #[derive(Clone, Debug, Error)]
3171 struct StringError {
3172 message: String,
3173 #[source]
3174 source: Option<Box<StringError>>,
3175 }
3176
3177 impl StringError {
3178 fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
3179 Self {
3180 message: message.into(),
3181 source: source.map(Box::new),
3182 }
3183 }
3184 }
3185
3186 impl fmt::Display for StringError {
3187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3188 write!(f, "{}", self.message)
3189 }
3190 }
3191}