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,
20 reuse_build::{ArchiveFormat, ArchiveStep},
21 target_runner::PlatformRunnerSource,
22};
23use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
24use config::ConfigError;
25use etcetera::HomeDirError;
26use itertools::{Either, Itertools};
27use nextest_filtering::errors::FiltersetParseErrors;
28use nextest_metadata::RustBinaryId;
29use quick_junit::ReportUuid;
30use serde::{Deserialize, Serialize};
31use smol_str::SmolStr;
32use std::{
33 borrow::Cow,
34 collections::BTreeSet,
35 env::JoinPathsError,
36 fmt::{self, Write as _},
37 path::PathBuf,
38 process::ExitStatus,
39 sync::Arc,
40};
41use target_spec_miette::IntoMietteDiagnostic;
42use thiserror::Error;
43pub use zip::result::ZipError;
45
46#[derive(Debug, Error)]
48#[error(
49 "failed to parse nextest config at `{config_file}`{}",
50 provided_by_tool(tool.as_ref())
51)]
52#[non_exhaustive]
53pub struct ConfigParseError {
54 config_file: Utf8PathBuf,
55 tool: Option<ToolName>,
56 #[source]
57 kind: ConfigParseErrorKind,
58}
59
60impl ConfigParseError {
61 pub(crate) fn new(
62 config_file: impl Into<Utf8PathBuf>,
63 tool: Option<&ToolName>,
64 kind: ConfigParseErrorKind,
65 ) -> Self {
66 Self {
67 config_file: config_file.into(),
68 tool: tool.cloned(),
69 kind,
70 }
71 }
72
73 pub fn config_file(&self) -> &Utf8Path {
75 &self.config_file
76 }
77
78 pub fn tool(&self) -> Option<&ToolName> {
80 self.tool.as_ref()
81 }
82
83 pub fn kind(&self) -> &ConfigParseErrorKind {
85 &self.kind
86 }
87}
88
89pub fn provided_by_tool(tool: Option<&ToolName>) -> String {
91 match tool {
92 Some(tool) => format!(" provided by tool `{tool}`"),
93 None => String::new(),
94 }
95}
96
97#[derive(Debug, Error)]
101#[non_exhaustive]
102pub enum ConfigParseErrorKind {
103 #[error(transparent)]
105 BuildError(Box<ConfigError>),
106 #[error(transparent)]
108 TomlParseError(Box<toml::de::Error>),
109 #[error(transparent)]
110 DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
112 #[error(transparent)]
114 VersionOnlyReadError(std::io::Error),
115 #[error(transparent)]
117 VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
118 #[error("error parsing compiled data (destructure this variant for more details)")]
120 CompileErrors(Vec<ConfigCompileError>),
121 #[error("invalid test groups defined: {}\n(test groups cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
123 InvalidTestGroupsDefined(BTreeSet<CustomTestGroup>),
124 #[error(
126 "invalid test groups defined by tool: {}\n(test groups must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
127 InvalidTestGroupsDefinedByTool(BTreeSet<CustomTestGroup>),
128 #[error("unknown test groups specified by config (destructure this variant for more details)")]
130 UnknownTestGroups {
131 errors: Vec<UnknownTestGroupError>,
133
134 known_groups: BTreeSet<TestGroup>,
136 },
137 #[error(
139 "both `[script.*]` and `[scripts.*]` defined\n\
140 (hint: [script.*] will be removed in the future: switch to [scripts.setup.*])"
141 )]
142 BothScriptAndScriptsDefined,
143 #[error("invalid config scripts defined: {}\n(config scripts cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
145 InvalidConfigScriptsDefined(BTreeSet<ScriptId>),
146 #[error(
148 "invalid config scripts defined by tool: {}\n(config scripts must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
149 InvalidConfigScriptsDefinedByTool(BTreeSet<ScriptId>),
150 #[error(
152 "config script names used more than once: {}\n\
153 (config script names must be unique across all script types)", .0.iter().join(", ")
154 )]
155 DuplicateConfigScriptNames(BTreeSet<ScriptId>),
156 #[error(
158 "errors in profile-specific config scripts (destructure this variant for more details)"
159 )]
160 ProfileScriptErrors {
161 errors: Box<ProfileScriptErrors>,
163
164 known_scripts: BTreeSet<ScriptId>,
166 },
167 #[error("unknown experimental features defined (destructure this variant for more details)")]
169 UnknownExperimentalFeatures {
170 unknown: BTreeSet<String>,
172
173 known: BTreeSet<ConfigExperimental>,
175 },
176 #[error(
180 "tool config file specifies experimental features `{}` \
181 -- only repository config files can do so",
182 .features.iter().join(", "),
183 )]
184 ExperimentalFeaturesInToolConfig {
185 features: BTreeSet<String>,
187 },
188 #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
190 ExperimentalFeaturesNotEnabled {
191 missing_features: BTreeSet<ConfigExperimental>,
193 },
194 #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
196 InheritanceErrors(Vec<InheritsError>),
197}
198
199#[derive(Debug)]
202#[non_exhaustive]
203pub struct ConfigCompileError {
204 pub profile_name: String,
206
207 pub section: ConfigCompileSection,
209
210 pub kind: ConfigCompileErrorKind,
212}
213
214#[derive(Debug)]
217pub enum ConfigCompileSection {
218 DefaultFilter,
220
221 Override(usize),
223
224 Script(usize),
226}
227
228#[derive(Debug)]
230#[non_exhaustive]
231pub enum ConfigCompileErrorKind {
232 ConstraintsNotSpecified {
234 default_filter_specified: bool,
239 },
240
241 FilterAndDefaultFilterSpecified,
245
246 Parse {
248 host_parse_error: Option<target_spec::Error>,
250
251 target_parse_error: Option<target_spec::Error>,
253
254 filter_parse_errors: Vec<FiltersetParseErrors>,
256 },
257}
258
259impl ConfigCompileErrorKind {
260 pub fn reports(&self) -> impl Iterator<Item = miette::Report> + '_ {
262 match self {
263 Self::ConstraintsNotSpecified {
264 default_filter_specified,
265 } => {
266 let message = if *default_filter_specified {
267 "for override with `default-filter`, `platform` must also be specified"
268 } else {
269 "at least one of `platform` and `filter` must be specified"
270 };
271 Either::Left(std::iter::once(miette::Report::msg(message)))
272 }
273 Self::FilterAndDefaultFilterSpecified => {
274 Either::Left(std::iter::once(miette::Report::msg(
275 "at most one of `filter` and `default-filter` must be specified",
276 )))
277 }
278 Self::Parse {
279 host_parse_error,
280 target_parse_error,
281 filter_parse_errors,
282 } => {
283 let host_parse_report = host_parse_error
284 .as_ref()
285 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
286 let target_parse_report = target_parse_error
287 .as_ref()
288 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
289 let filter_parse_reports =
290 filter_parse_errors.iter().flat_map(|filter_parse_errors| {
291 filter_parse_errors.errors.iter().map(|single_error| {
292 miette::Report::new(single_error.clone())
293 .with_source_code(filter_parse_errors.input.to_owned())
294 })
295 });
296
297 Either::Right(
298 host_parse_report
299 .into_iter()
300 .chain(target_parse_report)
301 .chain(filter_parse_reports),
302 )
303 }
304 }
305 }
306}
307
308#[derive(Clone, Debug, Error)]
310#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
311pub struct TestPriorityOutOfRange {
312 pub priority: i8,
314}
315
316#[derive(Clone, Debug, Error)]
318pub enum ChildStartError {
319 #[error("error creating temporary path for setup script")]
321 TempPath(#[source] Arc<std::io::Error>),
322
323 #[error("error spawning child process")]
325 Spawn(#[source] Arc<std::io::Error>),
326}
327
328#[derive(Clone, Debug, Error)]
330pub enum SetupScriptOutputError {
331 #[error("error opening environment file `{path}`")]
333 EnvFileOpen {
334 path: Utf8PathBuf,
336
337 #[source]
339 error: Arc<std::io::Error>,
340 },
341
342 #[error("error reading environment file `{path}`")]
344 EnvFileRead {
345 path: Utf8PathBuf,
347
348 #[source]
350 error: Arc<std::io::Error>,
351 },
352
353 #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
355 EnvFileParse {
356 path: Utf8PathBuf,
358 line: String,
360 },
361
362 #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
364 EnvFileReservedKey {
365 key: String,
367 },
368}
369
370#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
375pub struct ErrorList<T> {
376 description: Cow<'static, str>,
378 inner: Vec<T>,
380}
381
382impl<T: std::error::Error> ErrorList<T> {
383 pub(crate) fn new<U>(description: &'static str, errors: Vec<U>) -> Option<Self>
384 where
385 T: From<U>,
386 {
387 if errors.is_empty() {
388 None
389 } else {
390 Some(Self {
391 description: Cow::Borrowed(description),
392 inner: errors.into_iter().map(T::from).collect(),
393 })
394 }
395 }
396
397 pub(crate) fn short_message(&self) -> String {
399 let string = self.to_string();
400 match string.lines().next() {
401 Some(first_line) => first_line.trim_end_matches(':').to_string(),
403 None => String::new(),
404 }
405 }
406
407 pub fn description(&self) -> &str {
409 &self.description
410 }
411
412 pub fn iter(&self) -> impl Iterator<Item = &T> {
414 self.inner.iter()
415 }
416
417 pub fn map<U, F>(self, f: F) -> ErrorList<U>
419 where
420 U: std::error::Error,
421 F: FnMut(T) -> U,
422 {
423 ErrorList {
424 description: self.description,
425 inner: self.inner.into_iter().map(f).collect(),
426 }
427 }
428}
429
430impl<T: std::error::Error> IntoIterator for ErrorList<T> {
431 type Item = T;
432 type IntoIter = std::vec::IntoIter<T>;
433
434 fn into_iter(self) -> Self::IntoIter {
435 self.inner.into_iter()
436 }
437}
438
439impl<T: std::error::Error> fmt::Display for ErrorList<T> {
440 fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
441 if self.inner.len() == 1 {
443 return write!(f, "{}", self.inner[0]);
444 }
445
446 writeln!(
448 f,
449 "{} errors occurred {}:",
450 self.inner.len(),
451 self.description,
452 )?;
453 for error in &self.inner {
454 let mut indent = indented(f).with_str(" ").skip_initial();
455 writeln!(indent, "* {}", DisplayErrorChain::new(error))?;
456 f = indent.into_inner();
457 }
458 Ok(())
459 }
460}
461
462#[cfg(test)]
463impl<T: proptest::arbitrary::Arbitrary + std::fmt::Debug + 'static> proptest::arbitrary::Arbitrary
464 for ErrorList<T>
465{
466 type Parameters = ();
467 type Strategy = proptest::strategy::BoxedStrategy<Self>;
468
469 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
470 use proptest::prelude::*;
471
472 proptest::collection::vec(any::<T>(), 1..=5)
474 .prop_map(|inner| ErrorList {
475 description: Cow::Borrowed("test errors"),
476 inner,
477 })
478 .boxed()
479 }
480}
481
482impl<T: std::error::Error> std::error::Error for ErrorList<T> {
483 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
484 if self.inner.len() == 1 {
485 self.inner[0].source()
486 } else {
487 None
490 }
491 }
492}
493
494pub struct DisplayErrorChain<E> {
499 error: E,
500 initial_indent: &'static str,
501}
502
503impl<E: std::error::Error> DisplayErrorChain<E> {
504 pub fn new(error: E) -> Self {
506 Self {
507 error,
508 initial_indent: "",
509 }
510 }
511
512 pub fn new_with_initial_indent(initial_indent: &'static str, error: E) -> Self {
514 Self {
515 error,
516 initial_indent,
517 }
518 }
519}
520
521impl<E> fmt::Display for DisplayErrorChain<E>
522where
523 E: std::error::Error,
524{
525 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
526 let mut writer = indented(f).with_str(self.initial_indent);
527 write!(writer, "{}", self.error)?;
528
529 let Some(mut cause) = self.error.source() else {
530 return Ok(());
531 };
532
533 write!(writer, "\n caused by:")?;
534
535 loop {
536 writeln!(writer)?;
537 let mut indent = indented(&mut writer).with_str(" ").skip_initial();
539 write!(indent, " - {cause}")?;
540
541 let Some(next_cause) = cause.source() else {
542 break Ok(());
543 };
544
545 cause = next_cause;
546 }
547 }
548}
549
550#[derive(Clone, Debug, Error)]
552pub enum ChildError {
553 #[error(transparent)]
555 Fd(#[from] ChildFdError),
556
557 #[error(transparent)]
559 SetupScriptOutput(#[from] SetupScriptOutputError),
560}
561
562#[derive(Clone, Debug, Error)]
564pub enum ChildFdError {
565 #[error("error reading standard output")]
567 ReadStdout(#[source] Arc<std::io::Error>),
568
569 #[error("error reading standard error")]
571 ReadStderr(#[source] Arc<std::io::Error>),
572
573 #[error("error reading combined stream")]
575 ReadCombined(#[source] Arc<std::io::Error>),
576
577 #[error("error waiting for child process to exit")]
579 Wait(#[source] Arc<std::io::Error>),
580}
581
582#[derive(Clone, Debug, Eq, PartialEq)]
584#[non_exhaustive]
585pub struct UnknownTestGroupError {
586 pub profile_name: String,
588
589 pub name: TestGroup,
591}
592
593#[derive(Clone, Debug, Eq, PartialEq)]
596pub struct ProfileUnknownScriptError {
597 pub profile_name: String,
599
600 pub name: ScriptId,
602}
603
604#[derive(Clone, Debug, Eq, PartialEq)]
607pub struct ProfileWrongConfigScriptTypeError {
608 pub profile_name: String,
610
611 pub name: ScriptId,
613
614 pub attempted: ProfileScriptType,
616
617 pub actual: ScriptType,
619}
620
621#[derive(Clone, Debug, Eq, PartialEq)]
624pub struct ProfileListScriptUsesRunFiltersError {
625 pub profile_name: String,
627
628 pub name: ScriptId,
630
631 pub script_type: ProfileScriptType,
633
634 pub filters: BTreeSet<String>,
636}
637
638#[derive(Clone, Debug, Default)]
640pub struct ProfileScriptErrors {
641 pub unknown_scripts: Vec<ProfileUnknownScriptError>,
643
644 pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
646
647 pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
649}
650
651impl ProfileScriptErrors {
652 pub fn is_empty(&self) -> bool {
654 self.unknown_scripts.is_empty()
655 && self.wrong_script_types.is_empty()
656 && self.list_scripts_using_run_filters.is_empty()
657 }
658}
659
660#[derive(Clone, Debug, Error)]
662#[error("profile `{profile}` not found (known profiles: {})", .all_profiles.join(", "))]
663pub struct ProfileNotFound {
664 profile: String,
665 all_profiles: Vec<String>,
666}
667
668impl ProfileNotFound {
669 pub(crate) fn new(
670 profile: impl Into<String>,
671 all_profiles: impl IntoIterator<Item = impl Into<String>>,
672 ) -> Self {
673 let mut all_profiles: Vec<_> = all_profiles.into_iter().map(|s| s.into()).collect();
674 all_profiles.sort_unstable();
675 Self {
676 profile: profile.into(),
677 all_profiles,
678 }
679 }
680}
681
682#[derive(Clone, Debug, Error, Eq, PartialEq)]
684pub enum InvalidIdentifier {
685 #[error("identifier is empty")]
687 Empty,
688
689 #[error("invalid identifier `{0}`")]
691 InvalidXid(SmolStr),
692
693 #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
695 ToolIdentifierInvalidFormat(SmolStr),
696
697 #[error("tool identifier has empty component: `{0}`")]
699 ToolComponentEmpty(SmolStr),
700
701 #[error("invalid tool identifier `{0}`")]
703 ToolIdentifierInvalidXid(SmolStr),
704}
705
706#[derive(Clone, Debug, Error, Eq, PartialEq)]
708pub enum InvalidToolName {
709 #[error("tool name is empty")]
711 Empty,
712
713 #[error("invalid tool name `{0}`")]
715 InvalidXid(SmolStr),
716
717 #[error("tool name cannot start with \"@tool\": `{0}`")]
719 StartsWithToolPrefix(SmolStr),
720}
721
722#[derive(Clone, Debug, Error)]
724#[error("invalid custom test group name: {0}")]
725pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
726
727#[derive(Clone, Debug, Error)]
729#[error("invalid configuration script name: {0}")]
730pub struct InvalidConfigScriptName(pub InvalidIdentifier);
731
732#[derive(Clone, Debug, Error, PartialEq, Eq)]
734pub enum ToolConfigFileParseError {
735 #[error(
736 "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
737 )]
738 InvalidFormat {
740 input: String,
742 },
743
744 #[error("tool-config-file has invalid tool name: {input}")]
746 InvalidToolName {
747 input: String,
749
750 #[source]
752 error: InvalidToolName,
753 },
754
755 #[error("tool-config-file has empty config file path: {input}")]
757 EmptyConfigFile {
758 input: String,
760 },
761
762 #[error("tool-config-file is not an absolute path: {config_file}")]
764 ConfigFileNotAbsolute {
765 config_file: Utf8PathBuf,
767 },
768}
769
770#[derive(Debug, Error)]
772#[non_exhaustive]
773pub enum UserConfigError {
774 #[error("user config file not found at {path}")]
777 FileNotFound {
778 path: Utf8PathBuf,
780 },
781
782 #[error("failed to read user config at {path}")]
784 Read {
785 path: Utf8PathBuf,
787 #[source]
789 error: std::io::Error,
790 },
791
792 #[error("failed to parse user config at {path}")]
794 Parse {
795 path: Utf8PathBuf,
797 #[source]
799 error: toml::de::Error,
800 },
801
802 #[error("user config path contains non-UTF-8 characters")]
804 NonUtf8Path {
805 #[source]
807 error: FromPathBufError,
808 },
809
810 #[error(
812 "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
813 )]
814 OverridePlatformSpec {
815 path: Utf8PathBuf,
817 index: usize,
819 #[source]
821 error: target_spec::Error,
822 },
823}
824
825#[derive(Clone, Debug, Error)]
827#[error("unrecognized value for max-fail: {reason}")]
828pub struct MaxFailParseError {
829 pub reason: String,
831}
832
833impl MaxFailParseError {
834 pub(crate) fn new(reason: impl Into<String>) -> Self {
835 Self {
836 reason: reason.into(),
837 }
838 }
839}
840
841#[derive(Clone, Debug, Error)]
843#[error(
844 "unrecognized value for stress-count: {input}\n\
845 (hint: expected either a positive integer or \"infinite\")"
846)]
847pub struct StressCountParseError {
848 pub input: String,
850}
851
852impl StressCountParseError {
853 pub(crate) fn new(input: impl Into<String>) -> Self {
854 Self {
855 input: input.into(),
856 }
857 }
858}
859
860#[derive(Clone, Debug, Error)]
862#[non_exhaustive]
863pub enum DebuggerCommandParseError {
864 #[error(transparent)]
866 ShellWordsParse(shell_words::ParseError),
867
868 #[error("debugger command cannot be empty")]
870 EmptyCommand,
871}
872
873#[derive(Clone, Debug, Error)]
875#[non_exhaustive]
876pub enum TracerCommandParseError {
877 #[error(transparent)]
879 ShellWordsParse(shell_words::ParseError),
880
881 #[error("tracer command cannot be empty")]
883 EmptyCommand,
884}
885
886#[derive(Clone, Debug, Error)]
888#[error(
889 "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
890)]
891pub struct TestThreadsParseError {
892 pub input: String,
894}
895
896impl TestThreadsParseError {
897 pub(crate) fn new(input: impl Into<String>) -> Self {
898 Self {
899 input: input.into(),
900 }
901 }
902}
903
904#[derive(Clone, Debug, Error)]
907pub struct PartitionerBuilderParseError {
908 expected_format: Option<&'static str>,
909 message: Cow<'static, str>,
910}
911
912impl PartitionerBuilderParseError {
913 pub(crate) fn new(
914 expected_format: Option<&'static str>,
915 message: impl Into<Cow<'static, str>>,
916 ) -> Self {
917 Self {
918 expected_format,
919 message: message.into(),
920 }
921 }
922}
923
924impl fmt::Display for PartitionerBuilderParseError {
925 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
926 match self.expected_format {
927 Some(format) => {
928 write!(
929 f,
930 "partition must be in the format \"{}\":\n{}",
931 format, self.message
932 )
933 }
934 None => write!(f, "{}", self.message),
935 }
936 }
937}
938
939#[derive(Clone, Debug, Error)]
942pub enum TestFilterBuilderError {
943 #[error("error constructing test filters")]
945 Construct {
946 #[from]
948 error: aho_corasick::BuildError,
949 },
950}
951
952#[derive(Debug, Error)]
954pub enum PathMapperConstructError {
955 #[error("{kind} `{input}` failed to canonicalize")]
957 Canonicalization {
958 kind: PathMapperConstructKind,
960
961 input: Utf8PathBuf,
963
964 #[source]
966 err: std::io::Error,
967 },
968 #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
970 NonUtf8Path {
971 kind: PathMapperConstructKind,
973
974 input: Utf8PathBuf,
976
977 #[source]
979 err: FromPathBufError,
980 },
981 #[error("{kind} `{canonicalized_path}` is not a directory")]
983 NotADirectory {
984 kind: PathMapperConstructKind,
986
987 input: Utf8PathBuf,
989
990 canonicalized_path: Utf8PathBuf,
992 },
993}
994
995impl PathMapperConstructError {
996 pub fn kind(&self) -> PathMapperConstructKind {
998 match self {
999 Self::Canonicalization { kind, .. }
1000 | Self::NonUtf8Path { kind, .. }
1001 | Self::NotADirectory { kind, .. } => *kind,
1002 }
1003 }
1004
1005 pub fn input(&self) -> &Utf8Path {
1007 match self {
1008 Self::Canonicalization { input, .. }
1009 | Self::NonUtf8Path { input, .. }
1010 | Self::NotADirectory { input, .. } => input,
1011 }
1012 }
1013}
1014
1015#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1020pub enum PathMapperConstructKind {
1021 WorkspaceRoot,
1023
1024 TargetDir,
1026}
1027
1028impl fmt::Display for PathMapperConstructKind {
1029 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1030 match self {
1031 Self::WorkspaceRoot => write!(f, "remapped workspace root"),
1032 Self::TargetDir => write!(f, "remapped target directory"),
1033 }
1034 }
1035}
1036
1037#[derive(Debug, Error)]
1039pub enum RustBuildMetaParseError {
1040 #[error("error deserializing platform from build metadata")]
1042 PlatformDeserializeError(#[from] target_spec::Error),
1043
1044 #[error("the host platform could not be determined")]
1046 DetectBuildTargetError(#[source] target_spec::Error),
1047
1048 #[error("unsupported features in the build metadata: {message}")]
1050 Unsupported {
1051 message: String,
1053 },
1054}
1055
1056#[derive(Clone, Debug, thiserror::Error)]
1059#[error("invalid format version: {input}")]
1060pub struct FormatVersionError {
1061 pub input: String,
1063 #[source]
1065 pub error: FormatVersionErrorInner,
1066}
1067
1068#[derive(Clone, Debug, thiserror::Error)]
1070pub enum FormatVersionErrorInner {
1071 #[error("expected format version in form of `{expected}`")]
1073 InvalidFormat {
1074 expected: &'static str,
1076 },
1077 #[error("version component `{which}` could not be parsed as an integer")]
1079 InvalidInteger {
1080 which: &'static str,
1082 #[source]
1084 err: std::num::ParseIntError,
1085 },
1086 #[error("version component `{which}` value {value} is out of range {range:?}")]
1088 InvalidValue {
1089 which: &'static str,
1091 value: u8,
1093 range: std::ops::Range<u8>,
1095 },
1096}
1097
1098#[derive(Debug, Error)]
1101#[non_exhaustive]
1102pub enum FromMessagesError {
1103 #[error("error reading Cargo JSON messages")]
1105 ReadMessages(#[source] std::io::Error),
1106
1107 #[error("error querying package graph")]
1109 PackageGraph(#[source] guppy::Error),
1110
1111 #[error("missing kind for target {binary_name} in package {package_name}")]
1113 MissingTargetKind {
1114 package_name: String,
1116 binary_name: String,
1118 },
1119}
1120
1121#[derive(Debug, Error)]
1123#[non_exhaustive]
1124pub enum CreateTestListError {
1125 #[error(
1127 "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
1128 (hint: ensure project source is available at this location)"
1129 )]
1130 CwdIsNotDir {
1131 binary_id: RustBinaryId,
1133
1134 cwd: Utf8PathBuf,
1136 },
1137
1138 #[error(
1140 "for `{binary_id}`, running command `{}` failed to execute",
1141 shell_words::join(command)
1142 )]
1143 CommandExecFail {
1144 binary_id: RustBinaryId,
1146
1147 command: Vec<String>,
1149
1150 #[source]
1152 error: std::io::Error,
1153 },
1154
1155 #[error(
1157 "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1158 shell_words::join(command),
1159 display_exited_with(*exit_status),
1160 String::from_utf8_lossy(stdout),
1161 String::from_utf8_lossy(stderr),
1162 )]
1163 CommandFail {
1164 binary_id: RustBinaryId,
1166
1167 command: Vec<String>,
1169
1170 exit_status: ExitStatus,
1172
1173 stdout: Vec<u8>,
1175
1176 stderr: Vec<u8>,
1178 },
1179
1180 #[error(
1182 "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1183 shell_words::join(command),
1184 String::from_utf8_lossy(stdout),
1185 String::from_utf8_lossy(stderr)
1186 )]
1187 CommandNonUtf8 {
1188 binary_id: RustBinaryId,
1190
1191 command: Vec<String>,
1193
1194 stdout: Vec<u8>,
1196
1197 stderr: Vec<u8>,
1199 },
1200
1201 #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1203 ParseLine {
1204 binary_id: RustBinaryId,
1206
1207 message: Cow<'static, str>,
1209
1210 full_output: String,
1212 },
1213
1214 #[error(
1216 "error joining dynamic library paths for {}: [{}]",
1217 dylib_path_envvar(),
1218 itertools::join(.new_paths, ", ")
1219 )]
1220 DylibJoinPaths {
1221 new_paths: Vec<Utf8PathBuf>,
1223
1224 #[source]
1226 error: JoinPathsError,
1227 },
1228
1229 #[error("error creating Tokio runtime")]
1231 TokioRuntimeCreate(#[source] std::io::Error),
1232}
1233
1234impl CreateTestListError {
1235 pub(crate) fn parse_line(
1236 binary_id: RustBinaryId,
1237 message: impl Into<Cow<'static, str>>,
1238 full_output: impl Into<String>,
1239 ) -> Self {
1240 Self::ParseLine {
1241 binary_id,
1242 message: message.into(),
1243 full_output: full_output.into(),
1244 }
1245 }
1246
1247 pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1248 Self::DylibJoinPaths { new_paths, error }
1249 }
1250}
1251
1252#[derive(Debug, Error)]
1254#[non_exhaustive]
1255pub enum WriteTestListError {
1256 #[error("error writing to output")]
1258 Io(#[source] std::io::Error),
1259
1260 #[error("error serializing to JSON")]
1262 Json(#[source] serde_json::Error),
1263}
1264
1265#[derive(Debug, Error)]
1269pub enum ConfigureHandleInheritanceError {
1270 #[cfg(windows)]
1272 #[error("error configuring handle inheritance")]
1273 WindowsError(#[from] std::io::Error),
1274}
1275
1276#[derive(Debug, Error)]
1278#[non_exhaustive]
1279pub enum TestRunnerBuildError {
1280 #[error("error creating Tokio runtime")]
1282 TokioRuntimeCreate(#[source] std::io::Error),
1283
1284 #[error("error setting up signals")]
1286 SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1287}
1288
1289#[derive(Debug, Error)]
1291pub struct TestRunnerExecuteErrors<E> {
1292 pub report_error: Option<E>,
1294
1295 pub join_errors: Vec<tokio::task::JoinError>,
1298}
1299
1300impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1302 if let Some(report_error) = &self.report_error {
1303 write!(f, "error reporting results: {report_error}")?;
1304 }
1305
1306 if !self.join_errors.is_empty() {
1307 if self.report_error.is_some() {
1308 write!(f, "; ")?;
1309 }
1310
1311 write!(f, "errors joining tasks: ")?;
1312
1313 for (i, join_error) in self.join_errors.iter().enumerate() {
1314 if i > 0 {
1315 write!(f, ", ")?;
1316 }
1317
1318 write!(f, "{join_error}")?;
1319 }
1320 }
1321
1322 Ok(())
1323 }
1324}
1325
1326#[derive(Debug, Error)]
1330#[error(
1331 "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1332 supported_extensions()
1333)]
1334pub struct UnknownArchiveFormat {
1335 pub file_name: String,
1337}
1338
1339fn supported_extensions() -> String {
1340 ArchiveFormat::SUPPORTED_FORMATS
1341 .iter()
1342 .map(|(extension, _)| *extension)
1343 .join(", ")
1344}
1345
1346#[derive(Debug, Error)]
1348#[non_exhaustive]
1349pub enum ArchiveCreateError {
1350 #[error("error creating binary list")]
1352 CreateBinaryList(#[source] WriteTestListError),
1353
1354 #[error("extra path `{}` not found", .redactor.redact_path(path))]
1356 MissingExtraPath {
1357 path: Utf8PathBuf,
1359
1360 redactor: Redactor,
1365 },
1366
1367 #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1369 InputFileRead {
1370 step: ArchiveStep,
1372
1373 path: Utf8PathBuf,
1375
1376 is_dir: Option<bool>,
1378
1379 #[source]
1381 error: std::io::Error,
1382 },
1383
1384 #[error("error reading directory entry from `{path}")]
1386 DirEntryRead {
1387 path: Utf8PathBuf,
1389
1390 #[source]
1392 error: std::io::Error,
1393 },
1394
1395 #[error("error writing to archive")]
1397 OutputArchiveIo(#[source] std::io::Error),
1398
1399 #[error("error reporting archive status")]
1401 ReporterIo(#[source] std::io::Error),
1402}
1403
1404fn kind_str(is_dir: Option<bool>) -> &'static str {
1405 match is_dir {
1406 Some(true) => "directory",
1407 Some(false) => "file",
1408 None => "path",
1409 }
1410}
1411
1412#[derive(Debug, Error)]
1414pub enum MetadataMaterializeError {
1415 #[error("I/O error reading metadata file `{path}`")]
1417 Read {
1418 path: Utf8PathBuf,
1420
1421 #[source]
1423 error: std::io::Error,
1424 },
1425
1426 #[error("error deserializing metadata file `{path}`")]
1428 Deserialize {
1429 path: Utf8PathBuf,
1431
1432 #[source]
1434 error: serde_json::Error,
1435 },
1436
1437 #[error("error parsing Rust build metadata from `{path}`")]
1439 RustBuildMeta {
1440 path: Utf8PathBuf,
1442
1443 #[source]
1445 error: RustBuildMetaParseError,
1446 },
1447
1448 #[error("error building package graph from `{path}`")]
1450 PackageGraphConstruct {
1451 path: Utf8PathBuf,
1453
1454 #[source]
1456 error: guppy::Error,
1457 },
1458}
1459
1460#[derive(Debug, Error)]
1464#[non_exhaustive]
1465pub enum ArchiveReadError {
1466 #[error("I/O error reading archive")]
1468 Io(#[source] std::io::Error),
1469
1470 #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1472 NonUtf8Path(Vec<u8>),
1473
1474 #[error("path in archive `{0}` doesn't start with `target/`")]
1476 NoTargetPrefix(Utf8PathBuf),
1477
1478 #[error("path in archive `{path}` contains an invalid component `{component}`")]
1480 InvalidComponent {
1481 path: Utf8PathBuf,
1483
1484 component: String,
1486 },
1487
1488 #[error("corrupted archive: checksum read error for path `{path}`")]
1490 ChecksumRead {
1491 path: Utf8PathBuf,
1493
1494 #[source]
1496 error: std::io::Error,
1497 },
1498
1499 #[error("corrupted archive: invalid checksum for path `{path}`")]
1501 InvalidChecksum {
1502 path: Utf8PathBuf,
1504
1505 expected: u32,
1507
1508 actual: u32,
1510 },
1511
1512 #[error("metadata file `{0}` not found in archive")]
1514 MetadataFileNotFound(&'static Utf8Path),
1515
1516 #[error("error deserializing metadata file `{path}` in archive")]
1518 MetadataDeserializeError {
1519 path: &'static Utf8Path,
1521
1522 #[source]
1524 error: serde_json::Error,
1525 },
1526
1527 #[error("error building package graph from `{path}` in archive")]
1529 PackageGraphConstructError {
1530 path: &'static Utf8Path,
1532
1533 #[source]
1535 error: guppy::Error,
1536 },
1537}
1538
1539#[derive(Debug, Error)]
1543#[non_exhaustive]
1544pub enum ArchiveExtractError {
1545 #[error("error creating temporary directory")]
1547 TempDirCreate(#[source] std::io::Error),
1548
1549 #[error("error canonicalizing destination directory `{dir}`")]
1551 DestDirCanonicalization {
1552 dir: Utf8PathBuf,
1554
1555 #[source]
1557 error: std::io::Error,
1558 },
1559
1560 #[error("destination `{0}` already exists")]
1562 DestinationExists(Utf8PathBuf),
1563
1564 #[error("error reading archive")]
1566 Read(#[source] ArchiveReadError),
1567
1568 #[error("error deserializing Rust build metadata")]
1570 RustBuildMeta(#[from] RustBuildMetaParseError),
1571
1572 #[error("error writing file `{path}` to disk")]
1574 WriteFile {
1575 path: Utf8PathBuf,
1577
1578 #[source]
1580 error: std::io::Error,
1581 },
1582
1583 #[error("error reporting extract status")]
1585 ReporterIo(std::io::Error),
1586}
1587
1588#[derive(Debug, Error)]
1590#[non_exhaustive]
1591pub enum WriteEventError {
1592 #[error("error writing to output")]
1594 Io(#[source] std::io::Error),
1595
1596 #[error("error operating on path {file}")]
1598 Fs {
1599 file: Utf8PathBuf,
1601
1602 #[source]
1604 error: std::io::Error,
1605 },
1606
1607 #[error("error writing JUnit output to {file}")]
1609 Junit {
1610 file: Utf8PathBuf,
1612
1613 #[source]
1615 error: quick_junit::SerializeError,
1616 },
1617}
1618
1619#[derive(Debug, Error)]
1622#[non_exhaustive]
1623pub enum CargoConfigError {
1624 #[error("failed to retrieve current directory")]
1626 GetCurrentDir(#[source] std::io::Error),
1627
1628 #[error("current directory is invalid UTF-8")]
1630 CurrentDirInvalidUtf8(#[source] FromPathBufError),
1631
1632 #[error("failed to parse --config argument `{config_str}` as TOML")]
1634 CliConfigParseError {
1635 config_str: String,
1637
1638 #[source]
1640 error: toml_edit::TomlError,
1641 },
1642
1643 #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1645 CliConfigDeError {
1646 config_str: String,
1648
1649 #[source]
1651 error: toml_edit::de::Error,
1652 },
1653
1654 #[error(
1656 "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1657 )]
1658 InvalidCliConfig {
1659 config_str: String,
1661
1662 #[source]
1664 reason: InvalidCargoCliConfigReason,
1665 },
1666
1667 #[error("non-UTF-8 path encountered")]
1669 NonUtf8Path(#[source] FromPathBufError),
1670
1671 #[error("failed to retrieve the Cargo home directory")]
1673 GetCargoHome(#[source] std::io::Error),
1674
1675 #[error("failed to canonicalize path `{path}")]
1677 FailedPathCanonicalization {
1678 path: Utf8PathBuf,
1680
1681 #[source]
1683 error: std::io::Error,
1684 },
1685
1686 #[error("failed to read config at `{path}`")]
1688 ConfigReadError {
1689 path: Utf8PathBuf,
1691
1692 #[source]
1694 error: std::io::Error,
1695 },
1696
1697 #[error(transparent)]
1699 ConfigParseError(#[from] Box<CargoConfigParseError>),
1700}
1701
1702#[derive(Debug, Error)]
1706#[error("failed to parse config at `{path}`")]
1707pub struct CargoConfigParseError {
1708 pub path: Utf8PathBuf,
1710
1711 #[source]
1713 pub error: toml::de::Error,
1714}
1715
1716#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1720#[non_exhaustive]
1721pub enum InvalidCargoCliConfigReason {
1722 #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1724 NotDottedKv,
1725
1726 #[error("includes non-whitespace decoration")]
1728 IncludesNonWhitespaceDecoration,
1729
1730 #[error("sets a value to an inline table, which is not accepted")]
1732 SetsValueToInlineTable,
1733
1734 #[error("sets a value to an array of tables, which is not accepted")]
1736 SetsValueToArrayOfTables,
1737
1738 #[error("doesn't provide a value")]
1740 DoesntProvideValue,
1741}
1742
1743#[derive(Debug, Error)]
1745pub enum HostPlatformDetectError {
1746 #[error(
1749 "error spawning `rustc -vV`, and detecting the build \
1750 target failed as well\n\
1751 - rustc spawn error: {}\n\
1752 - build target error: {}\n",
1753 DisplayErrorChain::new_with_initial_indent(" ", error),
1754 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1755 )]
1756 RustcVvSpawnError {
1757 error: std::io::Error,
1759
1760 build_target_error: Box<target_spec::Error>,
1762 },
1763
1764 #[error(
1767 "`rustc -vV` failed with {}, and detecting the \
1768 build target failed as well\n\
1769 - `rustc -vV` stdout:\n{}\n\
1770 - `rustc -vV` stderr:\n{}\n\
1771 - build target error:\n{}\n",
1772 status,
1773 DisplayIndented { item: String::from_utf8_lossy(stdout), indent: " " },
1774 DisplayIndented { item: String::from_utf8_lossy(stderr), indent: " " },
1775 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1776 )]
1777 RustcVvFailed {
1778 status: ExitStatus,
1780
1781 stdout: Vec<u8>,
1783
1784 stderr: Vec<u8>,
1786
1787 build_target_error: Box<target_spec::Error>,
1789 },
1790
1791 #[error(
1794 "parsing `rustc -vV` output failed, and detecting the build target \
1795 failed as well\n\
1796 - host platform error:\n{}\n\
1797 - build target error:\n{}\n",
1798 DisplayErrorChain::new_with_initial_indent(" ", host_platform_error),
1799 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1800 )]
1801 HostPlatformParseError {
1802 host_platform_error: Box<target_spec::Error>,
1804
1805 build_target_error: Box<target_spec::Error>,
1807 },
1808
1809 #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
1812 BuildTargetError {
1813 #[source]
1815 build_target_error: Box<target_spec::Error>,
1816 },
1817}
1818
1819#[derive(Debug, Error)]
1821pub enum TargetTripleError {
1822 #[error(
1824 "environment variable '{}' contained non-UTF-8 data",
1825 TargetTriple::CARGO_BUILD_TARGET_ENV
1826 )]
1827 InvalidEnvironmentVar,
1828
1829 #[error("error deserializing target triple from {source}")]
1831 TargetSpecError {
1832 source: TargetTripleSource,
1834
1835 #[source]
1837 error: target_spec::Error,
1838 },
1839
1840 #[error("target path `{path}` is not a valid file")]
1842 TargetPathReadError {
1843 source: TargetTripleSource,
1845
1846 path: Utf8PathBuf,
1848
1849 #[source]
1851 error: std::io::Error,
1852 },
1853
1854 #[error(
1856 "for custom platform obtained from {source}, \
1857 failed to create temporary directory for custom platform"
1858 )]
1859 CustomPlatformTempDirError {
1860 source: TargetTripleSource,
1862
1863 #[source]
1865 error: std::io::Error,
1866 },
1867
1868 #[error(
1870 "for custom platform obtained from {source}, \
1871 failed to write JSON to temporary path `{path}`"
1872 )]
1873 CustomPlatformWriteError {
1874 source: TargetTripleSource,
1876
1877 path: Utf8PathBuf,
1879
1880 #[source]
1882 error: std::io::Error,
1883 },
1884
1885 #[error(
1887 "for custom platform obtained from {source}, \
1888 failed to close temporary directory `{dir_path}`"
1889 )]
1890 CustomPlatformCloseError {
1891 source: TargetTripleSource,
1893
1894 dir_path: Utf8PathBuf,
1896
1897 #[source]
1899 error: std::io::Error,
1900 },
1901}
1902
1903impl TargetTripleError {
1904 pub fn source_report(&self) -> Option<miette::Report> {
1909 match self {
1910 Self::TargetSpecError { error, .. } => {
1911 Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
1912 }
1913 TargetTripleError::InvalidEnvironmentVar
1915 | TargetTripleError::TargetPathReadError { .. }
1916 | TargetTripleError::CustomPlatformTempDirError { .. }
1917 | TargetTripleError::CustomPlatformWriteError { .. }
1918 | TargetTripleError::CustomPlatformCloseError { .. } => None,
1919 }
1920 }
1921}
1922
1923#[derive(Debug, Error)]
1925pub enum TargetRunnerError {
1926 #[error("environment variable '{0}' contained non-UTF-8 data")]
1928 InvalidEnvironmentVar(String),
1929
1930 #[error("runner '{key}' = '{value}' did not contain a runner binary")]
1933 BinaryNotSpecified {
1934 key: PlatformRunnerSource,
1936
1937 value: String,
1939 },
1940}
1941
1942#[derive(Debug, Error)]
1944#[error("error setting up signal handler")]
1945pub struct SignalHandlerSetupError(#[from] std::io::Error);
1946
1947#[derive(Debug, Error)]
1949pub enum ShowTestGroupsError {
1950 #[error(
1952 "unknown test groups specified: {}\n(known groups: {})",
1953 unknown_groups.iter().join(", "),
1954 known_groups.iter().join(", "),
1955 )]
1956 UnknownGroups {
1957 unknown_groups: BTreeSet<TestGroup>,
1959
1960 known_groups: BTreeSet<TestGroup>,
1962 },
1963}
1964
1965#[derive(Debug, Error, PartialEq, Eq, Hash)]
1967pub enum InheritsError {
1968 #[error("the {} profile should not inherit from other profiles", .0)]
1970 DefaultProfileInheritance(String),
1971 #[error("profile {} inherits from an unknown profile {}", .0, .1)]
1973 UnknownInheritance(String, String),
1974 #[error("a self referential inheritance is detected from profile: {}", .0)]
1976 SelfReferentialInheritance(String),
1977 #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
1979 format!("[{}]", scc.iter().join(", "))
1980 }).join(", "))]
1981 InheritanceCycle(Vec<Vec<String>>),
1982}
1983
1984#[derive(Debug, Error)]
1990pub enum RunStoreError {
1991 #[error("error creating run directory `{run_dir}`")]
1993 RunDirCreate {
1994 run_dir: Utf8PathBuf,
1996
1997 #[source]
1999 error: std::io::Error,
2000 },
2001
2002 #[error("error acquiring lock on `{path}`")]
2004 FileLock {
2005 path: Utf8PathBuf,
2007
2008 #[source]
2010 error: std::io::Error,
2011 },
2012
2013 #[error(
2015 "timed out acquiring lock on `{path}` after {timeout_secs}s (is the cache directory \
2016 on a networked filesystem?)"
2017 )]
2018 FileLockTimeout {
2019 path: Utf8PathBuf,
2021
2022 timeout_secs: u64,
2024 },
2025
2026 #[error("error reading run list from `{path}`")]
2028 RunListRead {
2029 path: Utf8PathBuf,
2031
2032 #[source]
2034 error: std::io::Error,
2035 },
2036
2037 #[error("error deserializing run list from `{path}`")]
2039 RunListDeserialize {
2040 path: Utf8PathBuf,
2042
2043 #[source]
2045 error: serde_json::Error,
2046 },
2047
2048 #[error("error serializing run list to `{path}`")]
2050 RunListSerialize {
2051 path: Utf8PathBuf,
2053
2054 #[source]
2056 error: serde_json::Error,
2057 },
2058
2059 #[error("error serializing rerun info")]
2061 RerunInfoSerialize {
2062 #[source]
2064 error: serde_json::Error,
2065 },
2066
2067 #[error("error serializing test list")]
2069 TestListSerialize {
2070 #[source]
2072 error: serde_json::Error,
2073 },
2074
2075 #[error("error serializing record options")]
2077 RecordOptionsSerialize {
2078 #[source]
2080 error: serde_json::Error,
2081 },
2082
2083 #[error("error serializing test event")]
2085 TestEventSerialize {
2086 #[source]
2088 error: serde_json::Error,
2089 },
2090
2091 #[error("error writing run list to `{path}`")]
2093 RunListWrite {
2094 path: Utf8PathBuf,
2096
2097 #[source]
2099 error: atomicwrites::Error<std::io::Error>,
2100 },
2101
2102 #[error("error writing to store at `{store_path}`")]
2104 StoreWrite {
2105 store_path: Utf8PathBuf,
2107
2108 #[source]
2110 error: StoreWriterError,
2111 },
2112
2113 #[error("error creating run log at `{path}`")]
2115 RunLogCreate {
2116 path: Utf8PathBuf,
2118
2119 #[source]
2121 error: std::io::Error,
2122 },
2123
2124 #[error("error writing to run log at `{path}`")]
2126 RunLogWrite {
2127 path: Utf8PathBuf,
2129
2130 #[source]
2132 error: std::io::Error,
2133 },
2134
2135 #[error("error flushing run log at `{path}`")]
2137 RunLogFlush {
2138 path: Utf8PathBuf,
2140
2141 #[source]
2143 error: std::io::Error,
2144 },
2145
2146 #[error(
2148 "cannot write to record store: runs.json.zst format version {file_version} is newer than \
2149 supported version {max_supported_version}"
2150 )]
2151 FormatVersionTooNew {
2152 file_version: RunsJsonFormatVersion,
2154 max_supported_version: RunsJsonFormatVersion,
2156 },
2157}
2158
2159#[derive(Debug, Error)]
2161#[non_exhaustive]
2162pub enum StoreWriterError {
2163 #[error("error creating store")]
2165 Create {
2166 #[source]
2168 error: std::io::Error,
2169 },
2170
2171 #[error("error creating path `{path}` in store")]
2173 StartFile {
2174 path: Utf8PathBuf,
2176
2177 #[source]
2179 error: zip::result::ZipError,
2180 },
2181
2182 #[error("error writing to path `{path}` in store")]
2184 Write {
2185 path: Utf8PathBuf,
2187
2188 #[source]
2190 error: std::io::Error,
2191 },
2192
2193 #[error("error compressing data")]
2195 Compress {
2196 #[source]
2198 error: std::io::Error,
2199 },
2200
2201 #[error("error finalizing store")]
2203 Finish {
2204 #[source]
2206 error: zip::result::ZipError,
2207 },
2208
2209 #[error("error flushing store")]
2211 Flush {
2212 #[source]
2214 error: std::io::Error,
2215 },
2216}
2217
2218#[derive(Debug, Error)]
2220pub enum RecordReporterError {
2221 #[error(transparent)]
2223 RunStore(RunStoreError),
2224
2225 #[error("record writer thread panicked: {message}")]
2227 WriterPanic {
2228 message: String,
2230 },
2231}
2232
2233#[derive(Debug, Error)]
2235pub enum CacheDirError {
2236 #[error("could not determine platform base directory strategy")]
2240 BaseDirStrategy(#[source] HomeDirError),
2241
2242 #[error("platform cache directory is not valid UTF-8: {path:?}")]
2244 CacheDirNotUtf8 {
2245 path: PathBuf,
2247 },
2248
2249 #[error("could not canonicalize workspace path `{workspace_root}`")]
2251 Canonicalize {
2252 workspace_root: Utf8PathBuf,
2254 #[source]
2256 error: std::io::Error,
2257 },
2258}
2259
2260#[derive(Debug, Error)]
2262pub enum RecordSetupError {
2263 #[error("could not determine platform cache directory for recording")]
2265 CacheDirNotFound(#[source] CacheDirError),
2266
2267 #[error("failed to create run store")]
2269 StoreCreate(#[source] RunStoreError),
2270
2271 #[error("failed to lock run store")]
2273 StoreLock(#[source] RunStoreError),
2274
2275 #[error("failed to create run recorder")]
2277 RecorderCreate(#[source] RunStoreError),
2278}
2279
2280impl RecordSetupError {
2281 pub fn disabled_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
2288 match self {
2289 RecordSetupError::RecorderCreate(err @ RunStoreError::FormatVersionTooNew { .. }) => {
2290 Some(err)
2291 }
2292 _ => None,
2293 }
2294 }
2295}
2296
2297#[derive(Debug, Error)]
2299pub enum RecordPruneError {
2300 #[error("error deleting run `{run_id}` at `{path}`")]
2302 DeleteRun {
2303 run_id: ReportUuid,
2305
2306 path: Utf8PathBuf,
2308
2309 #[source]
2311 error: std::io::Error,
2312 },
2313
2314 #[error("error calculating size of `{path}`")]
2316 CalculateSize {
2317 path: Utf8PathBuf,
2319
2320 #[source]
2322 error: std::io::Error,
2323 },
2324
2325 #[error("error deleting orphaned directory `{path}`")]
2327 DeleteOrphan {
2328 path: Utf8PathBuf,
2330
2331 #[source]
2333 error: std::io::Error,
2334 },
2335
2336 #[error("error reading runs directory `{path}`")]
2338 ReadRunsDir {
2339 path: Utf8PathBuf,
2341
2342 #[source]
2344 error: std::io::Error,
2345 },
2346
2347 #[error("error reading directory entry in `{dir}`")]
2349 ReadDirEntry {
2350 dir: Utf8PathBuf,
2352
2353 #[source]
2355 error: std::io::Error,
2356 },
2357
2358 #[error("error reading file type for `{path}`")]
2360 ReadFileType {
2361 path: Utf8PathBuf,
2363
2364 #[source]
2366 error: std::io::Error,
2367 },
2368}
2369
2370#[derive(Clone, Debug, PartialEq, Eq, Error)]
2375#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2376pub struct InvalidRunIdSelector {
2377 pub input: String,
2379}
2380
2381#[derive(Clone, Debug, PartialEq, Eq, Error)]
2386#[error(
2387 "invalid run ID selector `{input}`: expected `latest`, hex digits, or a path ending in `.zip`"
2388)]
2389pub struct InvalidRunIdOrRecordingSelector {
2390 pub input: String,
2392}
2393
2394#[derive(Debug, Error)]
2396pub enum RunIdResolutionError {
2397 #[error("no recorded run found matching `{prefix}`")]
2399 NotFound {
2400 prefix: String,
2402 },
2403
2404 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2406 Ambiguous {
2407 prefix: String,
2409
2410 count: usize,
2412
2413 candidates: Vec<RecordedRunInfo>,
2415
2416 run_id_index: RunIdIndex,
2418 },
2419
2420 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2422 InvalidPrefix {
2423 prefix: String,
2425 },
2426
2427 #[error("no recorded runs exist")]
2429 NoRuns,
2430}
2431
2432#[derive(Debug, Error)]
2434pub enum RecordReadError {
2435 #[error("run not found at `{path}`")]
2437 RunNotFound {
2438 path: Utf8PathBuf,
2440 },
2441
2442 #[error("error opening archive at `{path}`")]
2444 OpenArchive {
2445 path: Utf8PathBuf,
2447
2448 #[source]
2450 error: std::io::Error,
2451 },
2452
2453 #[error("error reading `{file_name}` from archive")]
2455 ReadArchiveFile {
2456 file_name: String,
2458
2459 #[source]
2461 error: zip::result::ZipError,
2462 },
2463
2464 #[error("error opening run log at `{path}`")]
2466 OpenRunLog {
2467 path: Utf8PathBuf,
2469
2470 #[source]
2472 error: std::io::Error,
2473 },
2474
2475 #[error("error reading line {line_number} from run log")]
2477 ReadRunLog {
2478 line_number: usize,
2480
2481 #[source]
2483 error: std::io::Error,
2484 },
2485
2486 #[error("error parsing event at line {line_number}")]
2488 ParseEvent {
2489 line_number: usize,
2491
2492 #[source]
2494 error: serde_json::Error,
2495 },
2496
2497 #[error("required file `{file_name}` not found in archive")]
2499 FileNotFound {
2500 file_name: String,
2502 },
2503
2504 #[error("error decompressing data from `{file_name}`")]
2506 Decompress {
2507 file_name: String,
2509
2510 #[source]
2512 error: std::io::Error,
2513 },
2514
2515 #[error(
2520 "unknown output file type `{file_name}` in archive \
2521 (archive may have been created by a newer version of nextest)"
2522 )]
2523 UnknownOutputType {
2524 file_name: String,
2526 },
2527
2528 #[error(
2530 "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2531 )]
2532 FileTooLarge {
2533 file_name: String,
2535
2536 size: u64,
2538
2539 limit: u64,
2541 },
2542
2543 #[error(
2548 "file `{file_name}` size mismatch: header claims {claimed_size} bytes, \
2549 but read {actual_size} bytes (archive may be corrupt or tampered)"
2550 )]
2551 SizeMismatch {
2552 file_name: String,
2554
2555 claimed_size: u64,
2557
2558 actual_size: u64,
2560 },
2561
2562 #[error("error deserializing `{file_name}`")]
2564 DeserializeMetadata {
2565 file_name: String,
2567
2568 #[source]
2570 error: serde_json::Error,
2571 },
2572
2573 #[error("failed to extract `{store_path}` to `{output_path}`")]
2575 ExtractFile {
2576 store_path: String,
2578
2579 output_path: Utf8PathBuf,
2581
2582 #[source]
2584 error: std::io::Error,
2585 },
2586
2587 #[error("error reading portable recording")]
2589 PortableRecording(#[source] PortableRecordingReadError),
2590}
2591
2592#[derive(Debug, Error)]
2594#[non_exhaustive]
2595pub enum PortableRecordingError {
2596 #[error("run directory does not exist: {path}")]
2598 RunDirNotFound {
2599 path: Utf8PathBuf,
2601 },
2602
2603 #[error("required file missing from run directory `{run_dir}`: `{file_name}`")]
2605 RequiredFileMissing {
2606 run_dir: Utf8PathBuf,
2608 file_name: &'static str,
2610 },
2611
2612 #[error("failed to serialize manifest")]
2614 SerializeManifest(#[source] serde_json::Error),
2615
2616 #[error("failed to start file {file_name} in archive")]
2618 ZipStartFile {
2619 file_name: &'static str,
2621 #[source]
2623 source: zip::result::ZipError,
2624 },
2625
2626 #[error("failed to write {file_name} to archive")]
2628 ZipWrite {
2629 file_name: &'static str,
2631 #[source]
2633 source: std::io::Error,
2634 },
2635
2636 #[error("failed to read {file_name}")]
2638 ReadFile {
2639 file_name: &'static str,
2641 #[source]
2643 source: std::io::Error,
2644 },
2645
2646 #[error("failed to finalize archive")]
2648 ZipFinalize(#[source] zip::result::ZipError),
2649
2650 #[error("failed to write archive atomically to {path}")]
2652 AtomicWrite {
2653 path: Utf8PathBuf,
2655 #[source]
2657 source: std::io::Error,
2658 },
2659}
2660
2661#[derive(Debug, Error)]
2663#[non_exhaustive]
2664pub enum PortableRecordingReadError {
2665 #[error("failed to open archive at `{path}`")]
2667 OpenArchive {
2668 path: Utf8PathBuf,
2670 #[source]
2672 error: std::io::Error,
2673 },
2674
2675 #[error("failed to read archive at `{path}`")]
2677 ReadArchive {
2678 path: Utf8PathBuf,
2680 #[source]
2682 error: zip::result::ZipError,
2683 },
2684
2685 #[error("required file `{file_name}` missing from archive at `{path}`")]
2687 MissingFile {
2688 path: Utf8PathBuf,
2690 file_name: Cow<'static, str>,
2692 },
2693
2694 #[error("failed to parse manifest from archive at `{path}`")]
2696 ParseManifest {
2697 path: Utf8PathBuf,
2699 #[source]
2701 error: serde_json::Error,
2702 },
2703
2704 #[error(
2706 "portable recording format version {found} in `{path}` is incompatible: {incompatibility} \
2707 (this nextest supports version {supported})"
2708 )]
2709 UnsupportedFormatVersion {
2710 path: Utf8PathBuf,
2712 found: PortableRecordingFormatVersion,
2714 supported: PortableRecordingFormatVersion,
2716 incompatibility: PortableRecordingVersionIncompatibility,
2718 },
2719
2720 #[error(
2722 "store format version {found} in `{path}` is incompatible: {incompatibility} \
2723 (this nextest supports version {supported})"
2724 )]
2725 UnsupportedStoreFormatVersion {
2726 path: Utf8PathBuf,
2728 found: StoreFormatVersion,
2730 supported: StoreFormatVersion,
2732 incompatibility: StoreVersionIncompatibility,
2734 },
2735
2736 #[error(
2738 "file `{file_name}` in archive `{path}` is too large \
2739 ({size} bytes, limit is {limit} bytes)"
2740 )]
2741 FileTooLarge {
2742 path: Utf8PathBuf,
2744 file_name: Cow<'static, str>,
2746 size: u64,
2748 limit: u64,
2750 },
2751
2752 #[error("failed to extract `{file_name}` from archive `{archive_path}` to `{output_path}`")]
2754 ExtractFile {
2755 archive_path: Utf8PathBuf,
2757 file_name: &'static str,
2759 output_path: Utf8PathBuf,
2761 #[source]
2763 error: std::io::Error,
2764 },
2765
2766 #[error(
2770 "archive at `{path}` has no manifest and is not a wrapper archive \
2771 (contains {file_count} {}, {zip_count} of which {} in .zip)",
2772 plural::files_str(*file_count),
2773 plural::end_str(*zip_count)
2774 )]
2775 NotAWrapperArchive {
2776 path: Utf8PathBuf,
2778 file_count: usize,
2780 zip_count: usize,
2782 },
2783}
2784
2785#[derive(Debug, Error)]
2789pub enum TestListFromSummaryError {
2790 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
2792 PackageNotFound {
2793 name: String,
2795
2796 package_id: String,
2798 },
2799
2800 #[error("error parsing rust build metadata")]
2802 RustBuildMeta(#[source] RustBuildMetaParseError),
2803}
2804
2805#[cfg(feature = "self-update")]
2806mod self_update_errors {
2807 use super::*;
2808 use crate::update::PrereleaseKind;
2809 use mukti_metadata::ReleaseStatus;
2810 use semver::{Version, VersionReq};
2811
2812 #[derive(Debug, Error)]
2816 #[non_exhaustive]
2817 pub enum UpdateError {
2818 #[error("failed to read release metadata from `{path}`")]
2820 ReadLocalMetadata {
2821 path: Utf8PathBuf,
2823
2824 #[source]
2826 error: std::io::Error,
2827 },
2828
2829 #[error("self-update failed")]
2831 SelfUpdate(#[source] self_update::errors::Error),
2832
2833 #[error("deserializing release metadata failed")]
2835 ReleaseMetadataDe(#[source] serde_json::Error),
2836
2837 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
2839 VersionNotFound {
2840 version: Version,
2842
2843 known: Vec<(Version, ReleaseStatus)>,
2845 },
2846
2847 #[error("no version found matching requirement `{req}`")]
2849 NoMatchForVersionReq {
2850 req: VersionReq,
2852 },
2853
2854 #[error("no stable version found")]
2856 NoStableVersion,
2857
2858 #[error("no version found matching {} channel", kind.description())]
2860 NoVersionForPrereleaseKind {
2861 kind: PrereleaseKind,
2863 },
2864
2865 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
2867 MuktiProjectNotFound {
2868 not_found: String,
2870
2871 known: Vec<String>,
2873 },
2874
2875 #[error(
2877 "for version {version}, no release information found for target `{triple}` \
2878 (known targets: {})",
2879 known_triples.iter().join(", ")
2880 )]
2881 NoTargetData {
2882 version: Version,
2884
2885 triple: String,
2887
2888 known_triples: BTreeSet<String>,
2890 },
2891
2892 #[error("the current executable's path could not be determined")]
2894 CurrentExe(#[source] std::io::Error),
2895
2896 #[error("temporary directory could not be created at `{location}`")]
2898 TempDirCreate {
2899 location: Utf8PathBuf,
2901
2902 #[source]
2904 error: std::io::Error,
2905 },
2906
2907 #[error("temporary archive could not be created at `{archive_path}`")]
2909 TempArchiveCreate {
2910 archive_path: Utf8PathBuf,
2912
2913 #[source]
2915 error: std::io::Error,
2916 },
2917
2918 #[error("error writing to temporary archive at `{archive_path}`")]
2920 TempArchiveWrite {
2921 archive_path: Utf8PathBuf,
2923
2924 #[source]
2926 error: std::io::Error,
2927 },
2928
2929 #[error("error reading from temporary archive at `{archive_path}`")]
2931 TempArchiveRead {
2932 archive_path: Utf8PathBuf,
2934
2935 #[source]
2937 error: std::io::Error,
2938 },
2939
2940 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
2942 ChecksumMismatch {
2943 expected: String,
2945
2946 actual: String,
2948 },
2949
2950 #[error("error renaming `{source}` to `{dest}`")]
2952 FsRename {
2953 source: Utf8PathBuf,
2955
2956 dest: Utf8PathBuf,
2958
2959 #[source]
2961 error: std::io::Error,
2962 },
2963
2964 #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
2966 SelfSetup(#[source] std::io::Error),
2967 }
2968
2969 fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
2970 use std::fmt::Write;
2971
2972 const DISPLAY_COUNT: usize = 4;
2974
2975 let display_versions: Vec<_> = versions
2976 .iter()
2977 .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
2978 .map(|(v, _)| v.to_string())
2979 .take(DISPLAY_COUNT)
2980 .collect();
2981 let mut display_str = display_versions.join(", ");
2982 if versions.len() > display_versions.len() {
2983 write!(
2984 display_str,
2985 " and {} others",
2986 versions.len() - display_versions.len()
2987 )
2988 .unwrap();
2989 }
2990
2991 display_str
2992 }
2993
2994 #[derive(Debug, Error)]
2996 pub enum UpdateVersionParseError {
2997 #[error("version string is empty")]
2999 EmptyString,
3000
3001 #[error(
3003 "`{input}` is not a valid semver requirement\n\
3004 (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
3005 )]
3006 InvalidVersionReq {
3007 input: String,
3009
3010 #[source]
3012 error: semver::Error,
3013 },
3014
3015 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3017 InvalidVersion {
3018 input: String,
3020
3021 #[source]
3023 error: semver::Error,
3024 },
3025 }
3026
3027 fn extra_semver_output(input: &str) -> String {
3028 if input.parse::<VersionReq>().is_ok() {
3031 format!(
3032 "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
3033 )
3034 } else {
3035 "".to_owned()
3036 }
3037 }
3038}
3039
3040#[cfg(feature = "self-update")]
3041pub use self_update_errors::*;
3042
3043#[cfg(test)]
3044mod tests {
3045 use super::*;
3046
3047 #[test]
3048 fn display_error_chain() {
3049 let err1 = StringError::new("err1", None);
3050
3051 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
3052
3053 let err2 = StringError::new("err2", Some(err1));
3054 let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
3055
3056 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
3057 err3
3058 err3 line 2
3059 caused by:
3060 - err2
3061 - err1
3062 ");
3063 }
3064
3065 #[test]
3066 fn display_error_list() {
3067 let err1 = StringError::new("err1", None);
3068
3069 let error_list =
3070 ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
3071 .expect(">= 1 error");
3072 insta::assert_snapshot!(format!("{}", error_list), @"err1");
3073 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
3074
3075 let err2 = StringError::new("err2", Some(err1));
3076 let err3 = StringError::new("err3", Some(err2));
3077
3078 let error_list =
3079 ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
3080 .expect(">= 1 error");
3081 insta::assert_snapshot!(format!("{}", error_list), @"err3");
3082 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3083 err3
3084 caused by:
3085 - err2
3086 - err1
3087 ");
3088
3089 let err4 = StringError::new("err4", None);
3090 let err5 = StringError::new("err5", Some(err4));
3091 let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
3092
3093 let error_list = ErrorList::<StringError>::new(
3094 "waiting for the heat death of the universe",
3095 vec![err3, err6],
3096 )
3097 .expect(">= 1 error");
3098
3099 insta::assert_snapshot!(format!("{}", error_list), @"
3100 2 errors occurred waiting for the heat death of the universe:
3101 * err3
3102 caused by:
3103 - err2
3104 - err1
3105 * err6
3106 err6 line 2
3107 caused by:
3108 - err5
3109 - err4
3110 ");
3111 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3112 2 errors occurred waiting for the heat death of the universe:
3113 * err3
3114 caused by:
3115 - err2
3116 - err1
3117 * err6
3118 err6 line 2
3119 caused by:
3120 - err5
3121 - err4
3122 ");
3123 }
3124
3125 #[derive(Clone, Debug, Error)]
3126 struct StringError {
3127 message: String,
3128 #[source]
3129 source: Option<Box<StringError>>,
3130 }
3131
3132 impl StringError {
3133 fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
3134 Self {
3135 message: message.into(),
3136 source: source.map(Box::new),
3137 }
3138 }
3139 }
3140
3141 impl fmt::Display for StringError {
3142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3143 write!(f, "{}", self.message)
3144 }
3145 }
3146}