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},
14 indenter::{DisplayIndented, indented},
15 record::{RecordedRunInfo, RunIdIndex},
16 redact::Redactor,
17 reuse_build::{ArchiveFormat, ArchiveStep},
18 target_runner::PlatformRunnerSource,
19};
20use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
21use config::ConfigError;
22use itertools::{Either, Itertools};
23use nextest_filtering::errors::FiltersetParseErrors;
24use nextest_metadata::RustBinaryId;
25use quick_junit::ReportUuid;
26use serde::{Deserialize, Serialize};
27use smol_str::SmolStr;
28use std::{
29 borrow::Cow,
30 collections::BTreeSet,
31 env::JoinPathsError,
32 fmt::{self, Write as _},
33 path::PathBuf,
34 process::ExitStatus,
35 sync::Arc,
36};
37use target_spec_miette::IntoMietteDiagnostic;
38use thiserror::Error;
39
40#[derive(Debug, Error)]
42#[error(
43 "failed to parse nextest config at `{config_file}`{}",
44 provided_by_tool(tool.as_ref())
45)]
46#[non_exhaustive]
47pub struct ConfigParseError {
48 config_file: Utf8PathBuf,
49 tool: Option<ToolName>,
50 #[source]
51 kind: ConfigParseErrorKind,
52}
53
54impl ConfigParseError {
55 pub(crate) fn new(
56 config_file: impl Into<Utf8PathBuf>,
57 tool: Option<&ToolName>,
58 kind: ConfigParseErrorKind,
59 ) -> Self {
60 Self {
61 config_file: config_file.into(),
62 tool: tool.cloned(),
63 kind,
64 }
65 }
66
67 pub fn config_file(&self) -> &Utf8Path {
69 &self.config_file
70 }
71
72 pub fn tool(&self) -> Option<&ToolName> {
74 self.tool.as_ref()
75 }
76
77 pub fn kind(&self) -> &ConfigParseErrorKind {
79 &self.kind
80 }
81}
82
83pub fn provided_by_tool(tool: Option<&ToolName>) -> String {
85 match tool {
86 Some(tool) => format!(" provided by tool `{tool}`"),
87 None => String::new(),
88 }
89}
90
91#[derive(Debug, Error)]
95#[non_exhaustive]
96pub enum ConfigParseErrorKind {
97 #[error(transparent)]
99 BuildError(Box<ConfigError>),
100 #[error(transparent)]
102 TomlParseError(Box<toml::de::Error>),
103 #[error(transparent)]
104 DeserializeError(Box<serde_path_to_error::Error<ConfigError>>),
106 #[error(transparent)]
108 VersionOnlyReadError(std::io::Error),
109 #[error(transparent)]
111 VersionOnlyDeserializeError(Box<serde_path_to_error::Error<toml::de::Error>>),
112 #[error("error parsing compiled data (destructure this variant for more details)")]
114 CompileErrors(Vec<ConfigCompileError>),
115 #[error("invalid test groups defined: {}\n(test groups cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
117 InvalidTestGroupsDefined(BTreeSet<CustomTestGroup>),
118 #[error(
120 "invalid test groups defined by tool: {}\n(test groups must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
121 InvalidTestGroupsDefinedByTool(BTreeSet<CustomTestGroup>),
122 #[error("unknown test groups specified by config (destructure this variant for more details)")]
124 UnknownTestGroups {
125 errors: Vec<UnknownTestGroupError>,
127
128 known_groups: BTreeSet<TestGroup>,
130 },
131 #[error(
133 "both `[script.*]` and `[scripts.*]` defined\n\
134 (hint: [script.*] will be removed in the future: switch to [scripts.setup.*])"
135 )]
136 BothScriptAndScriptsDefined,
137 #[error("invalid config scripts defined: {}\n(config scripts cannot start with '@tool:' unless specified by a tool)", .0.iter().join(", "))]
139 InvalidConfigScriptsDefined(BTreeSet<ScriptId>),
140 #[error(
142 "invalid config scripts defined by tool: {}\n(config scripts must start with '@tool:<tool-name>:')", .0.iter().join(", "))]
143 InvalidConfigScriptsDefinedByTool(BTreeSet<ScriptId>),
144 #[error(
146 "config script names used more than once: {}\n\
147 (config script names must be unique across all script types)", .0.iter().join(", ")
148 )]
149 DuplicateConfigScriptNames(BTreeSet<ScriptId>),
150 #[error(
152 "errors in profile-specific config scripts (destructure this variant for more details)"
153 )]
154 ProfileScriptErrors {
155 errors: Box<ProfileScriptErrors>,
157
158 known_scripts: BTreeSet<ScriptId>,
160 },
161 #[error("unknown experimental features defined (destructure this variant for more details)")]
163 UnknownExperimentalFeatures {
164 unknown: BTreeSet<String>,
166
167 known: BTreeSet<ConfigExperimental>,
169 },
170 #[error(
174 "tool config file specifies experimental features `{}` \
175 -- only repository config files can do so",
176 .features.iter().join(", "),
177 )]
178 ExperimentalFeaturesInToolConfig {
179 features: BTreeSet<String>,
181 },
182 #[error("experimental features used but not enabled: {}", .missing_features.iter().join(", "))]
184 ExperimentalFeaturesNotEnabled {
185 missing_features: BTreeSet<ConfigExperimental>,
187 },
188 #[error("inheritance error(s) detected: {}", .0.iter().join(", "))]
190 InheritanceErrors(Vec<InheritsError>),
191}
192
193#[derive(Debug)]
196#[non_exhaustive]
197pub struct ConfigCompileError {
198 pub profile_name: String,
200
201 pub section: ConfigCompileSection,
203
204 pub kind: ConfigCompileErrorKind,
206}
207
208#[derive(Debug)]
211pub enum ConfigCompileSection {
212 DefaultFilter,
214
215 Override(usize),
217
218 Script(usize),
220}
221
222#[derive(Debug)]
224#[non_exhaustive]
225pub enum ConfigCompileErrorKind {
226 ConstraintsNotSpecified {
228 default_filter_specified: bool,
233 },
234
235 FilterAndDefaultFilterSpecified,
239
240 Parse {
242 host_parse_error: Option<target_spec::Error>,
244
245 target_parse_error: Option<target_spec::Error>,
247
248 filter_parse_errors: Vec<FiltersetParseErrors>,
250 },
251}
252
253impl ConfigCompileErrorKind {
254 pub fn reports(&self) -> impl Iterator<Item = miette::Report> + '_ {
256 match self {
257 Self::ConstraintsNotSpecified {
258 default_filter_specified,
259 } => {
260 let message = if *default_filter_specified {
261 "for override with `default-filter`, `platform` must also be specified"
262 } else {
263 "at least one of `platform` and `filter` must be specified"
264 };
265 Either::Left(std::iter::once(miette::Report::msg(message)))
266 }
267 Self::FilterAndDefaultFilterSpecified => {
268 Either::Left(std::iter::once(miette::Report::msg(
269 "at most one of `filter` and `default-filter` must be specified",
270 )))
271 }
272 Self::Parse {
273 host_parse_error,
274 target_parse_error,
275 filter_parse_errors,
276 } => {
277 let host_parse_report = host_parse_error
278 .as_ref()
279 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
280 let target_parse_report = target_parse_error
281 .as_ref()
282 .map(|error| miette::Report::new_boxed(error.clone().into_diagnostic()));
283 let filter_parse_reports =
284 filter_parse_errors.iter().flat_map(|filter_parse_errors| {
285 filter_parse_errors.errors.iter().map(|single_error| {
286 miette::Report::new(single_error.clone())
287 .with_source_code(filter_parse_errors.input.to_owned())
288 })
289 });
290
291 Either::Right(
292 host_parse_report
293 .into_iter()
294 .chain(target_parse_report)
295 .chain(filter_parse_reports),
296 )
297 }
298 }
299 }
300}
301
302#[derive(Clone, Debug, Error)]
304#[error("test priority ({priority}) out of range: must be between -100 and 100, both inclusive")]
305pub struct TestPriorityOutOfRange {
306 pub priority: i8,
308}
309
310#[derive(Clone, Debug, Error)]
312pub enum ChildStartError {
313 #[error("error creating temporary path for setup script")]
315 TempPath(#[source] Arc<std::io::Error>),
316
317 #[error("error spawning child process")]
319 Spawn(#[source] Arc<std::io::Error>),
320}
321
322#[derive(Clone, Debug, Error)]
324pub enum SetupScriptOutputError {
325 #[error("error opening environment file `{path}`")]
327 EnvFileOpen {
328 path: Utf8PathBuf,
330
331 #[source]
333 error: Arc<std::io::Error>,
334 },
335
336 #[error("error reading environment file `{path}`")]
338 EnvFileRead {
339 path: Utf8PathBuf,
341
342 #[source]
344 error: Arc<std::io::Error>,
345 },
346
347 #[error("line `{line}` in environment file `{path}` not in KEY=VALUE format")]
349 EnvFileParse {
350 path: Utf8PathBuf,
352 line: String,
354 },
355
356 #[error("key `{key}` begins with `NEXTEST`, which is reserved for internal use")]
358 EnvFileReservedKey {
359 key: String,
361 },
362}
363
364#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
369pub struct ErrorList<T> {
370 description: Cow<'static, str>,
372 inner: Vec<T>,
374}
375
376impl<T: std::error::Error> ErrorList<T> {
377 pub(crate) fn new<U>(description: &'static str, errors: Vec<U>) -> Option<Self>
378 where
379 T: From<U>,
380 {
381 if errors.is_empty() {
382 None
383 } else {
384 Some(Self {
385 description: Cow::Borrowed(description),
386 inner: errors.into_iter().map(T::from).collect(),
387 })
388 }
389 }
390
391 pub(crate) fn short_message(&self) -> String {
393 let string = self.to_string();
394 match string.lines().next() {
395 Some(first_line) => first_line.trim_end_matches(':').to_string(),
397 None => String::new(),
398 }
399 }
400
401 pub fn description(&self) -> &str {
403 &self.description
404 }
405
406 pub fn iter(&self) -> impl Iterator<Item = &T> {
408 self.inner.iter()
409 }
410
411 pub fn map<U, F>(self, f: F) -> ErrorList<U>
413 where
414 U: std::error::Error,
415 F: FnMut(T) -> U,
416 {
417 ErrorList {
418 description: self.description,
419 inner: self.inner.into_iter().map(f).collect(),
420 }
421 }
422}
423
424impl<T: std::error::Error> IntoIterator for ErrorList<T> {
425 type Item = T;
426 type IntoIter = std::vec::IntoIter<T>;
427
428 fn into_iter(self) -> Self::IntoIter {
429 self.inner.into_iter()
430 }
431}
432
433impl<T: std::error::Error> fmt::Display for ErrorList<T> {
434 fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
435 if self.inner.len() == 1 {
437 return write!(f, "{}", self.inner[0]);
438 }
439
440 writeln!(
442 f,
443 "{} errors occurred {}:",
444 self.inner.len(),
445 self.description,
446 )?;
447 for error in &self.inner {
448 let mut indent = indented(f).with_str(" ").skip_initial();
449 writeln!(indent, "* {}", DisplayErrorChain::new(error))?;
450 f = indent.into_inner();
451 }
452 Ok(())
453 }
454}
455
456#[cfg(test)]
457impl<T: proptest::arbitrary::Arbitrary + std::fmt::Debug + 'static> proptest::arbitrary::Arbitrary
458 for ErrorList<T>
459{
460 type Parameters = ();
461 type Strategy = proptest::strategy::BoxedStrategy<Self>;
462
463 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
464 use proptest::prelude::*;
465
466 proptest::collection::vec(any::<T>(), 1..=5)
468 .prop_map(|inner| ErrorList {
469 description: Cow::Borrowed("test errors"),
470 inner,
471 })
472 .boxed()
473 }
474}
475
476impl<T: std::error::Error> std::error::Error for ErrorList<T> {
477 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
478 if self.inner.len() == 1 {
479 self.inner[0].source()
480 } else {
481 None
484 }
485 }
486}
487
488pub struct DisplayErrorChain<E> {
493 error: E,
494 initial_indent: &'static str,
495}
496
497impl<E: std::error::Error> DisplayErrorChain<E> {
498 pub fn new(error: E) -> Self {
500 Self {
501 error,
502 initial_indent: "",
503 }
504 }
505
506 pub fn new_with_initial_indent(initial_indent: &'static str, error: E) -> Self {
508 Self {
509 error,
510 initial_indent,
511 }
512 }
513}
514
515impl<E> fmt::Display for DisplayErrorChain<E>
516where
517 E: std::error::Error,
518{
519 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520 let mut writer = indented(f).with_str(self.initial_indent);
521 write!(writer, "{}", self.error)?;
522
523 let Some(mut cause) = self.error.source() else {
524 return Ok(());
525 };
526
527 write!(writer, "\n caused by:")?;
528
529 loop {
530 writeln!(writer)?;
531 let mut indent = indented(&mut writer).with_str(" ").skip_initial();
533 write!(indent, " - {cause}")?;
534
535 let Some(next_cause) = cause.source() else {
536 break Ok(());
537 };
538
539 cause = next_cause;
540 }
541 }
542}
543
544#[derive(Clone, Debug, Error)]
546pub enum ChildError {
547 #[error(transparent)]
549 Fd(#[from] ChildFdError),
550
551 #[error(transparent)]
553 SetupScriptOutput(#[from] SetupScriptOutputError),
554}
555
556#[derive(Clone, Debug, Error)]
558pub enum ChildFdError {
559 #[error("error reading standard output")]
561 ReadStdout(#[source] Arc<std::io::Error>),
562
563 #[error("error reading standard error")]
565 ReadStderr(#[source] Arc<std::io::Error>),
566
567 #[error("error reading combined stream")]
569 ReadCombined(#[source] Arc<std::io::Error>),
570
571 #[error("error waiting for child process to exit")]
573 Wait(#[source] Arc<std::io::Error>),
574}
575
576#[derive(Clone, Debug, Eq, PartialEq)]
578#[non_exhaustive]
579pub struct UnknownTestGroupError {
580 pub profile_name: String,
582
583 pub name: TestGroup,
585}
586
587#[derive(Clone, Debug, Eq, PartialEq)]
590pub struct ProfileUnknownScriptError {
591 pub profile_name: String,
593
594 pub name: ScriptId,
596}
597
598#[derive(Clone, Debug, Eq, PartialEq)]
601pub struct ProfileWrongConfigScriptTypeError {
602 pub profile_name: String,
604
605 pub name: ScriptId,
607
608 pub attempted: ProfileScriptType,
610
611 pub actual: ScriptType,
613}
614
615#[derive(Clone, Debug, Eq, PartialEq)]
618pub struct ProfileListScriptUsesRunFiltersError {
619 pub profile_name: String,
621
622 pub name: ScriptId,
624
625 pub script_type: ProfileScriptType,
627
628 pub filters: BTreeSet<String>,
630}
631
632#[derive(Clone, Debug, Default)]
634pub struct ProfileScriptErrors {
635 pub unknown_scripts: Vec<ProfileUnknownScriptError>,
637
638 pub wrong_script_types: Vec<ProfileWrongConfigScriptTypeError>,
640
641 pub list_scripts_using_run_filters: Vec<ProfileListScriptUsesRunFiltersError>,
643}
644
645impl ProfileScriptErrors {
646 pub fn is_empty(&self) -> bool {
648 self.unknown_scripts.is_empty()
649 && self.wrong_script_types.is_empty()
650 && self.list_scripts_using_run_filters.is_empty()
651 }
652}
653
654#[derive(Clone, Debug, Error)]
656#[error("profile `{profile}` not found (known profiles: {})", .all_profiles.join(", "))]
657pub struct ProfileNotFound {
658 profile: String,
659 all_profiles: Vec<String>,
660}
661
662impl ProfileNotFound {
663 pub(crate) fn new(
664 profile: impl Into<String>,
665 all_profiles: impl IntoIterator<Item = impl Into<String>>,
666 ) -> Self {
667 let mut all_profiles: Vec<_> = all_profiles.into_iter().map(|s| s.into()).collect();
668 all_profiles.sort_unstable();
669 Self {
670 profile: profile.into(),
671 all_profiles,
672 }
673 }
674}
675
676#[derive(Clone, Debug, Error, Eq, PartialEq)]
678pub enum InvalidIdentifier {
679 #[error("identifier is empty")]
681 Empty,
682
683 #[error("invalid identifier `{0}`")]
685 InvalidXid(SmolStr),
686
687 #[error("tool identifier not of the form \"@tool:tool-name:identifier\": `{0}`")]
689 ToolIdentifierInvalidFormat(SmolStr),
690
691 #[error("tool identifier has empty component: `{0}`")]
693 ToolComponentEmpty(SmolStr),
694
695 #[error("invalid tool identifier `{0}`")]
697 ToolIdentifierInvalidXid(SmolStr),
698}
699
700#[derive(Clone, Debug, Error, Eq, PartialEq)]
702pub enum InvalidToolName {
703 #[error("tool name is empty")]
705 Empty,
706
707 #[error("invalid tool name `{0}`")]
709 InvalidXid(SmolStr),
710
711 #[error("tool name cannot start with \"@tool\": `{0}`")]
713 StartsWithToolPrefix(SmolStr),
714}
715
716#[derive(Clone, Debug, Error)]
718#[error("invalid custom test group name: {0}")]
719pub struct InvalidCustomTestGroupName(pub InvalidIdentifier);
720
721#[derive(Clone, Debug, Error)]
723#[error("invalid configuration script name: {0}")]
724pub struct InvalidConfigScriptName(pub InvalidIdentifier);
725
726#[derive(Clone, Debug, Error, PartialEq, Eq)]
728pub enum ToolConfigFileParseError {
729 #[error(
730 "tool-config-file has invalid format: {input}\n(hint: tool configs must be in the format <tool-name>:<path>)"
731 )]
732 InvalidFormat {
734 input: String,
736 },
737
738 #[error("tool-config-file has invalid tool name: {input}")]
740 InvalidToolName {
741 input: String,
743
744 #[source]
746 error: InvalidToolName,
747 },
748
749 #[error("tool-config-file has empty config file path: {input}")]
751 EmptyConfigFile {
752 input: String,
754 },
755
756 #[error("tool-config-file is not an absolute path: {config_file}")]
758 ConfigFileNotAbsolute {
759 config_file: Utf8PathBuf,
761 },
762}
763
764#[derive(Debug, Error)]
766#[non_exhaustive]
767pub enum UserConfigError {
768 #[error("user config file not found at {path}")]
771 FileNotFound {
772 path: Utf8PathBuf,
774 },
775
776 #[error("failed to read user config at {path}")]
778 Read {
779 path: Utf8PathBuf,
781 #[source]
783 error: std::io::Error,
784 },
785
786 #[error("failed to parse user config at {path}")]
788 Parse {
789 path: Utf8PathBuf,
791 #[source]
793 error: toml::de::Error,
794 },
795
796 #[error("user config path contains non-UTF-8 characters")]
798 NonUtf8Path {
799 #[source]
801 error: FromPathBufError,
802 },
803
804 #[error(
806 "for user config at {path}, failed to compile platform spec in [[overrides]] at index {index}"
807 )]
808 OverridePlatformSpec {
809 path: Utf8PathBuf,
811 index: usize,
813 #[source]
815 error: target_spec::Error,
816 },
817}
818
819#[derive(Clone, Debug, Error)]
821#[error("unrecognized value for max-fail: {reason}")]
822pub struct MaxFailParseError {
823 pub reason: String,
825}
826
827impl MaxFailParseError {
828 pub(crate) fn new(reason: impl Into<String>) -> Self {
829 Self {
830 reason: reason.into(),
831 }
832 }
833}
834
835#[derive(Clone, Debug, Error)]
837#[error(
838 "unrecognized value for stress-count: {input}\n\
839 (hint: expected either a positive integer or \"infinite\")"
840)]
841pub struct StressCountParseError {
842 pub input: String,
844}
845
846impl StressCountParseError {
847 pub(crate) fn new(input: impl Into<String>) -> Self {
848 Self {
849 input: input.into(),
850 }
851 }
852}
853
854#[derive(Clone, Debug, Error)]
856#[non_exhaustive]
857pub enum DebuggerCommandParseError {
858 #[error(transparent)]
860 ShellWordsParse(shell_words::ParseError),
861
862 #[error("debugger command cannot be empty")]
864 EmptyCommand,
865}
866
867#[derive(Clone, Debug, Error)]
869#[non_exhaustive]
870pub enum TracerCommandParseError {
871 #[error(transparent)]
873 ShellWordsParse(shell_words::ParseError),
874
875 #[error("tracer command cannot be empty")]
877 EmptyCommand,
878}
879
880#[derive(Clone, Debug, Error)]
882#[error(
883 "unrecognized value for test-threads: {input}\n(hint: expected either an integer or \"num-cpus\")"
884)]
885pub struct TestThreadsParseError {
886 pub input: String,
888}
889
890impl TestThreadsParseError {
891 pub(crate) fn new(input: impl Into<String>) -> Self {
892 Self {
893 input: input.into(),
894 }
895 }
896}
897
898#[derive(Clone, Debug, Error)]
901pub struct PartitionerBuilderParseError {
902 expected_format: Option<&'static str>,
903 message: Cow<'static, str>,
904}
905
906impl PartitionerBuilderParseError {
907 pub(crate) fn new(
908 expected_format: Option<&'static str>,
909 message: impl Into<Cow<'static, str>>,
910 ) -> Self {
911 Self {
912 expected_format,
913 message: message.into(),
914 }
915 }
916}
917
918impl fmt::Display for PartitionerBuilderParseError {
919 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
920 match self.expected_format {
921 Some(format) => {
922 write!(
923 f,
924 "partition must be in the format \"{}\":\n{}",
925 format, self.message
926 )
927 }
928 None => write!(f, "{}", self.message),
929 }
930 }
931}
932
933#[derive(Clone, Debug, Error)]
936pub enum TestFilterBuilderError {
937 #[error("error constructing test filters")]
939 Construct {
940 #[from]
942 error: aho_corasick::BuildError,
943 },
944}
945
946#[derive(Debug, Error)]
948pub enum PathMapperConstructError {
949 #[error("{kind} `{input}` failed to canonicalize")]
951 Canonicalization {
952 kind: PathMapperConstructKind,
954
955 input: Utf8PathBuf,
957
958 #[source]
960 err: std::io::Error,
961 },
962 #[error("{kind} `{input}` canonicalized to a non-UTF-8 path")]
964 NonUtf8Path {
965 kind: PathMapperConstructKind,
967
968 input: Utf8PathBuf,
970
971 #[source]
973 err: FromPathBufError,
974 },
975 #[error("{kind} `{canonicalized_path}` is not a directory")]
977 NotADirectory {
978 kind: PathMapperConstructKind,
980
981 input: Utf8PathBuf,
983
984 canonicalized_path: Utf8PathBuf,
986 },
987}
988
989impl PathMapperConstructError {
990 pub fn kind(&self) -> PathMapperConstructKind {
992 match self {
993 Self::Canonicalization { kind, .. }
994 | Self::NonUtf8Path { kind, .. }
995 | Self::NotADirectory { kind, .. } => *kind,
996 }
997 }
998
999 pub fn input(&self) -> &Utf8Path {
1001 match self {
1002 Self::Canonicalization { input, .. }
1003 | Self::NonUtf8Path { input, .. }
1004 | Self::NotADirectory { input, .. } => input,
1005 }
1006 }
1007}
1008
1009#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1014pub enum PathMapperConstructKind {
1015 WorkspaceRoot,
1017
1018 TargetDir,
1020}
1021
1022impl fmt::Display for PathMapperConstructKind {
1023 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1024 match self {
1025 Self::WorkspaceRoot => write!(f, "remapped workspace root"),
1026 Self::TargetDir => write!(f, "remapped target directory"),
1027 }
1028 }
1029}
1030
1031#[derive(Debug, Error)]
1033pub enum RustBuildMetaParseError {
1034 #[error("error deserializing platform from build metadata")]
1036 PlatformDeserializeError(#[from] target_spec::Error),
1037
1038 #[error("the host platform could not be determined")]
1040 DetectBuildTargetError(#[source] target_spec::Error),
1041
1042 #[error("unsupported features in the build metadata: {message}")]
1044 Unsupported {
1045 message: String,
1047 },
1048}
1049
1050#[derive(Clone, Debug, thiserror::Error)]
1053#[error("invalid format version: {input}")]
1054pub struct FormatVersionError {
1055 pub input: String,
1057 #[source]
1059 pub error: FormatVersionErrorInner,
1060}
1061
1062#[derive(Clone, Debug, thiserror::Error)]
1064pub enum FormatVersionErrorInner {
1065 #[error("expected format version in form of `{expected}`")]
1067 InvalidFormat {
1068 expected: &'static str,
1070 },
1071 #[error("version component `{which}` could not be parsed as an integer")]
1073 InvalidInteger {
1074 which: &'static str,
1076 #[source]
1078 err: std::num::ParseIntError,
1079 },
1080 #[error("version component `{which}` value {value} is out of range {range:?}")]
1082 InvalidValue {
1083 which: &'static str,
1085 value: u8,
1087 range: std::ops::Range<u8>,
1089 },
1090}
1091
1092#[derive(Debug, Error)]
1095#[non_exhaustive]
1096pub enum FromMessagesError {
1097 #[error("error reading Cargo JSON messages")]
1099 ReadMessages(#[source] std::io::Error),
1100
1101 #[error("error querying package graph")]
1103 PackageGraph(#[source] guppy::Error),
1104
1105 #[error("missing kind for target {binary_name} in package {package_name}")]
1107 MissingTargetKind {
1108 package_name: String,
1110 binary_name: String,
1112 },
1113}
1114
1115#[derive(Debug, Error)]
1117#[non_exhaustive]
1118pub enum CreateTestListError {
1119 #[error(
1121 "for `{binary_id}`, current directory `{cwd}` is not a directory\n\
1122 (hint: ensure project source is available at this location)"
1123 )]
1124 CwdIsNotDir {
1125 binary_id: RustBinaryId,
1127
1128 cwd: Utf8PathBuf,
1130 },
1131
1132 #[error(
1134 "for `{binary_id}`, running command `{}` failed to execute",
1135 shell_words::join(command)
1136 )]
1137 CommandExecFail {
1138 binary_id: RustBinaryId,
1140
1141 command: Vec<String>,
1143
1144 #[source]
1146 error: std::io::Error,
1147 },
1148
1149 #[error(
1151 "for `{binary_id}`, command `{}` {}\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1152 shell_words::join(command),
1153 display_exited_with(*exit_status),
1154 String::from_utf8_lossy(stdout),
1155 String::from_utf8_lossy(stderr),
1156 )]
1157 CommandFail {
1158 binary_id: RustBinaryId,
1160
1161 command: Vec<String>,
1163
1164 exit_status: ExitStatus,
1166
1167 stdout: Vec<u8>,
1169
1170 stderr: Vec<u8>,
1172 },
1173
1174 #[error(
1176 "for `{binary_id}`, command `{}` produced non-UTF-8 output:\n--- stdout:\n{}\n--- stderr:\n{}\n---",
1177 shell_words::join(command),
1178 String::from_utf8_lossy(stdout),
1179 String::from_utf8_lossy(stderr)
1180 )]
1181 CommandNonUtf8 {
1182 binary_id: RustBinaryId,
1184
1185 command: Vec<String>,
1187
1188 stdout: Vec<u8>,
1190
1191 stderr: Vec<u8>,
1193 },
1194
1195 #[error("for `{binary_id}`, {message}\nfull output:\n{full_output}")]
1197 ParseLine {
1198 binary_id: RustBinaryId,
1200
1201 message: Cow<'static, str>,
1203
1204 full_output: String,
1206 },
1207
1208 #[error(
1210 "error joining dynamic library paths for {}: [{}]",
1211 dylib_path_envvar(),
1212 itertools::join(.new_paths, ", ")
1213 )]
1214 DylibJoinPaths {
1215 new_paths: Vec<Utf8PathBuf>,
1217
1218 #[source]
1220 error: JoinPathsError,
1221 },
1222
1223 #[error("error creating Tokio runtime")]
1225 TokioRuntimeCreate(#[source] std::io::Error),
1226}
1227
1228impl CreateTestListError {
1229 pub(crate) fn parse_line(
1230 binary_id: RustBinaryId,
1231 message: impl Into<Cow<'static, str>>,
1232 full_output: impl Into<String>,
1233 ) -> Self {
1234 Self::ParseLine {
1235 binary_id,
1236 message: message.into(),
1237 full_output: full_output.into(),
1238 }
1239 }
1240
1241 pub(crate) fn dylib_join_paths(new_paths: Vec<Utf8PathBuf>, error: JoinPathsError) -> Self {
1242 Self::DylibJoinPaths { new_paths, error }
1243 }
1244}
1245
1246#[derive(Debug, Error)]
1248#[non_exhaustive]
1249pub enum WriteTestListError {
1250 #[error("error writing to output")]
1252 Io(#[source] std::io::Error),
1253
1254 #[error("error serializing to JSON")]
1256 Json(#[source] serde_json::Error),
1257}
1258
1259#[derive(Debug, Error)]
1263pub enum ConfigureHandleInheritanceError {
1264 #[cfg(windows)]
1266 #[error("error configuring handle inheritance")]
1267 WindowsError(#[from] std::io::Error),
1268}
1269
1270#[derive(Debug, Error)]
1272#[non_exhaustive]
1273pub enum TestRunnerBuildError {
1274 #[error("error creating Tokio runtime")]
1276 TokioRuntimeCreate(#[source] std::io::Error),
1277
1278 #[error("error setting up signals")]
1280 SignalHandlerSetupError(#[from] SignalHandlerSetupError),
1281}
1282
1283#[derive(Debug, Error)]
1285pub struct TestRunnerExecuteErrors<E> {
1286 pub report_error: Option<E>,
1288
1289 pub join_errors: Vec<tokio::task::JoinError>,
1292}
1293
1294impl<E: std::error::Error> fmt::Display for TestRunnerExecuteErrors<E> {
1295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1296 if let Some(report_error) = &self.report_error {
1297 write!(f, "error reporting results: {report_error}")?;
1298 }
1299
1300 if !self.join_errors.is_empty() {
1301 if self.report_error.is_some() {
1302 write!(f, "; ")?;
1303 }
1304
1305 write!(f, "errors joining tasks: ")?;
1306
1307 for (i, join_error) in self.join_errors.iter().enumerate() {
1308 if i > 0 {
1309 write!(f, ", ")?;
1310 }
1311
1312 write!(f, "{join_error}")?;
1313 }
1314 }
1315
1316 Ok(())
1317 }
1318}
1319
1320#[derive(Debug, Error)]
1324#[error(
1325 "could not detect archive format from file name `{file_name}` (supported extensions: {})",
1326 supported_extensions()
1327)]
1328pub struct UnknownArchiveFormat {
1329 pub file_name: String,
1331}
1332
1333fn supported_extensions() -> String {
1334 ArchiveFormat::SUPPORTED_FORMATS
1335 .iter()
1336 .map(|(extension, _)| *extension)
1337 .join(", ")
1338}
1339
1340#[derive(Debug, Error)]
1342#[non_exhaustive]
1343pub enum ArchiveCreateError {
1344 #[error("error creating binary list")]
1346 CreateBinaryList(#[source] WriteTestListError),
1347
1348 #[error("extra path `{}` not found", .redactor.redact_path(path))]
1350 MissingExtraPath {
1351 path: Utf8PathBuf,
1353
1354 redactor: Redactor,
1359 },
1360
1361 #[error("while archiving {step}, error writing {} `{path}` to archive", kind_str(*.is_dir))]
1363 InputFileRead {
1364 step: ArchiveStep,
1366
1367 path: Utf8PathBuf,
1369
1370 is_dir: Option<bool>,
1372
1373 #[source]
1375 error: std::io::Error,
1376 },
1377
1378 #[error("error reading directory entry from `{path}")]
1380 DirEntryRead {
1381 path: Utf8PathBuf,
1383
1384 #[source]
1386 error: std::io::Error,
1387 },
1388
1389 #[error("error writing to archive")]
1391 OutputArchiveIo(#[source] std::io::Error),
1392
1393 #[error("error reporting archive status")]
1395 ReporterIo(#[source] std::io::Error),
1396}
1397
1398fn kind_str(is_dir: Option<bool>) -> &'static str {
1399 match is_dir {
1400 Some(true) => "directory",
1401 Some(false) => "file",
1402 None => "path",
1403 }
1404}
1405
1406#[derive(Debug, Error)]
1408pub enum MetadataMaterializeError {
1409 #[error("I/O error reading metadata file `{path}`")]
1411 Read {
1412 path: Utf8PathBuf,
1414
1415 #[source]
1417 error: std::io::Error,
1418 },
1419
1420 #[error("error deserializing metadata file `{path}`")]
1422 Deserialize {
1423 path: Utf8PathBuf,
1425
1426 #[source]
1428 error: serde_json::Error,
1429 },
1430
1431 #[error("error parsing Rust build metadata from `{path}`")]
1433 RustBuildMeta {
1434 path: Utf8PathBuf,
1436
1437 #[source]
1439 error: RustBuildMetaParseError,
1440 },
1441
1442 #[error("error building package graph from `{path}`")]
1444 PackageGraphConstruct {
1445 path: Utf8PathBuf,
1447
1448 #[source]
1450 error: guppy::Error,
1451 },
1452}
1453
1454#[derive(Debug, Error)]
1458#[non_exhaustive]
1459pub enum ArchiveReadError {
1460 #[error("I/O error reading archive")]
1462 Io(#[source] std::io::Error),
1463
1464 #[error("path in archive `{}` wasn't valid UTF-8", String::from_utf8_lossy(.0))]
1466 NonUtf8Path(Vec<u8>),
1467
1468 #[error("path in archive `{0}` doesn't start with `target/`")]
1470 NoTargetPrefix(Utf8PathBuf),
1471
1472 #[error("path in archive `{path}` contains an invalid component `{component}`")]
1474 InvalidComponent {
1475 path: Utf8PathBuf,
1477
1478 component: String,
1480 },
1481
1482 #[error("corrupted archive: checksum read error for path `{path}`")]
1484 ChecksumRead {
1485 path: Utf8PathBuf,
1487
1488 #[source]
1490 error: std::io::Error,
1491 },
1492
1493 #[error("corrupted archive: invalid checksum for path `{path}`")]
1495 InvalidChecksum {
1496 path: Utf8PathBuf,
1498
1499 expected: u32,
1501
1502 actual: u32,
1504 },
1505
1506 #[error("metadata file `{0}` not found in archive")]
1508 MetadataFileNotFound(&'static Utf8Path),
1509
1510 #[error("error deserializing metadata file `{path}` in archive")]
1512 MetadataDeserializeError {
1513 path: &'static Utf8Path,
1515
1516 #[source]
1518 error: serde_json::Error,
1519 },
1520
1521 #[error("error building package graph from `{path}` in archive")]
1523 PackageGraphConstructError {
1524 path: &'static Utf8Path,
1526
1527 #[source]
1529 error: guppy::Error,
1530 },
1531}
1532
1533#[derive(Debug, Error)]
1537#[non_exhaustive]
1538pub enum ArchiveExtractError {
1539 #[error("error creating temporary directory")]
1541 TempDirCreate(#[source] std::io::Error),
1542
1543 #[error("error canonicalizing destination directory `{dir}`")]
1545 DestDirCanonicalization {
1546 dir: Utf8PathBuf,
1548
1549 #[source]
1551 error: std::io::Error,
1552 },
1553
1554 #[error("destination `{0}` already exists")]
1556 DestinationExists(Utf8PathBuf),
1557
1558 #[error("error reading archive")]
1560 Read(#[source] ArchiveReadError),
1561
1562 #[error("error deserializing Rust build metadata")]
1564 RustBuildMeta(#[from] RustBuildMetaParseError),
1565
1566 #[error("error writing file `{path}` to disk")]
1568 WriteFile {
1569 path: Utf8PathBuf,
1571
1572 #[source]
1574 error: std::io::Error,
1575 },
1576
1577 #[error("error reporting extract status")]
1579 ReporterIo(std::io::Error),
1580}
1581
1582#[derive(Debug, Error)]
1584#[non_exhaustive]
1585pub enum WriteEventError {
1586 #[error("error writing to output")]
1588 Io(#[source] std::io::Error),
1589
1590 #[error("error operating on path {file}")]
1592 Fs {
1593 file: Utf8PathBuf,
1595
1596 #[source]
1598 error: std::io::Error,
1599 },
1600
1601 #[error("error writing JUnit output to {file}")]
1603 Junit {
1604 file: Utf8PathBuf,
1606
1607 #[source]
1609 error: quick_junit::SerializeError,
1610 },
1611}
1612
1613#[derive(Debug, Error)]
1616#[non_exhaustive]
1617pub enum CargoConfigError {
1618 #[error("failed to retrieve current directory")]
1620 GetCurrentDir(#[source] std::io::Error),
1621
1622 #[error("current directory is invalid UTF-8")]
1624 CurrentDirInvalidUtf8(#[source] FromPathBufError),
1625
1626 #[error("failed to parse --config argument `{config_str}` as TOML")]
1628 CliConfigParseError {
1629 config_str: String,
1631
1632 #[source]
1634 error: toml_edit::TomlError,
1635 },
1636
1637 #[error("failed to deserialize --config argument `{config_str}` as TOML")]
1639 CliConfigDeError {
1640 config_str: String,
1642
1643 #[source]
1645 error: toml_edit::de::Error,
1646 },
1647
1648 #[error(
1650 "invalid format for --config argument `{config_str}` (should be a dotted key expression)"
1651 )]
1652 InvalidCliConfig {
1653 config_str: String,
1655
1656 #[source]
1658 reason: InvalidCargoCliConfigReason,
1659 },
1660
1661 #[error("non-UTF-8 path encountered")]
1663 NonUtf8Path(#[source] FromPathBufError),
1664
1665 #[error("failed to retrieve the Cargo home directory")]
1667 GetCargoHome(#[source] std::io::Error),
1668
1669 #[error("failed to canonicalize path `{path}")]
1671 FailedPathCanonicalization {
1672 path: Utf8PathBuf,
1674
1675 #[source]
1677 error: std::io::Error,
1678 },
1679
1680 #[error("failed to read config at `{path}`")]
1682 ConfigReadError {
1683 path: Utf8PathBuf,
1685
1686 #[source]
1688 error: std::io::Error,
1689 },
1690
1691 #[error(transparent)]
1693 ConfigParseError(#[from] Box<CargoConfigParseError>),
1694}
1695
1696#[derive(Debug, Error)]
1700#[error("failed to parse config at `{path}`")]
1701pub struct CargoConfigParseError {
1702 pub path: Utf8PathBuf,
1704
1705 #[source]
1707 pub error: toml::de::Error,
1708}
1709
1710#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
1714#[non_exhaustive]
1715pub enum InvalidCargoCliConfigReason {
1716 #[error("was not a TOML dotted key expression (such as `build.jobs = 2`)")]
1718 NotDottedKv,
1719
1720 #[error("includes non-whitespace decoration")]
1722 IncludesNonWhitespaceDecoration,
1723
1724 #[error("sets a value to an inline table, which is not accepted")]
1726 SetsValueToInlineTable,
1727
1728 #[error("sets a value to an array of tables, which is not accepted")]
1730 SetsValueToArrayOfTables,
1731
1732 #[error("doesn't provide a value")]
1734 DoesntProvideValue,
1735}
1736
1737#[derive(Debug, Error)]
1739pub enum HostPlatformDetectError {
1740 #[error(
1743 "error spawning `rustc -vV`, and detecting the build \
1744 target failed as well\n\
1745 - rustc spawn error: {}\n\
1746 - build target error: {}\n",
1747 DisplayErrorChain::new_with_initial_indent(" ", error),
1748 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1749 )]
1750 RustcVvSpawnError {
1751 error: std::io::Error,
1753
1754 build_target_error: Box<target_spec::Error>,
1756 },
1757
1758 #[error(
1761 "`rustc -vV` failed with {}, and detecting the \
1762 build target failed as well\n\
1763 - `rustc -vV` stdout:\n{}\n\
1764 - `rustc -vV` stderr:\n{}\n\
1765 - build target error:\n{}\n",
1766 status,
1767 DisplayIndented { item: String::from_utf8_lossy(stdout), indent: " " },
1768 DisplayIndented { item: String::from_utf8_lossy(stderr), indent: " " },
1769 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1770 )]
1771 RustcVvFailed {
1772 status: ExitStatus,
1774
1775 stdout: Vec<u8>,
1777
1778 stderr: Vec<u8>,
1780
1781 build_target_error: Box<target_spec::Error>,
1783 },
1784
1785 #[error(
1788 "parsing `rustc -vV` output failed, and detecting the build target \
1789 failed as well\n\
1790 - host platform error:\n{}\n\
1791 - build target error:\n{}\n",
1792 DisplayErrorChain::new_with_initial_indent(" ", host_platform_error),
1793 DisplayErrorChain::new_with_initial_indent(" ", build_target_error)
1794 )]
1795 HostPlatformParseError {
1796 host_platform_error: Box<target_spec::Error>,
1798
1799 build_target_error: Box<target_spec::Error>,
1801 },
1802
1803 #[error("test-only code, so `rustc -vV` was not called; failed to detect build target")]
1806 BuildTargetError {
1807 #[source]
1809 build_target_error: Box<target_spec::Error>,
1810 },
1811}
1812
1813#[derive(Debug, Error)]
1815pub enum TargetTripleError {
1816 #[error(
1818 "environment variable '{}' contained non-UTF-8 data",
1819 TargetTriple::CARGO_BUILD_TARGET_ENV
1820 )]
1821 InvalidEnvironmentVar,
1822
1823 #[error("error deserializing target triple from {source}")]
1825 TargetSpecError {
1826 source: TargetTripleSource,
1828
1829 #[source]
1831 error: target_spec::Error,
1832 },
1833
1834 #[error("target path `{path}` is not a valid file")]
1836 TargetPathReadError {
1837 source: TargetTripleSource,
1839
1840 path: Utf8PathBuf,
1842
1843 #[source]
1845 error: std::io::Error,
1846 },
1847
1848 #[error(
1850 "for custom platform obtained from {source}, \
1851 failed to create temporary directory for custom platform"
1852 )]
1853 CustomPlatformTempDirError {
1854 source: TargetTripleSource,
1856
1857 #[source]
1859 error: std::io::Error,
1860 },
1861
1862 #[error(
1864 "for custom platform obtained from {source}, \
1865 failed to write JSON to temporary path `{path}`"
1866 )]
1867 CustomPlatformWriteError {
1868 source: TargetTripleSource,
1870
1871 path: Utf8PathBuf,
1873
1874 #[source]
1876 error: std::io::Error,
1877 },
1878
1879 #[error(
1881 "for custom platform obtained from {source}, \
1882 failed to close temporary directory `{dir_path}`"
1883 )]
1884 CustomPlatformCloseError {
1885 source: TargetTripleSource,
1887
1888 dir_path: Utf8PathBuf,
1890
1891 #[source]
1893 error: std::io::Error,
1894 },
1895}
1896
1897impl TargetTripleError {
1898 pub fn source_report(&self) -> Option<miette::Report> {
1903 match self {
1904 Self::TargetSpecError { error, .. } => {
1905 Some(miette::Report::new_boxed(error.clone().into_diagnostic()))
1906 }
1907 TargetTripleError::InvalidEnvironmentVar
1909 | TargetTripleError::TargetPathReadError { .. }
1910 | TargetTripleError::CustomPlatformTempDirError { .. }
1911 | TargetTripleError::CustomPlatformWriteError { .. }
1912 | TargetTripleError::CustomPlatformCloseError { .. } => None,
1913 }
1914 }
1915}
1916
1917#[derive(Debug, Error)]
1919pub enum TargetRunnerError {
1920 #[error("environment variable '{0}' contained non-UTF-8 data")]
1922 InvalidEnvironmentVar(String),
1923
1924 #[error("runner '{key}' = '{value}' did not contain a runner binary")]
1927 BinaryNotSpecified {
1928 key: PlatformRunnerSource,
1930
1931 value: String,
1933 },
1934}
1935
1936#[derive(Debug, Error)]
1938#[error("error setting up signal handler")]
1939pub struct SignalHandlerSetupError(#[from] std::io::Error);
1940
1941#[derive(Debug, Error)]
1943pub enum ShowTestGroupsError {
1944 #[error(
1946 "unknown test groups specified: {}\n(known groups: {})",
1947 unknown_groups.iter().join(", "),
1948 known_groups.iter().join(", "),
1949 )]
1950 UnknownGroups {
1951 unknown_groups: BTreeSet<TestGroup>,
1953
1954 known_groups: BTreeSet<TestGroup>,
1956 },
1957}
1958
1959#[derive(Debug, Error, PartialEq, Eq, Hash)]
1961pub enum InheritsError {
1962 #[error("the {} profile should not inherit from other profiles", .0)]
1964 DefaultProfileInheritance(String),
1965 #[error("profile {} inherits from an unknown profile {}", .0, .1)]
1967 UnknownInheritance(String, String),
1968 #[error("a self referential inheritance is detected from profile: {}", .0)]
1970 SelfReferentialInheritance(String),
1971 #[error("inheritance cycle detected in profile configuration from: {}", .0.iter().map(|scc| {
1973 format!("[{}]", scc.iter().join(", "))
1974 }).join(", "))]
1975 InheritanceCycle(Vec<Vec<String>>),
1976}
1977
1978#[derive(Debug, Error)]
1984pub enum RunStoreError {
1985 #[error("error creating run directory `{run_dir}`")]
1987 RunDirCreate {
1988 run_dir: Utf8PathBuf,
1990
1991 #[source]
1993 error: std::io::Error,
1994 },
1995
1996 #[error("error acquiring lock on `{path}`")]
1998 FileLock {
1999 path: Utf8PathBuf,
2001
2002 #[source]
2004 error: std::io::Error,
2005 },
2006
2007 #[error(
2009 "timed out acquiring lock on `{path}` after {timeout_secs}s (is the cache directory \
2010 on a networked filesystem?)"
2011 )]
2012 FileLockTimeout {
2013 path: Utf8PathBuf,
2015
2016 timeout_secs: u64,
2018 },
2019
2020 #[error("error reading run list from `{path}`")]
2022 RunListRead {
2023 path: Utf8PathBuf,
2025
2026 #[source]
2028 error: std::io::Error,
2029 },
2030
2031 #[error("error deserializing run list from `{path}`")]
2033 RunListDeserialize {
2034 path: Utf8PathBuf,
2036
2037 #[source]
2039 error: serde_json::Error,
2040 },
2041
2042 #[error("error serializing run list to `{path}`")]
2044 RunListSerialize {
2045 path: Utf8PathBuf,
2047
2048 #[source]
2050 error: serde_json::Error,
2051 },
2052
2053 #[error("error serializing test list")]
2055 TestListSerialize {
2056 #[source]
2058 error: serde_json::Error,
2059 },
2060
2061 #[error("error serializing record options")]
2063 RecordOptionsSerialize {
2064 #[source]
2066 error: serde_json::Error,
2067 },
2068
2069 #[error("error serializing test event")]
2071 TestEventSerialize {
2072 #[source]
2074 error: serde_json::Error,
2075 },
2076
2077 #[error("error writing run list to `{path}`")]
2079 RunListWrite {
2080 path: Utf8PathBuf,
2082
2083 #[source]
2085 error: atomicwrites::Error<std::io::Error>,
2086 },
2087
2088 #[error("error writing to store at `{store_path}`")]
2090 StoreWrite {
2091 store_path: Utf8PathBuf,
2093
2094 #[source]
2096 error: StoreWriterError,
2097 },
2098
2099 #[error("error creating run log at `{path}`")]
2101 RunLogCreate {
2102 path: Utf8PathBuf,
2104
2105 #[source]
2107 error: std::io::Error,
2108 },
2109
2110 #[error("error writing to run log at `{path}`")]
2112 RunLogWrite {
2113 path: Utf8PathBuf,
2115
2116 #[source]
2118 error: std::io::Error,
2119 },
2120
2121 #[error("error flushing run log at `{path}`")]
2123 RunLogFlush {
2124 path: Utf8PathBuf,
2126
2127 #[source]
2129 error: std::io::Error,
2130 },
2131
2132 #[error(
2134 "cannot write to record store: runs.json.zst format version {file_version} is newer than \
2135 supported version {max_supported_version}"
2136 )]
2137 FormatVersionTooNew {
2138 file_version: u32,
2140 max_supported_version: u32,
2142 },
2143}
2144
2145#[derive(Debug, Error)]
2147#[non_exhaustive]
2148pub enum StoreWriterError {
2149 #[error("error creating store")]
2151 Create {
2152 #[source]
2154 error: std::io::Error,
2155 },
2156
2157 #[error("error creating path `{path}` in store")]
2159 StartFile {
2160 path: Utf8PathBuf,
2162
2163 #[source]
2165 error: zip::result::ZipError,
2166 },
2167
2168 #[error("error writing to path `{path}` in store")]
2170 Write {
2171 path: Utf8PathBuf,
2173
2174 #[source]
2176 error: std::io::Error,
2177 },
2178
2179 #[error("error compressing data")]
2181 Compress {
2182 #[source]
2184 error: std::io::Error,
2185 },
2186
2187 #[error("error finalizing store")]
2189 Finish {
2190 #[source]
2192 error: zip::result::ZipError,
2193 },
2194
2195 #[error("error flushing store")]
2197 Flush {
2198 #[source]
2200 error: std::io::Error,
2201 },
2202}
2203
2204#[derive(Debug, Error)]
2206pub enum RecordReporterError {
2207 #[error(transparent)]
2209 RunStore(RunStoreError),
2210
2211 #[error("record writer thread panicked: {message}")]
2213 WriterPanic {
2214 message: String,
2216 },
2217}
2218
2219#[derive(Debug, Error)]
2221pub enum CacheDirError {
2222 #[error("could not determine platform base directory strategy")]
2226 BaseDirStrategy,
2227
2228 #[error("platform cache directory is not valid UTF-8: {path:?}")]
2230 CacheDirNotUtf8 {
2231 path: PathBuf,
2233 },
2234
2235 #[error("could not canonicalize workspace path `{workspace_root}`")]
2237 Canonicalize {
2238 workspace_root: Utf8PathBuf,
2240 #[source]
2242 error: std::io::Error,
2243 },
2244}
2245
2246#[derive(Debug, Error)]
2248pub enum RecordSetupError {
2249 #[error("could not determine platform cache directory for recording")]
2251 CacheDirNotFound(#[source] CacheDirError),
2252
2253 #[error("failed to create run store")]
2255 StoreCreate(#[source] RunStoreError),
2256
2257 #[error("failed to lock run store")]
2259 StoreLock(#[source] RunStoreError),
2260
2261 #[error("failed to create run recorder")]
2263 RecorderCreate(#[source] RunStoreError),
2264}
2265
2266impl RecordSetupError {
2267 pub fn disabled_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
2274 match self {
2275 RecordSetupError::RecorderCreate(err @ RunStoreError::FormatVersionTooNew { .. }) => {
2276 Some(err)
2277 }
2278 _ => None,
2279 }
2280 }
2281}
2282
2283#[derive(Debug, Error)]
2285pub enum RecordPruneError {
2286 #[error("error deleting run `{run_id}` at `{path}`")]
2288 DeleteRun {
2289 run_id: ReportUuid,
2291
2292 path: Utf8PathBuf,
2294
2295 #[source]
2297 error: std::io::Error,
2298 },
2299
2300 #[error("error calculating size of `{path}`")]
2302 CalculateSize {
2303 path: Utf8PathBuf,
2305
2306 #[source]
2308 error: std::io::Error,
2309 },
2310
2311 #[error("error deleting orphaned directory `{path}`")]
2313 DeleteOrphan {
2314 path: Utf8PathBuf,
2316
2317 #[source]
2319 error: std::io::Error,
2320 },
2321
2322 #[error("error reading runs directory `{path}`")]
2324 ReadRunsDir {
2325 path: Utf8PathBuf,
2327
2328 #[source]
2330 error: std::io::Error,
2331 },
2332
2333 #[error("error reading directory entry in `{dir}`")]
2335 ReadDirEntry {
2336 dir: Utf8PathBuf,
2338
2339 #[source]
2341 error: std::io::Error,
2342 },
2343
2344 #[error("error reading file type for `{path}`")]
2346 ReadFileType {
2347 path: Utf8PathBuf,
2349
2350 #[source]
2352 error: std::io::Error,
2353 },
2354}
2355
2356#[derive(Clone, Debug, PartialEq, Eq, Error)]
2361#[error("invalid run ID selector `{input}`: expected `latest` or hex digits")]
2362pub struct InvalidRunIdSelector {
2363 pub input: String,
2365}
2366
2367#[derive(Debug, Error)]
2369pub enum RunIdResolutionError {
2370 #[error("no recorded run found matching `{prefix}`")]
2372 NotFound {
2373 prefix: String,
2375 },
2376
2377 #[error("prefix `{prefix}` is ambiguous, matches {count} runs")]
2379 Ambiguous {
2380 prefix: String,
2382
2383 count: usize,
2385
2386 candidates: Vec<RecordedRunInfo>,
2388
2389 run_id_index: RunIdIndex,
2391 },
2392
2393 #[error("prefix `{prefix}` contains invalid characters (expected hexadecimal)")]
2395 InvalidPrefix {
2396 prefix: String,
2398 },
2399
2400 #[error("no recorded runs exist")]
2402 NoRuns,
2403
2404 #[error("{non_replayable_count} recorded runs exist, but none are replayable")]
2407 NoReplayableRuns {
2408 non_replayable_count: usize,
2410 },
2411}
2412
2413#[derive(Debug, Error)]
2415pub enum RecordReadError {
2416 #[error("run not found at `{path}`")]
2418 RunNotFound {
2419 path: Utf8PathBuf,
2421 },
2422
2423 #[error("error opening archive at `{path}`")]
2425 OpenArchive {
2426 path: Utf8PathBuf,
2428
2429 #[source]
2431 error: std::io::Error,
2432 },
2433
2434 #[error("error reading `{file_name}` from archive")]
2436 ReadArchiveFile {
2437 file_name: String,
2439
2440 #[source]
2442 error: zip::result::ZipError,
2443 },
2444
2445 #[error("error opening run log at `{path}`")]
2447 OpenRunLog {
2448 path: Utf8PathBuf,
2450
2451 #[source]
2453 error: std::io::Error,
2454 },
2455
2456 #[error("error reading line {line_number} from run log")]
2458 ReadRunLog {
2459 line_number: usize,
2461
2462 #[source]
2464 error: std::io::Error,
2465 },
2466
2467 #[error("error parsing event at line {line_number}")]
2469 ParseEvent {
2470 line_number: usize,
2472
2473 #[source]
2475 error: serde_json::Error,
2476 },
2477
2478 #[error("required file `{file_name}` not found in archive")]
2480 FileNotFound {
2481 file_name: String,
2483 },
2484
2485 #[error("error decompressing data from `{file_name}`")]
2487 Decompress {
2488 file_name: String,
2490
2491 #[source]
2493 error: std::io::Error,
2494 },
2495
2496 #[error(
2501 "unknown output file type `{file_name}` in archive \
2502 (archive may have been created by a newer version of nextest)"
2503 )]
2504 UnknownOutputType {
2505 file_name: String,
2507 },
2508
2509 #[error(
2511 "file `{file_name}` in archive exceeds maximum size ({size} bytes, limit is {limit} bytes)"
2512 )]
2513 FileTooLarge {
2514 file_name: String,
2516
2517 size: u64,
2519
2520 limit: u64,
2522 },
2523
2524 #[error(
2529 "file `{file_name}` size mismatch: header claims {claimed_size} bytes, \
2530 but read {actual_size} bytes (archive may be corrupt or tampered)"
2531 )]
2532 SizeMismatch {
2533 file_name: String,
2535
2536 claimed_size: u64,
2538
2539 actual_size: u64,
2541 },
2542
2543 #[error("error deserializing `{file_name}`")]
2545 DeserializeMetadata {
2546 file_name: String,
2548
2549 #[source]
2551 error: serde_json::Error,
2552 },
2553}
2554
2555#[derive(Debug, Error)]
2559pub enum TestListFromSummaryError {
2560 #[error("package `{name}` (id: `{package_id}`) not found in cargo metadata")]
2562 PackageNotFound {
2563 name: String,
2565
2566 package_id: String,
2568 },
2569
2570 #[error("error parsing rust build metadata")]
2572 RustBuildMeta(#[source] RustBuildMetaParseError),
2573}
2574
2575#[cfg(feature = "self-update")]
2576mod self_update_errors {
2577 use super::*;
2578 use crate::update::PrereleaseKind;
2579 use mukti_metadata::ReleaseStatus;
2580 use semver::{Version, VersionReq};
2581
2582 #[derive(Debug, Error)]
2586 #[non_exhaustive]
2587 pub enum UpdateError {
2588 #[error("failed to read release metadata from `{path}`")]
2590 ReadLocalMetadata {
2591 path: Utf8PathBuf,
2593
2594 #[source]
2596 error: std::io::Error,
2597 },
2598
2599 #[error("self-update failed")]
2601 SelfUpdate(#[source] self_update::errors::Error),
2602
2603 #[error("deserializing release metadata failed")]
2605 ReleaseMetadataDe(#[source] serde_json::Error),
2606
2607 #[error("version `{version}` not found (known versions: {})", known_versions(.known))]
2609 VersionNotFound {
2610 version: Version,
2612
2613 known: Vec<(Version, ReleaseStatus)>,
2615 },
2616
2617 #[error("no version found matching requirement `{req}`")]
2619 NoMatchForVersionReq {
2620 req: VersionReq,
2622 },
2623
2624 #[error("no stable version found")]
2626 NoStableVersion,
2627
2628 #[error("no version found matching {} channel", kind.description())]
2630 NoVersionForPrereleaseKind {
2631 kind: PrereleaseKind,
2633 },
2634
2635 #[error("project {not_found} not found in release metadata (known projects: {})", known.join(", "))]
2637 MuktiProjectNotFound {
2638 not_found: String,
2640
2641 known: Vec<String>,
2643 },
2644
2645 #[error(
2647 "for version {version}, no release information found for target `{triple}` \
2648 (known targets: {})",
2649 known_triples.iter().join(", ")
2650 )]
2651 NoTargetData {
2652 version: Version,
2654
2655 triple: String,
2657
2658 known_triples: BTreeSet<String>,
2660 },
2661
2662 #[error("the current executable's path could not be determined")]
2664 CurrentExe(#[source] std::io::Error),
2665
2666 #[error("temporary directory could not be created at `{location}`")]
2668 TempDirCreate {
2669 location: Utf8PathBuf,
2671
2672 #[source]
2674 error: std::io::Error,
2675 },
2676
2677 #[error("temporary archive could not be created at `{archive_path}`")]
2679 TempArchiveCreate {
2680 archive_path: Utf8PathBuf,
2682
2683 #[source]
2685 error: std::io::Error,
2686 },
2687
2688 #[error("error writing to temporary archive at `{archive_path}`")]
2690 TempArchiveWrite {
2691 archive_path: Utf8PathBuf,
2693
2694 #[source]
2696 error: std::io::Error,
2697 },
2698
2699 #[error("error reading from temporary archive at `{archive_path}`")]
2701 TempArchiveRead {
2702 archive_path: Utf8PathBuf,
2704
2705 #[source]
2707 error: std::io::Error,
2708 },
2709
2710 #[error("SHA-256 checksum mismatch: expected: {expected}, actual: {actual}")]
2712 ChecksumMismatch {
2713 expected: String,
2715
2716 actual: String,
2718 },
2719
2720 #[error("error renaming `{source}` to `{dest}`")]
2722 FsRename {
2723 source: Utf8PathBuf,
2725
2726 dest: Utf8PathBuf,
2728
2729 #[source]
2731 error: std::io::Error,
2732 },
2733
2734 #[error("cargo-nextest binary updated, but error running `cargo nextest self setup`")]
2736 SelfSetup(#[source] std::io::Error),
2737 }
2738
2739 fn known_versions(versions: &[(Version, ReleaseStatus)]) -> String {
2740 use std::fmt::Write;
2741
2742 const DISPLAY_COUNT: usize = 4;
2744
2745 let display_versions: Vec<_> = versions
2746 .iter()
2747 .filter(|(v, status)| v.pre.is_empty() && *status == ReleaseStatus::Active)
2748 .map(|(v, _)| v.to_string())
2749 .take(DISPLAY_COUNT)
2750 .collect();
2751 let mut display_str = display_versions.join(", ");
2752 if versions.len() > display_versions.len() {
2753 write!(
2754 display_str,
2755 " and {} others",
2756 versions.len() - display_versions.len()
2757 )
2758 .unwrap();
2759 }
2760
2761 display_str
2762 }
2763
2764 #[derive(Debug, Error)]
2766 pub enum UpdateVersionParseError {
2767 #[error("version string is empty")]
2769 EmptyString,
2770
2771 #[error(
2773 "`{input}` is not a valid semver requirement\n\
2774 (hint: see https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html for the correct format)"
2775 )]
2776 InvalidVersionReq {
2777 input: String,
2779
2780 #[source]
2782 error: semver::Error,
2783 },
2784
2785 #[error("`{input}` is not a valid semver{}", extra_semver_output(.input))]
2787 InvalidVersion {
2788 input: String,
2790
2791 #[source]
2793 error: semver::Error,
2794 },
2795 }
2796
2797 fn extra_semver_output(input: &str) -> String {
2798 if input.parse::<VersionReq>().is_ok() {
2801 format!(
2802 "\n(if you want to specify a semver range, add an explicit qualifier, like ^{input})"
2803 )
2804 } else {
2805 "".to_owned()
2806 }
2807 }
2808}
2809
2810#[cfg(feature = "self-update")]
2811pub use self_update_errors::*;
2812
2813#[cfg(test)]
2814mod tests {
2815 use super::*;
2816
2817 #[test]
2818 fn display_error_chain() {
2819 let err1 = StringError::new("err1", None);
2820
2821 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err1)), @"err1");
2822
2823 let err2 = StringError::new("err2", Some(err1));
2824 let err3 = StringError::new("err3\nerr3 line 2", Some(err2));
2825
2826 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&err3)), @"
2827 err3
2828 err3 line 2
2829 caused by:
2830 - err2
2831 - err1
2832 ");
2833 }
2834
2835 #[test]
2836 fn display_error_list() {
2837 let err1 = StringError::new("err1", None);
2838
2839 let error_list =
2840 ErrorList::<StringError>::new("waiting on the water to boil", vec![err1.clone()])
2841 .expect(">= 1 error");
2842 insta::assert_snapshot!(format!("{}", error_list), @"err1");
2843 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"err1");
2844
2845 let err2 = StringError::new("err2", Some(err1));
2846 let err3 = StringError::new("err3", Some(err2));
2847
2848 let error_list =
2849 ErrorList::<StringError>::new("waiting on flowers to bloom", vec![err3.clone()])
2850 .expect(">= 1 error");
2851 insta::assert_snapshot!(format!("{}", error_list), @"err3");
2852 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
2853 err3
2854 caused by:
2855 - err2
2856 - err1
2857 ");
2858
2859 let err4 = StringError::new("err4", None);
2860 let err5 = StringError::new("err5", Some(err4));
2861 let err6 = StringError::new("err6\nerr6 line 2", Some(err5));
2862
2863 let error_list = ErrorList::<StringError>::new(
2864 "waiting for the heat death of the universe",
2865 vec![err3, err6],
2866 )
2867 .expect(">= 1 error");
2868
2869 insta::assert_snapshot!(format!("{}", error_list), @"
2870 2 errors occurred waiting for the heat death of the universe:
2871 * err3
2872 caused by:
2873 - err2
2874 - err1
2875 * err6
2876 err6 line 2
2877 caused by:
2878 - err5
2879 - err4
2880 ");
2881 insta::assert_snapshot!(format!("{}", DisplayErrorChain::new(&error_list)), @"
2882 2 errors occurred waiting for the heat death of the universe:
2883 * err3
2884 caused by:
2885 - err2
2886 - err1
2887 * err6
2888 err6 line 2
2889 caused by:
2890 - err5
2891 - err4
2892 ");
2893 }
2894
2895 #[derive(Clone, Debug, Error)]
2896 struct StringError {
2897 message: String,
2898 #[source]
2899 source: Option<Box<StringError>>,
2900 }
2901
2902 impl StringError {
2903 fn new(message: impl Into<String>, source: Option<StringError>) -> Self {
2904 Self {
2905 message: message.into(),
2906 source: source.map(Box::new),
2907 }
2908 }
2909 }
2910
2911 impl fmt::Display for StringError {
2912 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2913 write!(f, "{}", self.message)
2914 }
2915 }
2916}