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 eazip::CompressionMethod;
27use etcetera::HomeDirError;
28use itertools::{Either, Itertools};
29use nextest_filtering::errors::FiltersetParseErrors;
30use nextest_metadata::RustBinaryId;
31use quick_junit::ReportUuid;
32use serde::{Deserialize, Serialize};
33use smol_str::SmolStr;
34use std::{
35 borrow::Cow,
36 collections::BTreeSet,
37 env::JoinPathsError,
38 fmt::{self, Write as _},
39 path::PathBuf,
40 process::ExitStatus,
41 sync::Arc,
42};
43use target_spec_miette::IntoMietteDiagnostic;
44use thiserror::Error;
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 TestFilterBuildError {
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)]
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 writing to path `{path}` in store")]
2174 Write {
2175 path: Utf8PathBuf,
2177
2178 #[source]
2180 error: std::io::Error,
2181 },
2182
2183 #[error("error compressing data")]
2185 Compress {
2186 #[source]
2188 error: std::io::Error,
2189 },
2190
2191 #[error("error finalizing store")]
2193 Finish {
2194 #[source]
2196 error: std::io::Error,
2197 },
2198
2199 #[error("error flushing store")]
2201 Flush {
2202 #[source]
2204 error: std::io::Error,
2205 },
2206}
2207
2208#[derive(Debug, Error)]
2210pub enum RecordReporterError {
2211 #[error(transparent)]
2213 RunStore(RunStoreError),
2214
2215 #[error("record writer thread panicked: {message}")]
2217 WriterPanic {
2218 message: String,
2220 },
2221}
2222
2223#[derive(Debug, Error)]
2225pub enum StateDirError {
2226 #[error("could not determine platform base directory strategy")]
2230 BaseDirStrategy(#[source] HomeDirError),
2231
2232 #[error("platform state directory is not valid UTF-8: {path:?}")]
2234 StateDirNotUtf8 {
2235 path: PathBuf,
2237 },
2238
2239 #[error("could not canonicalize workspace path `{workspace_root}`")]
2241 Canonicalize {
2242 workspace_root: Utf8PathBuf,
2244 #[source]
2246 error: std::io::Error,
2247 },
2248}
2249
2250#[derive(Debug, Error)]
2252pub enum RecordSetupError {
2253 #[error("could not determine platform state directory for recording")]
2255 StateDirNotFound(#[source] StateDirError),
2256
2257 #[error("failed to create run store")]
2259 StoreCreate(#[source] RunStoreError),
2260
2261 #[error("failed to lock run store")]
2263 StoreLock(#[source] RunStoreError),
2264
2265 #[error("failed to create run recorder")]
2267 RecorderCreate(#[source] RunStoreError),
2268}
2269
2270impl RecordSetupError {
2271 pub fn disabled_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
2278 match self {
2279 RecordSetupError::RecorderCreate(err @ RunStoreError::FormatVersionTooNew { .. }) => {
2280 Some(err)
2281 }
2282 _ => None,
2283 }
2284 }
2285}
2286
2287#[derive(Debug, Error)]
2289pub enum RecordPruneError {
2290 #[error("error deleting run `{run_id}` at `{path}`")]
2292 DeleteRun {
2293 run_id: ReportUuid,
2295
2296 path: Utf8PathBuf,
2298
2299 #[source]
2301 error: std::io::Error,
2302 },
2303
2304 #[error("error calculating size of `{path}`")]
2306 CalculateSize {
2307 path: Utf8PathBuf,
2309
2310 #[source]
2312 error: std::io::Error,
2313 },
2314
2315 #[error("error deleting orphaned directory `{path}`")]
2317 DeleteOrphan {
2318 path: Utf8PathBuf,
2320
2321 #[source]
2323 error: std::io::Error,
2324 },
2325
2326 #[error("error reading runs directory `{path}`")]
2328 ReadRunsDir {
2329 path: Utf8PathBuf,
2331
2332 #[source]
2334 error: std::io::Error,
2335 },
2336
2337 #[error("error reading directory entry in `{dir}`")]
2339 ReadDirEntry {
2340 dir: Utf8PathBuf,
2342
2343 #[source]
2345 error: std::io::Error,
2346 },
2347
2348 #[error("error reading file type for `{path}`")]
2350 ReadFileType {
2351 path: Utf8PathBuf,
2353
2354 #[source]
2356 error: std::io::Error,
2357 },
2358}
2359
2360#[derive(Clone, Debug, PartialEq, Eq, Error)]
2365#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2366pub struct InvalidRunIdSelector {
2367 pub input: String,
2369}
2370
2371#[derive(Clone, Debug, PartialEq, Eq, Error)]
2377#[error(
2378 "invalid run ID selector `{input}`: expected `latest`, hex digits, \
2379 or a file path (ending in `.zip` or containing path separators)"
2380)]
2381pub struct InvalidRunIdOrRecordingSelector {
2382 pub input: String,
2384}
2385
2386#[derive(Debug, Error)]
2388pub enum RunIdResolutionError {
2389 #[error("no recorded run found matching `{prefix}`")]
2391 NotFound {
2392 prefix: String,
2394 },
2395
2396 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2398 Ambiguous {
2399 prefix: String,
2401
2402 count: usize,
2404
2405 candidates: Vec<RecordedRunInfo>,
2407
2408 run_id_index: RunIdIndex,
2410 },
2411
2412 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2414 InvalidPrefix {
2415 prefix: String,
2417 },
2418
2419 #[error("no recorded runs exist")]
2421 NoRuns,
2422}
2423
2424#[derive(Debug, Error)]
2426pub enum RecordReadError {
2427 #[error("run not found at `{path}`")]
2429 RunNotFound {
2430 path: Utf8PathBuf,
2432 },
2433
2434 #[error("error opening archive at `{path}`")]
2436 OpenArchive {
2437 path: Utf8PathBuf,
2439
2440 #[source]
2442 error: std::io::Error,
2443 },
2444
2445 #[error("error parsing archive at `{path}`")]
2447 ParseArchive {
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: std::io::Error,
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: std::io::Error,
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] std::io::Error),
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: std::io::Error,
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 "for portable recording `{archive_path}`, the inner archive is stored \
2774 with {:?} compression -- it must be stored uncompressed",
2775 compression
2776 )]
2777 CompressedInnerArchive {
2778 archive_path: Utf8PathBuf,
2780 compression: CompressionMethod,
2782 },
2783
2784 #[error(
2788 "archive at `{path}` has no manifest and is not a wrapper archive \
2789 (contains {file_count} {}, {zip_count} of which {} in .zip)",
2790 plural::files_str(*file_count),
2791 plural::end_str(*zip_count)
2792 )]
2793 NotAWrapperArchive {
2794 path: Utf8PathBuf,
2796 file_count: usize,
2798 zip_count: usize,
2800 },
2801
2802 #[error("unexpected I/O error while probing seekability of `{path}`")]
2809 SeekProbe {
2810 path: Utf8PathBuf,
2812 #[source]
2814 error: std::io::Error,
2815 },
2816
2817 #[error("failed to spool non-seekable input `{path}` to a temporary file")]
2823 SpoolTempFile {
2824 path: Utf8PathBuf,
2826 #[source]
2828 error: std::io::Error,
2829 },
2830
2831 #[error(
2833 "recording at `{path}` exceeds the spool size limit \
2834 ({}); use a file path instead of process substitution",
2835 SizeDisplay(.limit.0)
2836 )]
2837 SpoolTooLarge {
2838 path: Utf8PathBuf,
2840 limit: ByteSize,
2842 },
2843}
2844
2845#[derive(Debug, Error)]
2849pub enum TestListFromSummaryError {
2850 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
2852 PackageNotFound {
2853 name: String,
2855
2856 package_id: String,
2858 },
2859
2860 #[error("error parsing rust build metadata")]
2862 RustBuildMeta(#[source] RustBuildMetaParseError),
2863}
2864
2865#[cfg(feature = "self-update")]
2866mod self_update_errors {
2867 use super::*;
2868 use crate::update::PrereleaseKind;
2869 use mukti_metadata::ReleaseStatus;
2870 use semver::{Version, VersionReq};
2871
2872 #[derive(Debug, Error)]
2876 #[non_exhaustive]
2877 pub enum UpdateError {
2878 #[error("failed to read release metadata from `{path}`")]
2880 ReadLocalMetadata {
2881 path: Utf8PathBuf,
2883
2884 #[source]
2886 error: std::io::Error,
2887 },
2888
2889 #[error("self-update failed")]
2891 SelfUpdate(#[source] self_update::errors::Error),
2892
2893 #[error("error performing HTTP request")]
2895 Http(#[source] ureq::Error),
2896
2897 #[error("error reading HTTP response body")]
2899 HttpBody(#[source] std::io::Error),
2900
2901 #[error("Content-Length header present but could not be parsed as an integer: {value:?}")]
2904 ContentLengthInvalid {
2905 value: String,
2907 },
2908
2909 #[error("content length mismatch: expected {expected} bytes, received {actual} bytes")]
2912 ContentLengthMismatch {
2913 expected: u64,
2915 actual: u64,
2917 },
2918
2919 #[error("deserializing release metadata failed")]
2921 ReleaseMetadataDe(#[source] serde_json::Error),
2922
2923 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
2925 VersionNotFound {
2926 version: Version,
2928
2929 known: Vec<(Version, ReleaseStatus)>,
2931 },
2932
2933 #[error("no version found matching requirement `{req}`")]
2935 NoMatchForVersionReq {
2936 req: VersionReq,
2938 },
2939
2940 #[error("no stable version found")]
2942 NoStableVersion,
2943
2944 #[error("no version found matching {} channel", kind.description())]
2946 NoVersionForPrereleaseKind {
2947 kind: PrereleaseKind,
2949 },
2950
2951 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
2953 MuktiProjectNotFound {
2954 not_found: String,
2956
2957 known: Vec<String>,
2959 },
2960
2961 #[error(
2963 "for version {version}, no release information found for target `{triple}` \
2964 (known targets: {})",
2965 known_triples.iter().join(", ")
2966 )]
2967 NoTargetData {
2968 version: Version,
2970
2971 triple: String,
2973
2974 known_triples: BTreeSet<String>,
2976 },
2977
2978 #[error("the current executable's path could not be determined")]
2980 CurrentExe(#[source] std::io::Error),
2981
2982 #[error("temporary directory could not be created at `{location}`")]
2984 TempDirCreate {
2985 location: Utf8PathBuf,
2987
2988 #[source]
2990 error: std::io::Error,
2991 },
2992
2993 #[error("temporary archive could not be created at `{archive_path}`")]
2995 TempArchiveCreate {
2996 archive_path: Utf8PathBuf,
2998
2999 #[source]
3001 error: std::io::Error,
3002 },
3003
3004 #[error("error writing to temporary archive at `{archive_path}`")]
3006 TempArchiveWrite {
3007 archive_path: Utf8PathBuf,
3009
3010 #[source]
3012 error: std::io::Error,
3013 },
3014
3015 #[error("error reading from temporary archive at `{archive_path}`")]
3017 TempArchiveRead {
3018 archive_path: Utf8PathBuf,
3020
3021 #[source]
3023 error: std::io::Error,
3024 },
3025
3026 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
3028 ChecksumMismatch {
3029 expected: String,
3031
3032 actual: String,
3034 },
3035
3036 #[error("error renaming `{source}` to `{dest}`")]
3038 FsRename {
3039 source: Utf8PathBuf,
3041
3042 dest: Utf8PathBuf,
3044
3045 #[source]
3047 error: std::io::Error,
3048 },
3049
3050 #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
3052 SelfSetup(#[source] std::io::Error),
3053 }
3054
3055 fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
3056 use std::fmt::Write;
3057
3058 const DISPLAY_COUNT: usize = 4;
3060
3061 let display_versions: Vec<_> = versions
3062 .iter()
3063 .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
3064 .map(|(v, _)| v.to_string())
3065 .take(DISPLAY_COUNT)
3066 .collect();
3067 let mut display_str = display_versions.join(", ");
3068 if versions.len() > display_versions.len() {
3069 write!(
3070 display_str,
3071 " and {} others",
3072 versions.len() - display_versions.len()
3073 )
3074 .unwrap();
3075 }
3076
3077 display_str
3078 }
3079
3080 #[derive(Debug, Error)]
3082 pub enum UpdateVersionParseError {
3083 #[error("version string is empty")]
3085 EmptyString,
3086
3087 #[error(
3089 "`{input}` is not a valid semver requirement\n\
3090 (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
3091 )]
3092 InvalidVersionReq {
3093 input: String,
3095
3096 #[source]
3098 error: semver::Error,
3099 },
3100
3101 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
3103 InvalidVersion {
3104 input: String,
3106
3107 #[source]
3109 error: semver::Error,
3110 },
3111 }
3112
3113 fn extra_semver_output(input: &str) -> String {
3114 if input.parse::<VersionReq>().is_ok() {
3117 format!(
3118 "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
3119 )
3120 } else {
3121 "".to_owned()
3122 }
3123 }
3124}
3125
3126#[cfg(feature = "self-update")]
3127pub use self_update_errors::*;
3128
3129#[cfg(test)]
3130mod tests {
3131 use super::*;
3132
3133 #[test]
3134 fn display_error_chain() {
3135 let err1 = StringError::new("err1", None);
3136
3137 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
3138
3139 let err2 = StringError::new("err2", Some(err1));
3140 let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
3141
3142 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
3143 err3
3144 err3 line 2
3145 caused by:
3146 - err2
3147 - err1
3148 ");
3149 }
3150
3151 #[test]
3152 fn display_error_list() {
3153 let err1 = StringError::new("err1", None);
3154
3155 let error_list =
3156 ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
3157 .expect(">= 1 error");
3158 insta::assert_snapshot!(format!("{}", error_list), @"err1");
3159 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
3160
3161 let err2 = StringError::new("err2", Some(err1));
3162 let err3 = StringError::new("err3", Some(err2));
3163
3164 let error_list =
3165 ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
3166 .expect(">= 1 error");
3167 insta::assert_snapshot!(format!("{}", error_list), @"err3");
3168 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3169 err3
3170 caused by:
3171 - err2
3172 - err1
3173 ");
3174
3175 let err4 = StringError::new("err4", None);
3176 let err5 = StringError::new("err5", Some(err4));
3177 let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
3178
3179 let error_list = ErrorList::<StringError>::new(
3180 "waiting for the heat death of the universe",
3181 vec![err3, err6],
3182 )
3183 .expect(">= 1 error");
3184
3185 insta::assert_snapshot!(format!("{}", error_list), @"
3186 2 errors occurred waiting for the heat death of the universe:
3187 * err3
3188 caused by:
3189 - err2
3190 - err1
3191 * err6
3192 err6 line 2
3193 caused by:
3194 - err5
3195 - err4
3196 ");
3197 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
3198 2 errors occurred waiting for the heat death of the universe:
3199 * err3
3200 caused by:
3201 - err2
3202 - err1
3203 * err6
3204 err6 line 2
3205 caused by:
3206 - err5
3207 - err4
3208 ");
3209 }
3210
3211 #[derive(Clone, Debug, Error)]
3212 struct StringError {
3213 message: String,
3214 #[source]
3215 source: Option<Box<StringError>>,
3216 }
3217
3218 impl StringError {
3219 fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
3220 Self {
3221 message: message.into(),
3222 source: source.map(Box::new),
3223 }
3224 }
3225 }
3226
3227 impl fmt::Display for StringError {
3228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3229 write!(f, "{}", self.message)
3230 }
3231 }
3232}