1use super::{DisplayFilterMatcher, TestListDisplayFilter};
5use crate::{
6 cargo_config::EnvironmentMap,
7 config::{
8 core::EvaluatableProfile,
9 overrides::{ListSettings, TestSettings},
10 scripts::{WrapperScriptConfig, WrapperScriptTargetRunner},
11 },
12 double_spawn::DoubleSpawnInfo,
13 errors::{
14 CreateTestListError, FromMessagesError, TestListFromSummaryError, WriteTestListError,
15 },
16 helpers::{convert_build_platform, dylib_path, dylib_path_envvar, write_test_name},
17 indenter::indented,
18 list::{BinaryList, OutputFormat, RustBuildMeta, Styles, TestListState},
19 reuse_build::PathMapper,
20 run_mode::NextestRunMode,
21 runner::Interceptor,
22 target_runner::{PlatformRunner, TargetRunner},
23 test_command::{LocalExecuteContext, TestCommand, TestCommandPhase},
24 test_filter::{BinaryMismatchReason, FilterBinaryMatch, FilterBound, TestFilterBuilder},
25 write_str::WriteStr,
26};
27use camino::{Utf8Path, Utf8PathBuf};
28use debug_ignore::DebugIgnore;
29use futures::prelude::*;
30use guppy::{
31 PackageId,
32 graph::{PackageGraph, PackageMetadata},
33};
34use iddqd::{IdOrdItem, IdOrdMap, id_upcast};
35use nextest_filtering::{BinaryQuery, EvalContext, TestQuery};
36use nextest_metadata::{
37 BuildPlatform, FilterMatch, MismatchReason, RustBinaryId, RustNonTestBinaryKind,
38 RustTestBinaryKind, RustTestBinarySummary, RustTestCaseSummary, RustTestKind,
39 RustTestSuiteStatusSummary, RustTestSuiteSummary, TestCaseName, TestListSummary,
40};
41use owo_colors::OwoColorize;
42use quick_junit::ReportUuid;
43use serde::{Deserialize, Serialize};
44use std::{
45 borrow::{Borrow, Cow},
46 collections::{BTreeMap, BTreeSet},
47 ffi::{OsStr, OsString},
48 fmt,
49 hash::{Hash, Hasher},
50 io,
51 path::PathBuf,
52 sync::{Arc, OnceLock},
53};
54use swrite::{SWrite, swrite};
55use tokio::runtime::Runtime;
56use tracing::debug;
57
58#[derive(Clone, Debug)]
63pub struct RustTestArtifact<'g> {
64 pub binary_id: RustBinaryId,
66
67 pub package: PackageMetadata<'g>,
70
71 pub binary_path: Utf8PathBuf,
73
74 pub binary_name: String,
76
77 pub kind: RustTestBinaryKind,
79
80 pub non_test_binaries: BTreeSet<(String, Utf8PathBuf)>,
82
83 pub cwd: Utf8PathBuf,
85
86 pub build_platform: BuildPlatform,
88}
89
90impl<'g> RustTestArtifact<'g> {
91 pub fn from_binary_list(
93 graph: &'g PackageGraph,
94 binary_list: Arc<BinaryList>,
95 rust_build_meta: &RustBuildMeta<TestListState>,
96 path_mapper: &PathMapper,
97 platform_filter: Option<BuildPlatform>,
98 ) -> Result<Vec<Self>, FromMessagesError> {
99 let mut binaries = vec![];
100
101 for binary in &binary_list.rust_binaries {
102 if platform_filter.is_some() && platform_filter != Some(binary.build_platform) {
103 continue;
104 }
105
106 let package_id = PackageId::new(binary.package_id.clone());
108 let package = graph
109 .metadata(&package_id)
110 .map_err(FromMessagesError::PackageGraph)?;
111
112 let cwd = package
114 .manifest_path()
115 .parent()
116 .unwrap_or_else(|| {
117 panic!(
118 "manifest path {} doesn't have a parent",
119 package.manifest_path()
120 )
121 })
122 .to_path_buf();
123
124 let binary_path = path_mapper.map_binary(binary.path.clone());
125 let cwd = path_mapper.map_cwd(cwd);
126
127 let non_test_binaries = if binary.kind == RustTestBinaryKind::TEST
129 || binary.kind == RustTestBinaryKind::BENCH
130 {
131 match rust_build_meta.non_test_binaries.get(package_id.repr()) {
134 Some(binaries) => binaries
135 .iter()
136 .filter(|binary| {
137 binary.kind == RustNonTestBinaryKind::BIN_EXE
139 })
140 .map(|binary| {
141 let abs_path = rust_build_meta.target_directory.join(&binary.path);
143 (binary.name.clone(), abs_path)
144 })
145 .collect(),
146 None => BTreeSet::new(),
147 }
148 } else {
149 BTreeSet::new()
150 };
151
152 binaries.push(RustTestArtifact {
153 binary_id: binary.id.clone(),
154 package,
155 binary_path,
156 binary_name: binary.name.clone(),
157 kind: binary.kind.clone(),
158 cwd,
159 non_test_binaries,
160 build_platform: binary.build_platform,
161 })
162 }
163
164 Ok(binaries)
165 }
166
167 pub fn to_binary_query(&self) -> BinaryQuery<'_> {
169 BinaryQuery {
170 package_id: self.package.id(),
171 binary_id: &self.binary_id,
172 kind: &self.kind,
173 binary_name: &self.binary_name,
174 platform: convert_build_platform(self.build_platform),
175 }
176 }
177
178 fn into_test_suite(self, status: RustTestSuiteStatus) -> RustTestSuite<'g> {
182 let Self {
183 binary_id,
184 package,
185 binary_path,
186 binary_name,
187 kind,
188 non_test_binaries,
189 cwd,
190 build_platform,
191 } = self;
192
193 RustTestSuite {
194 binary_id,
195 binary_path,
196 package,
197 binary_name,
198 kind,
199 non_test_binaries,
200 cwd,
201 build_platform,
202 status,
203 }
204 }
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
209pub struct SkipCounts {
210 pub skipped_tests: usize,
212
213 pub skipped_tests_rerun: usize,
216
217 pub skipped_tests_non_benchmark: usize,
221
222 pub skipped_tests_default_filter: usize,
224
225 pub skipped_binaries: usize,
227
228 pub skipped_binaries_default_filter: usize,
230}
231
232#[derive(Clone, Debug)]
234pub struct TestList<'g> {
235 test_count: usize,
236 mode: NextestRunMode,
237 rust_build_meta: RustBuildMeta<TestListState>,
238 rust_suites: IdOrdMap<RustTestSuite<'g>>,
239 workspace_root: Utf8PathBuf,
240 env: EnvironmentMap,
241 updated_dylib_path: OsString,
242 skip_counts: OnceLock<SkipCounts>,
244}
245
246impl<'g> TestList<'g> {
247 #[expect(clippy::too_many_arguments)]
249 pub fn new<I>(
250 ctx: &TestExecuteContext<'_>,
251 test_artifacts: I,
252 rust_build_meta: RustBuildMeta<TestListState>,
253 filter: &TestFilterBuilder,
254 workspace_root: Utf8PathBuf,
255 env: EnvironmentMap,
256 profile: &impl ListProfile,
257 bound: FilterBound,
258 list_threads: usize,
259 ) -> Result<Self, CreateTestListError>
260 where
261 I: IntoIterator<Item = RustTestArtifact<'g>>,
262 I::IntoIter: Send,
263 {
264 let updated_dylib_path = Self::create_dylib_path(&rust_build_meta)?;
265 debug!(
266 "updated {}: {}",
267 dylib_path_envvar(),
268 updated_dylib_path.to_string_lossy(),
269 );
270 let lctx = LocalExecuteContext {
271 phase: TestCommandPhase::List,
272 workspace_root: &workspace_root,
275 rust_build_meta: &rust_build_meta,
276 double_spawn: ctx.double_spawn,
277 dylib_path: &updated_dylib_path,
278 profile_name: ctx.profile_name,
279 env: &env,
280 };
281
282 let ecx = profile.filterset_ecx();
283
284 let runtime = Runtime::new().map_err(CreateTestListError::TokioRuntimeCreate)?;
285
286 let stream = futures::stream::iter(test_artifacts).map(|test_binary| {
287 async {
288 let binary_query = test_binary.to_binary_query();
289 let binary_match = filter.filter_binary_match(&test_binary, &ecx, bound);
290 match binary_match {
291 FilterBinaryMatch::Definite | FilterBinaryMatch::Possible => {
292 debug!(
293 "executing test binary to obtain test list \
294 (match result is {binary_match:?}): {}",
295 test_binary.binary_id,
296 );
297 let list_settings = profile.list_settings_for(&binary_query);
299 let (non_ignored, ignored) = test_binary
300 .exec(&lctx, &list_settings, ctx.target_runner)
301 .await?;
302 let info = Self::process_output(
303 test_binary,
304 filter,
305 &ecx,
306 bound,
307 non_ignored.as_str(),
308 ignored.as_str(),
309 )?;
310 Ok::<_, CreateTestListError>(info)
311 }
312 FilterBinaryMatch::Mismatch { reason } => {
313 debug!("skipping test binary: {reason}: {}", test_binary.binary_id,);
314 Ok(Self::process_skipped(test_binary, reason))
315 }
316 }
317 }
318 });
319 let fut = stream.buffer_unordered(list_threads).try_collect();
320
321 let rust_suites: IdOrdMap<_> = runtime.block_on(fut)?;
322
323 runtime.shutdown_background();
326
327 let test_count = rust_suites
328 .iter()
329 .map(|suite| suite.status.test_count())
330 .sum();
331
332 Ok(Self {
333 rust_suites,
334 mode: filter.mode(),
335 workspace_root,
336 env,
337 rust_build_meta,
338 updated_dylib_path,
339 test_count,
340 skip_counts: OnceLock::new(),
341 })
342 }
343
344 #[cfg(test)]
346 fn new_with_outputs(
347 test_bin_outputs: impl IntoIterator<
348 Item = (RustTestArtifact<'g>, impl AsRef<str>, impl AsRef<str>),
349 >,
350 workspace_root: Utf8PathBuf,
351 rust_build_meta: RustBuildMeta<TestListState>,
352 filter: &TestFilterBuilder,
353 env: EnvironmentMap,
354 ecx: &EvalContext<'_>,
355 bound: FilterBound,
356 ) -> Result<Self, CreateTestListError> {
357 let mut test_count = 0;
358
359 let updated_dylib_path = Self::create_dylib_path(&rust_build_meta)?;
360
361 let rust_suites = test_bin_outputs
362 .into_iter()
363 .map(|(test_binary, non_ignored, ignored)| {
364 let binary_match = filter.filter_binary_match(&test_binary, ecx, bound);
365 match binary_match {
366 FilterBinaryMatch::Definite | FilterBinaryMatch::Possible => {
367 debug!(
368 "processing output for binary \
369 (match result is {binary_match:?}): {}",
370 test_binary.binary_id,
371 );
372 let info = Self::process_output(
373 test_binary,
374 filter,
375 ecx,
376 bound,
377 non_ignored.as_ref(),
378 ignored.as_ref(),
379 )?;
380 test_count += info.status.test_count();
381 Ok(info)
382 }
383 FilterBinaryMatch::Mismatch { reason } => {
384 debug!("skipping test binary: {reason}: {}", test_binary.binary_id,);
385 Ok(Self::process_skipped(test_binary, reason))
386 }
387 }
388 })
389 .collect::<Result<IdOrdMap<_>, _>>()?;
390
391 Ok(Self {
392 rust_suites,
393 mode: filter.mode(),
394 workspace_root,
395 env,
396 rust_build_meta,
397 updated_dylib_path,
398 test_count,
399 skip_counts: OnceLock::new(),
400 })
401 }
402
403 pub fn from_summary(
409 graph: &'g PackageGraph,
410 summary: &TestListSummary,
411 mode: NextestRunMode,
412 ) -> Result<Self, TestListFromSummaryError> {
413 let rust_build_meta = RustBuildMeta::from_summary(summary.rust_build_meta.clone())
415 .map_err(TestListFromSummaryError::RustBuildMeta)?;
416
417 let workspace_root = graph.workspace().root().to_path_buf();
419
420 let env = EnvironmentMap::empty();
422
423 let updated_dylib_path = OsString::new();
425
426 let mut rust_suites = IdOrdMap::new();
428 let mut test_count = 0;
429
430 for (binary_id, suite_summary) in &summary.rust_suites {
431 let package_id = PackageId::new(suite_summary.binary.package_id.clone());
433 let package = graph.metadata(&package_id).map_err(|_| {
434 TestListFromSummaryError::PackageNotFound {
435 name: suite_summary.package_name.clone(),
436 package_id: suite_summary.binary.package_id.clone(),
437 }
438 })?;
439
440 let status = if suite_summary.status == RustTestSuiteStatusSummary::SKIPPED {
442 RustTestSuiteStatus::Skipped {
443 reason: BinaryMismatchReason::Expression,
444 }
445 } else if suite_summary.status == RustTestSuiteStatusSummary::SKIPPED_DEFAULT_FILTER {
446 RustTestSuiteStatus::Skipped {
447 reason: BinaryMismatchReason::DefaultSet,
448 }
449 } else {
450 let test_cases: IdOrdMap<RustTestCase> = suite_summary
452 .test_cases
453 .iter()
454 .map(|(name, info)| RustTestCase {
455 name: name.clone(),
456 test_info: info.clone(),
457 })
458 .collect();
459
460 test_count += test_cases.len();
461
462 RustTestSuiteStatus::Listed {
464 test_cases: DebugIgnore(test_cases),
465 }
466 };
467
468 let suite = RustTestSuite {
469 binary_id: binary_id.clone(),
470 binary_path: suite_summary.binary.binary_path.clone(),
471 package,
472 binary_name: suite_summary.binary.binary_name.clone(),
473 kind: suite_summary.binary.kind.clone(),
474 non_test_binaries: BTreeSet::new(), cwd: suite_summary.cwd.clone(),
476 build_platform: suite_summary.binary.build_platform,
477 status,
478 };
479
480 let _ = rust_suites.insert_unique(suite);
481 }
482
483 Ok(Self {
484 rust_suites,
485 mode,
486 workspace_root,
487 env,
488 rust_build_meta,
489 updated_dylib_path,
490 test_count,
491 skip_counts: OnceLock::new(),
492 })
493 }
494
495 pub fn test_count(&self) -> usize {
497 self.test_count
498 }
499
500 pub fn mode(&self) -> NextestRunMode {
502 self.mode
503 }
504
505 pub fn rust_build_meta(&self) -> &RustBuildMeta<TestListState> {
507 &self.rust_build_meta
508 }
509
510 pub fn skip_counts(&self) -> &SkipCounts {
512 self.skip_counts.get_or_init(|| {
513 let mut skipped_tests_rerun = 0;
514 let mut skipped_tests_non_benchmark = 0;
515 let mut skipped_tests_default_filter = 0;
516 let skipped_tests = self
517 .iter_tests()
518 .filter(|instance| match instance.test_info.filter_match {
519 FilterMatch::Mismatch {
520 reason: MismatchReason::RerunAlreadyPassed,
521 } => {
522 skipped_tests_rerun += 1;
523 true
524 }
525 FilterMatch::Mismatch {
526 reason: MismatchReason::NotBenchmark,
527 } => {
528 skipped_tests_non_benchmark += 1;
529 true
530 }
531 FilterMatch::Mismatch {
532 reason: MismatchReason::DefaultFilter,
533 } => {
534 skipped_tests_default_filter += 1;
535 true
536 }
537 FilterMatch::Mismatch { .. } => true,
538 FilterMatch::Matches => false,
539 })
540 .count();
541
542 let mut skipped_binaries_default_filter = 0;
543 let skipped_binaries = self
544 .rust_suites
545 .iter()
546 .filter(|suite| match suite.status {
547 RustTestSuiteStatus::Skipped {
548 reason: BinaryMismatchReason::DefaultSet,
549 } => {
550 skipped_binaries_default_filter += 1;
551 true
552 }
553 RustTestSuiteStatus::Skipped { .. } => true,
554 RustTestSuiteStatus::Listed { .. } => false,
555 })
556 .count();
557
558 SkipCounts {
559 skipped_tests,
560 skipped_tests_rerun,
561 skipped_tests_non_benchmark,
562 skipped_tests_default_filter,
563 skipped_binaries,
564 skipped_binaries_default_filter,
565 }
566 })
567 }
568
569 pub fn run_count(&self) -> usize {
573 self.test_count - self.skip_counts().skipped_tests
574 }
575
576 pub fn binary_count(&self) -> usize {
578 self.rust_suites.len()
579 }
580
581 pub fn listed_binary_count(&self) -> usize {
583 self.binary_count() - self.skip_counts().skipped_binaries
584 }
585
586 pub fn workspace_root(&self) -> &Utf8Path {
588 &self.workspace_root
589 }
590
591 pub fn cargo_env(&self) -> &EnvironmentMap {
593 &self.env
594 }
595
596 pub fn updated_dylib_path(&self) -> &OsStr {
598 &self.updated_dylib_path
599 }
600
601 pub fn to_summary(&self) -> TestListSummary {
603 let rust_suites = self
604 .rust_suites
605 .iter()
606 .map(|test_suite| {
607 let (status, test_cases) = test_suite.status.to_summary();
608 let testsuite = RustTestSuiteSummary {
609 package_name: test_suite.package.name().to_owned(),
610 binary: RustTestBinarySummary {
611 binary_name: test_suite.binary_name.clone(),
612 package_id: test_suite.package.id().repr().to_owned(),
613 kind: test_suite.kind.clone(),
614 binary_path: test_suite.binary_path.clone(),
615 binary_id: test_suite.binary_id.clone(),
616 build_platform: test_suite.build_platform,
617 },
618 cwd: test_suite.cwd.clone(),
619 status,
620 test_cases,
621 };
622 (test_suite.binary_id.clone(), testsuite)
623 })
624 .collect();
625 let mut summary = TestListSummary::new(self.rust_build_meta.to_summary());
626 summary.test_count = self.test_count;
627 summary.rust_suites = rust_suites;
628 summary
629 }
630
631 pub fn write(
633 &self,
634 output_format: OutputFormat,
635 writer: &mut dyn WriteStr,
636 colorize: bool,
637 ) -> Result<(), WriteTestListError> {
638 match output_format {
639 OutputFormat::Human { verbose } => self
640 .write_human(writer, verbose, colorize)
641 .map_err(WriteTestListError::Io),
642 OutputFormat::Oneline { verbose } => self
643 .write_oneline(writer, verbose, colorize)
644 .map_err(WriteTestListError::Io),
645 OutputFormat::Serializable(format) => format.to_writer(&self.to_summary(), writer),
646 }
647 }
648
649 pub fn iter(&self) -> impl Iterator<Item = &RustTestSuite<'_>> + '_ {
651 self.rust_suites.iter()
652 }
653
654 pub fn get_suite(&self, binary_id: &RustBinaryId) -> Option<&RustTestSuite<'_>> {
656 self.rust_suites.get(binary_id)
657 }
658
659 pub fn iter_tests(&self) -> impl Iterator<Item = TestInstance<'_>> + '_ {
661 self.rust_suites.iter().flat_map(|test_suite| {
662 test_suite
663 .status
664 .test_cases()
665 .map(move |case| TestInstance::new(case, test_suite))
666 })
667 }
668
669 pub fn to_priority_queue(
671 &'g self,
672 profile: &'g EvaluatableProfile<'g>,
673 ) -> TestPriorityQueue<'g> {
674 TestPriorityQueue::new(self, profile)
675 }
676
677 pub fn to_string(&self, output_format: OutputFormat) -> Result<String, WriteTestListError> {
679 let mut s = String::with_capacity(1024);
680 self.write(output_format, &mut s, false)?;
681 Ok(s)
682 }
683
684 pub fn empty() -> Self {
692 Self {
693 test_count: 0,
694 mode: NextestRunMode::Test,
695 workspace_root: Utf8PathBuf::new(),
696 rust_build_meta: RustBuildMeta::empty(),
697 env: EnvironmentMap::empty(),
698 updated_dylib_path: OsString::new(),
699 rust_suites: IdOrdMap::new(),
700 skip_counts: OnceLock::new(),
701 }
702 }
703
704 pub(crate) fn create_dylib_path(
705 rust_build_meta: &RustBuildMeta<TestListState>,
706 ) -> Result<OsString, CreateTestListError> {
707 let dylib_path = dylib_path();
708 let dylib_path_is_empty = dylib_path.is_empty();
709 let new_paths = rust_build_meta.dylib_paths();
710
711 let mut updated_dylib_path: Vec<PathBuf> =
712 Vec::with_capacity(dylib_path.len() + new_paths.len());
713 updated_dylib_path.extend(
714 new_paths
715 .iter()
716 .map(|path| path.clone().into_std_path_buf()),
717 );
718 updated_dylib_path.extend(dylib_path);
719
720 if cfg!(target_os = "macos") && dylib_path_is_empty {
727 if let Some(home) = home::home_dir() {
728 updated_dylib_path.push(home.join("lib"));
729 }
730 updated_dylib_path.push("/usr/local/lib".into());
731 updated_dylib_path.push("/usr/lib".into());
732 }
733
734 std::env::join_paths(updated_dylib_path)
735 .map_err(move |error| CreateTestListError::dylib_join_paths(new_paths, error))
736 }
737
738 fn process_output(
739 test_binary: RustTestArtifact<'g>,
740 filter: &TestFilterBuilder,
741 ecx: &EvalContext<'_>,
742 bound: FilterBound,
743 non_ignored: impl AsRef<str>,
744 ignored: impl AsRef<str>,
745 ) -> Result<RustTestSuite<'g>, CreateTestListError> {
746 let mut test_cases = IdOrdMap::new();
747
748 let mut non_ignored_filter = filter.build();
751 for (test_name, kind) in Self::parse(&test_binary.binary_id, non_ignored.as_ref())? {
752 let name = TestCaseName::new(test_name);
753 let filter_match =
754 non_ignored_filter.filter_match(&test_binary, &name, &kind, ecx, bound, false);
755 test_cases.insert_overwrite(RustTestCase {
756 name,
757 test_info: RustTestCaseSummary {
758 kind: Some(kind),
759 ignored: false,
760 filter_match,
761 },
762 });
763 }
764
765 let mut ignored_filter = filter.build();
766 for (test_name, kind) in Self::parse(&test_binary.binary_id, ignored.as_ref())? {
767 let name = TestCaseName::new(test_name);
772 let filter_match =
773 ignored_filter.filter_match(&test_binary, &name, &kind, ecx, bound, true);
774 test_cases.insert_overwrite(RustTestCase {
775 name,
776 test_info: RustTestCaseSummary {
777 kind: Some(kind),
778 ignored: true,
779 filter_match,
780 },
781 });
782 }
783
784 Ok(test_binary.into_test_suite(RustTestSuiteStatus::Listed {
785 test_cases: test_cases.into(),
786 }))
787 }
788
789 fn process_skipped(
790 test_binary: RustTestArtifact<'g>,
791 reason: BinaryMismatchReason,
792 ) -> RustTestSuite<'g> {
793 test_binary.into_test_suite(RustTestSuiteStatus::Skipped { reason })
794 }
795
796 fn parse<'a>(
798 binary_id: &'a RustBinaryId,
799 list_output: &'a str,
800 ) -> Result<Vec<(&'a str, RustTestKind)>, CreateTestListError> {
801 let mut list = parse_list_lines(binary_id, list_output).collect::<Result<Vec<_>, _>>()?;
802 list.sort_unstable();
803 Ok(list)
804 }
805
806 pub fn write_human(
808 &self,
809 writer: &mut dyn WriteStr,
810 verbose: bool,
811 colorize: bool,
812 ) -> io::Result<()> {
813 self.write_human_impl(None, writer, verbose, colorize)
814 }
815
816 pub(crate) fn write_human_with_filter(
818 &self,
819 filter: &TestListDisplayFilter<'_>,
820 writer: &mut dyn WriteStr,
821 verbose: bool,
822 colorize: bool,
823 ) -> io::Result<()> {
824 self.write_human_impl(Some(filter), writer, verbose, colorize)
825 }
826
827 fn write_human_impl(
828 &self,
829 filter: Option<&TestListDisplayFilter<'_>>,
830 mut writer: &mut dyn WriteStr,
831 verbose: bool,
832 colorize: bool,
833 ) -> io::Result<()> {
834 let mut styles = Styles::default();
835 if colorize {
836 styles.colorize();
837 }
838
839 for info in &self.rust_suites {
840 let matcher = match filter {
841 Some(filter) => match filter.matcher_for(&info.binary_id) {
842 Some(matcher) => matcher,
843 None => continue,
844 },
845 None => DisplayFilterMatcher::All,
846 };
847
848 if !verbose
851 && info
852 .status
853 .test_cases()
854 .all(|case| !case.test_info.filter_match.is_match())
855 {
856 continue;
857 }
858
859 writeln!(writer, "{}:", info.binary_id.style(styles.binary_id))?;
860 if verbose {
861 writeln!(
862 writer,
863 " {} {}",
864 "bin:".style(styles.field),
865 info.binary_path
866 )?;
867 writeln!(writer, " {} {}", "cwd:".style(styles.field), info.cwd)?;
868 writeln!(
869 writer,
870 " {} {}",
871 "build platform:".style(styles.field),
872 info.build_platform,
873 )?;
874 }
875
876 let mut indented = indented(writer).with_str(" ");
877
878 match &info.status {
879 RustTestSuiteStatus::Listed { test_cases } => {
880 let matching_tests: Vec<_> = test_cases
881 .iter()
882 .filter(|case| matcher.is_match(&case.name))
883 .collect();
884 if matching_tests.is_empty() {
885 writeln!(indented, "(no tests)")?;
886 } else {
887 for case in matching_tests {
888 match (verbose, case.test_info.filter_match.is_match()) {
889 (_, true) => {
890 write_test_name(&case.name, &styles, &mut indented)?;
891 writeln!(indented)?;
892 }
893 (true, false) => {
894 write_test_name(&case.name, &styles, &mut indented)?;
895 writeln!(indented, " (skipped)")?;
896 }
897 (false, false) => {
898 }
900 }
901 }
902 }
903 }
904 RustTestSuiteStatus::Skipped { reason } => {
905 writeln!(indented, "(test binary {reason}, skipped)")?;
906 }
907 }
908
909 writer = indented.into_inner();
910 }
911 Ok(())
912 }
913
914 pub fn write_oneline(
916 &self,
917 writer: &mut dyn WriteStr,
918 verbose: bool,
919 colorize: bool,
920 ) -> io::Result<()> {
921 let mut styles = Styles::default();
922 if colorize {
923 styles.colorize();
924 }
925
926 for info in &self.rust_suites {
927 match &info.status {
928 RustTestSuiteStatus::Listed { test_cases } => {
929 for case in test_cases.iter() {
930 let is_match = case.test_info.filter_match.is_match();
931 if !verbose && !is_match {
933 continue;
934 }
935
936 write!(writer, "{} ", info.binary_id.style(styles.binary_id))?;
937 write_test_name(&case.name, &styles, writer)?;
938
939 if verbose {
940 write!(
941 writer,
942 " [{}{}] [{}{}] [{}{}]{}",
943 "bin: ".style(styles.field),
944 info.binary_path,
945 "cwd: ".style(styles.field),
946 info.cwd,
947 "build platform: ".style(styles.field),
948 info.build_platform,
949 if is_match { "" } else { " (skipped)" },
950 )?;
951 }
952
953 writeln!(writer)?;
954 }
955 }
956 RustTestSuiteStatus::Skipped { .. } => {
957 }
959 }
960 }
961
962 Ok(())
963 }
964}
965
966fn parse_list_lines<'a>(
967 binary_id: &'a RustBinaryId,
968 list_output: &'a str,
969) -> impl Iterator<Item = Result<(&'a str, RustTestKind), CreateTestListError>> + 'a + use<'a> {
970 list_output
976 .lines()
977 .map(move |line| match line.strip_suffix(": test") {
978 Some(test_name) => Ok((test_name, RustTestKind::TEST)),
979 None => match line.strip_suffix(": benchmark") {
980 Some(test_name) => Ok((test_name, RustTestKind::BENCH)),
981 None => Err(CreateTestListError::parse_line(
982 binary_id.clone(),
983 format!(
984 "line {line:?} did not end with the string \": test\" or \": benchmark\""
985 ),
986 list_output,
987 )),
988 },
989 })
990}
991
992pub trait ListProfile {
994 fn filterset_ecx(&self) -> EvalContext<'_>;
996
997 fn list_settings_for(&self, query: &BinaryQuery<'_>) -> ListSettings<'_>;
999}
1000
1001impl<'g> ListProfile for EvaluatableProfile<'g> {
1002 fn filterset_ecx(&self) -> EvalContext<'_> {
1003 self.filterset_ecx()
1004 }
1005
1006 fn list_settings_for(&self, query: &BinaryQuery<'_>) -> ListSettings<'_> {
1007 self.list_settings_for(query)
1008 }
1009}
1010
1011pub struct TestPriorityQueue<'a> {
1013 tests: Vec<TestInstanceWithSettings<'a>>,
1014}
1015
1016impl<'a> TestPriorityQueue<'a> {
1017 fn new(test_list: &'a TestList<'a>, profile: &'a EvaluatableProfile<'a>) -> Self {
1018 let mode = test_list.mode();
1019 let mut tests = test_list
1020 .iter_tests()
1021 .map(|instance| {
1022 let settings = profile.settings_for(mode, &instance.to_test_query());
1023 TestInstanceWithSettings { instance, settings }
1024 })
1025 .collect::<Vec<_>>();
1026 tests.sort_by_key(|test| test.settings.priority());
1029
1030 Self { tests }
1031 }
1032}
1033
1034impl<'a> IntoIterator for TestPriorityQueue<'a> {
1035 type Item = TestInstanceWithSettings<'a>;
1036 type IntoIter = std::vec::IntoIter<Self::Item>;
1037
1038 fn into_iter(self) -> Self::IntoIter {
1039 self.tests.into_iter()
1040 }
1041}
1042
1043#[derive(Debug)]
1047pub struct TestInstanceWithSettings<'a> {
1048 pub instance: TestInstance<'a>,
1050
1051 pub settings: TestSettings<'a>,
1053}
1054
1055#[derive(Clone, Debug, Eq, PartialEq)]
1059pub struct RustTestSuite<'g> {
1060 pub binary_id: RustBinaryId,
1062
1063 pub binary_path: Utf8PathBuf,
1065
1066 pub package: PackageMetadata<'g>,
1068
1069 pub binary_name: String,
1071
1072 pub kind: RustTestBinaryKind,
1074
1075 pub cwd: Utf8PathBuf,
1078
1079 pub build_platform: BuildPlatform,
1081
1082 pub non_test_binaries: BTreeSet<(String, Utf8PathBuf)>,
1084
1085 pub status: RustTestSuiteStatus,
1087}
1088
1089impl IdOrdItem for RustTestSuite<'_> {
1090 type Key<'a>
1091 = &'a RustBinaryId
1092 where
1093 Self: 'a;
1094
1095 fn key(&self) -> Self::Key<'_> {
1096 &self.binary_id
1097 }
1098
1099 id_upcast!();
1100}
1101
1102impl RustTestArtifact<'_> {
1103 async fn exec(
1105 &self,
1106 lctx: &LocalExecuteContext<'_>,
1107 list_settings: &ListSettings<'_>,
1108 target_runner: &TargetRunner,
1109 ) -> Result<(String, String), CreateTestListError> {
1110 if !self.cwd.is_dir() {
1113 return Err(CreateTestListError::CwdIsNotDir {
1114 binary_id: self.binary_id.clone(),
1115 cwd: self.cwd.clone(),
1116 });
1117 }
1118 let platform_runner = target_runner.for_build_platform(self.build_platform);
1119
1120 let non_ignored = self.exec_single(false, lctx, list_settings, platform_runner);
1121 let ignored = self.exec_single(true, lctx, list_settings, platform_runner);
1122
1123 let (non_ignored_out, ignored_out) = futures::future::join(non_ignored, ignored).await;
1124 Ok((non_ignored_out?, ignored_out?))
1125 }
1126
1127 async fn exec_single(
1128 &self,
1129 ignored: bool,
1130 lctx: &LocalExecuteContext<'_>,
1131 list_settings: &ListSettings<'_>,
1132 runner: Option<&PlatformRunner>,
1133 ) -> Result<String, CreateTestListError> {
1134 let mut cli = TestCommandCli::default();
1135 cli.apply_wrappers(
1136 list_settings.list_wrapper(),
1137 runner,
1138 lctx.workspace_root,
1139 &lctx.rust_build_meta.target_directory,
1140 );
1141 cli.push(self.binary_path.as_str());
1142
1143 cli.extend(["--list", "--format", "terse"]);
1144 if ignored {
1145 cli.push("--ignored");
1146 }
1147
1148 let cmd = TestCommand::new(
1149 lctx,
1150 cli.program
1151 .clone()
1152 .expect("at least one argument passed in")
1153 .into_owned(),
1154 &cli.args,
1155 &self.cwd,
1156 &self.package,
1157 &self.non_test_binaries,
1158 &Interceptor::None, );
1160
1161 let output =
1162 cmd.wait_with_output()
1163 .await
1164 .map_err(|error| CreateTestListError::CommandExecFail {
1165 binary_id: self.binary_id.clone(),
1166 command: cli.to_owned_cli(),
1167 error,
1168 })?;
1169
1170 if output.status.success() {
1171 String::from_utf8(output.stdout).map_err(|err| CreateTestListError::CommandNonUtf8 {
1172 binary_id: self.binary_id.clone(),
1173 command: cli.to_owned_cli(),
1174 stdout: err.into_bytes(),
1175 stderr: output.stderr,
1176 })
1177 } else {
1178 Err(CreateTestListError::CommandFail {
1179 binary_id: self.binary_id.clone(),
1180 command: cli.to_owned_cli(),
1181 exit_status: output.status,
1182 stdout: output.stdout,
1183 stderr: output.stderr,
1184 })
1185 }
1186 }
1187}
1188
1189#[derive(Clone, Debug, Eq, PartialEq)]
1193pub enum RustTestSuiteStatus {
1194 Listed {
1196 test_cases: DebugIgnore<IdOrdMap<RustTestCase>>,
1198 },
1199
1200 Skipped {
1202 reason: BinaryMismatchReason,
1204 },
1205}
1206
1207static EMPTY_TEST_CASE_MAP: IdOrdMap<RustTestCase> = IdOrdMap::new();
1208
1209impl RustTestSuiteStatus {
1210 pub fn test_count(&self) -> usize {
1212 match self {
1213 RustTestSuiteStatus::Listed { test_cases } => test_cases.len(),
1214 RustTestSuiteStatus::Skipped { .. } => 0,
1215 }
1216 }
1217
1218 pub fn test_cases(&self) -> impl Iterator<Item = &RustTestCase> + '_ {
1220 match self {
1221 RustTestSuiteStatus::Listed { test_cases } => test_cases.iter(),
1222 RustTestSuiteStatus::Skipped { .. } => {
1223 EMPTY_TEST_CASE_MAP.iter()
1225 }
1226 }
1227 }
1228
1229 pub fn to_summary(
1231 &self,
1232 ) -> (
1233 RustTestSuiteStatusSummary,
1234 BTreeMap<TestCaseName, RustTestCaseSummary>,
1235 ) {
1236 match self {
1237 Self::Listed { test_cases } => (
1238 RustTestSuiteStatusSummary::LISTED,
1239 test_cases
1240 .iter()
1241 .cloned()
1242 .map(|case| (case.name, case.test_info))
1243 .collect(),
1244 ),
1245 Self::Skipped {
1246 reason: BinaryMismatchReason::Expression,
1247 } => (RustTestSuiteStatusSummary::SKIPPED, BTreeMap::new()),
1248 Self::Skipped {
1249 reason: BinaryMismatchReason::DefaultSet,
1250 } => (
1251 RustTestSuiteStatusSummary::SKIPPED_DEFAULT_FILTER,
1252 BTreeMap::new(),
1253 ),
1254 }
1255 }
1256}
1257
1258#[derive(Clone, Debug, Eq, PartialEq)]
1260pub struct RustTestCase {
1261 pub name: TestCaseName,
1263
1264 pub test_info: RustTestCaseSummary,
1266}
1267
1268impl IdOrdItem for RustTestCase {
1269 type Key<'a> = &'a TestCaseName;
1270 fn key(&self) -> Self::Key<'_> {
1271 &self.name
1272 }
1273 id_upcast!();
1274}
1275
1276#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1278pub struct TestInstance<'a> {
1279 pub name: &'a TestCaseName,
1281
1282 pub suite_info: &'a RustTestSuite<'a>,
1284
1285 pub test_info: &'a RustTestCaseSummary,
1287}
1288
1289impl<'a> TestInstance<'a> {
1290 pub(crate) fn new(case: &'a RustTestCase, suite_info: &'a RustTestSuite) -> Self {
1292 Self {
1293 name: &case.name,
1294 suite_info,
1295 test_info: &case.test_info,
1296 }
1297 }
1298
1299 #[inline]
1302 pub fn id(&self) -> TestInstanceId<'a> {
1303 TestInstanceId {
1304 binary_id: &self.suite_info.binary_id,
1305 test_name: self.name,
1306 }
1307 }
1308
1309 pub fn to_test_query(&self) -> TestQuery<'a> {
1311 TestQuery {
1312 binary_query: BinaryQuery {
1313 package_id: self.suite_info.package.id(),
1314 binary_id: &self.suite_info.binary_id,
1315 kind: &self.suite_info.kind,
1316 binary_name: &self.suite_info.binary_name,
1317 platform: convert_build_platform(self.suite_info.build_platform),
1318 },
1319 test_name: self.name,
1320 }
1321 }
1322
1323 pub(crate) fn make_command(
1325 &self,
1326 ctx: &TestExecuteContext<'_>,
1327 test_list: &TestList<'_>,
1328 wrapper_script: Option<&WrapperScriptConfig>,
1329 extra_args: &[String],
1330 interceptor: &Interceptor,
1331 ) -> TestCommand {
1332 let cli = self.compute_cli(ctx, test_list, wrapper_script, extra_args);
1334
1335 let lctx = LocalExecuteContext {
1336 phase: TestCommandPhase::Run,
1337 workspace_root: test_list.workspace_root(),
1338 rust_build_meta: &test_list.rust_build_meta,
1339 double_spawn: ctx.double_spawn,
1340 dylib_path: test_list.updated_dylib_path(),
1341 profile_name: ctx.profile_name,
1342 env: &test_list.env,
1343 };
1344
1345 TestCommand::new(
1346 &lctx,
1347 cli.program
1348 .expect("at least one argument is guaranteed")
1349 .into_owned(),
1350 &cli.args,
1351 &self.suite_info.cwd,
1352 &self.suite_info.package,
1353 &self.suite_info.non_test_binaries,
1354 interceptor,
1355 )
1356 }
1357
1358 pub(crate) fn command_line(
1359 &self,
1360 ctx: &TestExecuteContext<'_>,
1361 test_list: &TestList<'_>,
1362 wrapper_script: Option<&WrapperScriptConfig>,
1363 extra_args: &[String],
1364 ) -> Vec<String> {
1365 self.compute_cli(ctx, test_list, wrapper_script, extra_args)
1366 .to_owned_cli()
1367 }
1368
1369 fn compute_cli(
1370 &self,
1371 ctx: &'a TestExecuteContext<'_>,
1372 test_list: &TestList<'_>,
1373 wrapper_script: Option<&'a WrapperScriptConfig>,
1374 extra_args: &'a [String],
1375 ) -> TestCommandCli<'a> {
1376 let platform_runner = ctx
1377 .target_runner
1378 .for_build_platform(self.suite_info.build_platform);
1379
1380 let mut cli = TestCommandCli::default();
1381 cli.apply_wrappers(
1382 wrapper_script,
1383 platform_runner,
1384 test_list.workspace_root(),
1385 &test_list.rust_build_meta().target_directory,
1386 );
1387 cli.push(self.suite_info.binary_path.as_str());
1388
1389 cli.extend(["--exact", self.name.as_str(), "--nocapture"]);
1390 if self.test_info.ignored {
1391 cli.push("--ignored");
1392 }
1393 match test_list.mode() {
1394 NextestRunMode::Test => {}
1395 NextestRunMode::Benchmark => {
1396 cli.push("--bench");
1397 }
1398 }
1399 cli.extend(extra_args.iter().map(String::as_str));
1400
1401 cli
1402 }
1403}
1404
1405#[derive(Clone, Debug, Default)]
1406struct TestCommandCli<'a> {
1407 program: Option<Cow<'a, str>>,
1408 args: Vec<Cow<'a, str>>,
1409}
1410
1411impl<'a> TestCommandCli<'a> {
1412 fn apply_wrappers(
1413 &mut self,
1414 wrapper_script: Option<&'a WrapperScriptConfig>,
1415 platform_runner: Option<&'a PlatformRunner>,
1416 workspace_root: &Utf8Path,
1417 target_dir: &Utf8Path,
1418 ) {
1419 if let Some(wrapper) = wrapper_script {
1421 match wrapper.target_runner {
1422 WrapperScriptTargetRunner::Ignore => {
1423 self.push(wrapper.command.program(workspace_root, target_dir));
1425 self.extend(wrapper.command.args.iter().map(String::as_str));
1426 }
1427 WrapperScriptTargetRunner::AroundWrapper => {
1428 if let Some(runner) = platform_runner {
1430 self.push(runner.binary());
1431 self.extend(runner.args());
1432 }
1433 self.push(wrapper.command.program(workspace_root, target_dir));
1434 self.extend(wrapper.command.args.iter().map(String::as_str));
1435 }
1436 WrapperScriptTargetRunner::WithinWrapper => {
1437 self.push(wrapper.command.program(workspace_root, target_dir));
1439 self.extend(wrapper.command.args.iter().map(String::as_str));
1440 if let Some(runner) = platform_runner {
1441 self.push(runner.binary());
1442 self.extend(runner.args());
1443 }
1444 }
1445 WrapperScriptTargetRunner::OverridesWrapper => {
1446 if let Some(runner) = platform_runner {
1448 self.push(runner.binary());
1449 self.extend(runner.args());
1450 }
1451 }
1452 }
1453 } else {
1454 if let Some(runner) = platform_runner {
1456 self.push(runner.binary());
1457 self.extend(runner.args());
1458 }
1459 }
1460 }
1461
1462 fn push(&mut self, arg: impl Into<Cow<'a, str>>) {
1463 if self.program.is_none() {
1464 self.program = Some(arg.into());
1465 } else {
1466 self.args.push(arg.into());
1467 }
1468 }
1469
1470 fn extend(&mut self, args: impl IntoIterator<Item = &'a str>) {
1471 for arg in args {
1472 self.push(arg);
1473 }
1474 }
1475
1476 fn to_owned_cli(&self) -> Vec<String> {
1477 let mut owned_cli = Vec::new();
1478 if let Some(program) = &self.program {
1479 owned_cli.push(program.to_string());
1480 }
1481 owned_cli.extend(self.args.iter().map(|arg| arg.clone().into_owned()));
1482 owned_cli
1483 }
1484}
1485
1486#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)]
1490pub struct TestInstanceId<'a> {
1491 pub binary_id: &'a RustBinaryId,
1493
1494 pub test_name: &'a TestCaseName,
1496}
1497
1498impl TestInstanceId<'_> {
1499 pub fn attempt_id(
1503 &self,
1504 run_id: ReportUuid,
1505 stress_index: Option<u32>,
1506 attempt: u32,
1507 ) -> String {
1508 let mut out = String::new();
1509 swrite!(out, "{run_id}:{}", self.binary_id);
1510 if let Some(stress_index) = stress_index {
1511 swrite!(out, "@stress-{}", stress_index);
1512 }
1513 swrite!(out, "${}", self.test_name);
1514 if attempt > 1 {
1515 swrite!(out, "#{attempt}");
1516 }
1517
1518 out
1519 }
1520}
1521
1522impl fmt::Display for TestInstanceId<'_> {
1523 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1524 write!(f, "{} {}", self.binary_id, self.test_name)
1525 }
1526}
1527
1528#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
1530#[serde(rename_all = "kebab-case")]
1531#[cfg_attr(test, derive(test_strategy::Arbitrary))]
1532pub struct OwnedTestInstanceId {
1533 pub binary_id: RustBinaryId,
1535
1536 #[serde(rename = "name")]
1538 pub test_name: TestCaseName,
1539}
1540
1541impl OwnedTestInstanceId {
1542 pub fn as_ref(&self) -> TestInstanceId<'_> {
1544 TestInstanceId {
1545 binary_id: &self.binary_id,
1546 test_name: &self.test_name,
1547 }
1548 }
1549}
1550
1551impl TestInstanceId<'_> {
1552 pub fn to_owned(&self) -> OwnedTestInstanceId {
1554 OwnedTestInstanceId {
1555 binary_id: self.binary_id.clone(),
1556 test_name: self.test_name.clone(),
1557 }
1558 }
1559}
1560
1561pub trait TestInstanceIdKey {
1567 fn key<'k>(&'k self) -> TestInstanceId<'k>;
1569}
1570
1571impl TestInstanceIdKey for OwnedTestInstanceId {
1572 fn key<'k>(&'k self) -> TestInstanceId<'k> {
1573 TestInstanceId {
1574 binary_id: &self.binary_id,
1575 test_name: &self.test_name,
1576 }
1577 }
1578}
1579
1580impl<'a> TestInstanceIdKey for TestInstanceId<'a> {
1581 fn key<'k>(&'k self) -> TestInstanceId<'k> {
1582 *self
1583 }
1584}
1585
1586impl<'a> Borrow<dyn TestInstanceIdKey + 'a> for OwnedTestInstanceId {
1587 fn borrow(&self) -> &(dyn TestInstanceIdKey + 'a) {
1588 self
1589 }
1590}
1591
1592impl<'a> PartialEq for dyn TestInstanceIdKey + 'a {
1593 fn eq(&self, other: &(dyn TestInstanceIdKey + 'a)) -> bool {
1594 self.key() == other.key()
1595 }
1596}
1597
1598impl<'a> Eq for dyn TestInstanceIdKey + 'a {}
1599
1600impl<'a> PartialOrd for dyn TestInstanceIdKey + 'a {
1601 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1602 Some(self.cmp(other))
1603 }
1604}
1605
1606impl<'a> Ord for dyn TestInstanceIdKey + 'a {
1607 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1608 self.key().cmp(&other.key())
1609 }
1610}
1611
1612impl<'a> Hash for dyn TestInstanceIdKey + 'a {
1613 fn hash<H: Hasher>(&self, state: &mut H) {
1614 self.key().hash(state);
1615 }
1616}
1617
1618#[derive(Clone, Debug)]
1620pub struct TestExecuteContext<'a> {
1621 pub profile_name: &'a str,
1623
1624 pub double_spawn: &'a DoubleSpawnInfo,
1626
1627 pub target_runner: &'a TargetRunner,
1629}
1630
1631#[cfg(test)]
1632mod tests {
1633 use super::*;
1634 use crate::{
1635 cargo_config::{TargetDefinitionLocation, TargetTriple, TargetTripleSource},
1636 config::scripts::{ScriptCommand, ScriptCommandRelativeTo},
1637 list::SerializableFormat,
1638 platform::{BuildPlatforms, HostPlatform, PlatformLibdir, TargetPlatform},
1639 target_runner::PlatformRunnerSource,
1640 test_filter::{RunIgnored, TestFilterPatterns},
1641 };
1642 use guppy::CargoMetadata;
1643 use iddqd::id_ord_map;
1644 use indoc::indoc;
1645 use nextest_filtering::{CompiledExpr, Filterset, FiltersetKind, ParseContext};
1646 use nextest_metadata::{FilterMatch, MismatchReason, PlatformLibdirUnavailable, RustTestKind};
1647 use pretty_assertions::assert_eq;
1648 use std::{hash::DefaultHasher, sync::LazyLock};
1649 use target_spec::Platform;
1650 use test_strategy::proptest;
1651
1652 #[test]
1653 fn test_parse_test_list() {
1654 let non_ignored_output = indoc! {"
1656 tests::foo::test_bar: test
1657 tests::baz::test_quux: test
1658 benches::bench_foo: benchmark
1659 "};
1660 let ignored_output = indoc! {"
1661 tests::ignored::test_bar: test
1662 tests::baz::test_ignored: test
1663 benches::ignored_bench_foo: benchmark
1664 "};
1665
1666 let cx = ParseContext::new(&PACKAGE_GRAPH_FIXTURE);
1667
1668 let test_filter = TestFilterBuilder::new(
1669 NextestRunMode::Test,
1670 RunIgnored::Default,
1671 None,
1672 TestFilterPatterns::default(),
1673 vec![
1675 Filterset::parse("platform(target)".to_owned(), &cx, FiltersetKind::Test).unwrap(),
1676 ],
1677 )
1678 .unwrap();
1679 let fake_cwd: Utf8PathBuf = "/fake/cwd".into();
1680 let fake_binary_name = "fake-binary".to_owned();
1681 let fake_binary_id = RustBinaryId::new("fake-package::fake-binary");
1682
1683 let test_binary = RustTestArtifact {
1684 binary_path: "/fake/binary".into(),
1685 cwd: fake_cwd.clone(),
1686 package: package_metadata(),
1687 binary_name: fake_binary_name.clone(),
1688 binary_id: fake_binary_id.clone(),
1689 kind: RustTestBinaryKind::LIB,
1690 non_test_binaries: BTreeSet::new(),
1691 build_platform: BuildPlatform::Target,
1692 };
1693
1694 let skipped_binary_name = "skipped-binary".to_owned();
1695 let skipped_binary_id = RustBinaryId::new("fake-package::skipped-binary");
1696 let skipped_binary = RustTestArtifact {
1697 binary_path: "/fake/skipped-binary".into(),
1698 cwd: fake_cwd.clone(),
1699 package: package_metadata(),
1700 binary_name: skipped_binary_name.clone(),
1701 binary_id: skipped_binary_id.clone(),
1702 kind: RustTestBinaryKind::PROC_MACRO,
1703 non_test_binaries: BTreeSet::new(),
1704 build_platform: BuildPlatform::Host,
1705 };
1706
1707 let fake_triple = TargetTriple {
1708 platform: Platform::new(
1709 "aarch64-unknown-linux-gnu",
1710 target_spec::TargetFeatures::Unknown,
1711 )
1712 .unwrap(),
1713 source: TargetTripleSource::CliOption,
1714 location: TargetDefinitionLocation::Builtin,
1715 };
1716 let fake_host_libdir = "/home/fake/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib";
1717 let build_platforms = BuildPlatforms {
1718 host: HostPlatform {
1719 platform: TargetTriple::x86_64_unknown_linux_gnu().platform,
1720 libdir: PlatformLibdir::Available(fake_host_libdir.into()),
1721 },
1722 target: Some(TargetPlatform {
1723 triple: fake_triple,
1724 libdir: PlatformLibdir::Unavailable(PlatformLibdirUnavailable::new_const("test")),
1726 }),
1727 };
1728
1729 let fake_env = EnvironmentMap::empty();
1730 let rust_build_meta =
1731 RustBuildMeta::new("/fake", build_platforms).map_paths(&PathMapper::noop());
1732 let ecx = EvalContext {
1733 default_filter: &CompiledExpr::ALL,
1734 };
1735 let test_list = TestList::new_with_outputs(
1736 [
1737 (test_binary, &non_ignored_output, &ignored_output),
1738 (
1739 skipped_binary,
1740 &"should-not-show-up-stdout",
1741 &"should-not-show-up-stderr",
1742 ),
1743 ],
1744 Utf8PathBuf::from("/fake/path"),
1745 rust_build_meta,
1746 &test_filter,
1747 fake_env,
1748 &ecx,
1749 FilterBound::All,
1750 )
1751 .expect("valid output");
1752 assert_eq!(
1753 test_list.rust_suites,
1754 id_ord_map! {
1755 RustTestSuite {
1756 status: RustTestSuiteStatus::Listed {
1757 test_cases: id_ord_map! {
1758 RustTestCase {
1759 name: TestCaseName::new("tests::foo::test_bar"),
1760 test_info: RustTestCaseSummary {
1761 kind: Some(RustTestKind::TEST),
1762 ignored: false,
1763 filter_match: FilterMatch::Matches,
1764 },
1765 },
1766 RustTestCase {
1767 name: TestCaseName::new("tests::baz::test_quux"),
1768 test_info: RustTestCaseSummary {
1769 kind: Some(RustTestKind::TEST),
1770 ignored: false,
1771 filter_match: FilterMatch::Matches,
1772 },
1773 },
1774 RustTestCase {
1775 name: TestCaseName::new("benches::bench_foo"),
1776 test_info: RustTestCaseSummary {
1777 kind: Some(RustTestKind::BENCH),
1778 ignored: false,
1779 filter_match: FilterMatch::Matches,
1780 },
1781 },
1782 RustTestCase {
1783 name: TestCaseName::new("tests::ignored::test_bar"),
1784 test_info: RustTestCaseSummary {
1785 kind: Some(RustTestKind::TEST),
1786 ignored: true,
1787 filter_match: FilterMatch::Mismatch { reason: MismatchReason::Ignored },
1788 },
1789 },
1790 RustTestCase {
1791 name: TestCaseName::new("tests::baz::test_ignored"),
1792 test_info: RustTestCaseSummary {
1793 kind: Some(RustTestKind::TEST),
1794 ignored: true,
1795 filter_match: FilterMatch::Mismatch { reason: MismatchReason::Ignored },
1796 },
1797 },
1798 RustTestCase {
1799 name: TestCaseName::new("benches::ignored_bench_foo"),
1800 test_info: RustTestCaseSummary {
1801 kind: Some(RustTestKind::BENCH),
1802 ignored: true,
1803 filter_match: FilterMatch::Mismatch { reason: MismatchReason::Ignored },
1804 },
1805 },
1806 }.into(),
1807 },
1808 cwd: fake_cwd.clone(),
1809 build_platform: BuildPlatform::Target,
1810 package: package_metadata(),
1811 binary_name: fake_binary_name,
1812 binary_id: fake_binary_id,
1813 binary_path: "/fake/binary".into(),
1814 kind: RustTestBinaryKind::LIB,
1815 non_test_binaries: BTreeSet::new(),
1816 },
1817 RustTestSuite {
1818 status: RustTestSuiteStatus::Skipped {
1819 reason: BinaryMismatchReason::Expression,
1820 },
1821 cwd: fake_cwd,
1822 build_platform: BuildPlatform::Host,
1823 package: package_metadata(),
1824 binary_name: skipped_binary_name,
1825 binary_id: skipped_binary_id,
1826 binary_path: "/fake/skipped-binary".into(),
1827 kind: RustTestBinaryKind::PROC_MACRO,
1828 non_test_binaries: BTreeSet::new(),
1829 },
1830 }
1831 );
1832
1833 static EXPECTED_HUMAN: &str = indoc! {"
1835 fake-package::fake-binary:
1836 benches::bench_foo
1837 tests::baz::test_quux
1838 tests::foo::test_bar
1839 "};
1840 static EXPECTED_HUMAN_VERBOSE: &str = indoc! {"
1841 fake-package::fake-binary:
1842 bin: /fake/binary
1843 cwd: /fake/cwd
1844 build platform: target
1845 benches::bench_foo
1846 benches::ignored_bench_foo (skipped)
1847 tests::baz::test_ignored (skipped)
1848 tests::baz::test_quux
1849 tests::foo::test_bar
1850 tests::ignored::test_bar (skipped)
1851 fake-package::skipped-binary:
1852 bin: /fake/skipped-binary
1853 cwd: /fake/cwd
1854 build platform: host
1855 (test binary didn't match filtersets, skipped)
1856 "};
1857 static EXPECTED_JSON_PRETTY: &str = indoc! {r#"
1858 {
1859 "rust-build-meta": {
1860 "target-directory": "/fake",
1861 "base-output-directories": [],
1862 "non-test-binaries": {},
1863 "build-script-out-dirs": {},
1864 "linked-paths": [],
1865 "platforms": {
1866 "host": {
1867 "platform": {
1868 "triple": "x86_64-unknown-linux-gnu",
1869 "target-features": "unknown"
1870 },
1871 "libdir": {
1872 "status": "available",
1873 "path": "/home/fake/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib"
1874 }
1875 },
1876 "targets": [
1877 {
1878 "platform": {
1879 "triple": "aarch64-unknown-linux-gnu",
1880 "target-features": "unknown"
1881 },
1882 "libdir": {
1883 "status": "unavailable",
1884 "reason": "test"
1885 }
1886 }
1887 ]
1888 },
1889 "target-platforms": [
1890 {
1891 "triple": "aarch64-unknown-linux-gnu",
1892 "target-features": "unknown"
1893 }
1894 ],
1895 "target-platform": "aarch64-unknown-linux-gnu"
1896 },
1897 "test-count": 6,
1898 "rust-suites": {
1899 "fake-package::fake-binary": {
1900 "package-name": "metadata-helper",
1901 "binary-id": "fake-package::fake-binary",
1902 "binary-name": "fake-binary",
1903 "package-id": "metadata-helper 0.1.0 (path+file:///Users/fakeuser/local/testcrates/metadata/metadata-helper)",
1904 "kind": "lib",
1905 "binary-path": "/fake/binary",
1906 "build-platform": "target",
1907 "cwd": "/fake/cwd",
1908 "status": "listed",
1909 "testcases": {
1910 "benches::bench_foo": {
1911 "kind": "bench",
1912 "ignored": false,
1913 "filter-match": {
1914 "status": "matches"
1915 }
1916 },
1917 "benches::ignored_bench_foo": {
1918 "kind": "bench",
1919 "ignored": true,
1920 "filter-match": {
1921 "status": "mismatch",
1922 "reason": "ignored"
1923 }
1924 },
1925 "tests::baz::test_ignored": {
1926 "kind": "test",
1927 "ignored": true,
1928 "filter-match": {
1929 "status": "mismatch",
1930 "reason": "ignored"
1931 }
1932 },
1933 "tests::baz::test_quux": {
1934 "kind": "test",
1935 "ignored": false,
1936 "filter-match": {
1937 "status": "matches"
1938 }
1939 },
1940 "tests::foo::test_bar": {
1941 "kind": "test",
1942 "ignored": false,
1943 "filter-match": {
1944 "status": "matches"
1945 }
1946 },
1947 "tests::ignored::test_bar": {
1948 "kind": "test",
1949 "ignored": true,
1950 "filter-match": {
1951 "status": "mismatch",
1952 "reason": "ignored"
1953 }
1954 }
1955 }
1956 },
1957 "fake-package::skipped-binary": {
1958 "package-name": "metadata-helper",
1959 "binary-id": "fake-package::skipped-binary",
1960 "binary-name": "skipped-binary",
1961 "package-id": "metadata-helper 0.1.0 (path+file:///Users/fakeuser/local/testcrates/metadata/metadata-helper)",
1962 "kind": "proc-macro",
1963 "binary-path": "/fake/skipped-binary",
1964 "build-platform": "host",
1965 "cwd": "/fake/cwd",
1966 "status": "skipped",
1967 "testcases": {}
1968 }
1969 }
1970 }"#};
1971 static EXPECTED_ONELINE: &str = indoc! {"
1972 fake-package::fake-binary benches::bench_foo
1973 fake-package::fake-binary tests::baz::test_quux
1974 fake-package::fake-binary tests::foo::test_bar
1975 "};
1976 static EXPECTED_ONELINE_VERBOSE: &str = indoc! {"
1977 fake-package::fake-binary benches::bench_foo [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target]
1978 fake-package::fake-binary benches::ignored_bench_foo [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target] (skipped)
1979 fake-package::fake-binary tests::baz::test_ignored [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target] (skipped)
1980 fake-package::fake-binary tests::baz::test_quux [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target]
1981 fake-package::fake-binary tests::foo::test_bar [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target]
1982 fake-package::fake-binary tests::ignored::test_bar [bin: /fake/binary] [cwd: /fake/cwd] [build platform: target] (skipped)
1983 "};
1984
1985 assert_eq!(
1986 test_list
1987 .to_string(OutputFormat::Human { verbose: false })
1988 .expect("human succeeded"),
1989 EXPECTED_HUMAN
1990 );
1991 assert_eq!(
1992 test_list
1993 .to_string(OutputFormat::Human { verbose: true })
1994 .expect("human succeeded"),
1995 EXPECTED_HUMAN_VERBOSE
1996 );
1997 println!(
1998 "{}",
1999 test_list
2000 .to_string(OutputFormat::Serializable(SerializableFormat::JsonPretty))
2001 .expect("json-pretty succeeded")
2002 );
2003 assert_eq!(
2004 test_list
2005 .to_string(OutputFormat::Serializable(SerializableFormat::JsonPretty))
2006 .expect("json-pretty succeeded"),
2007 EXPECTED_JSON_PRETTY
2008 );
2009 assert_eq!(
2010 test_list
2011 .to_string(OutputFormat::Oneline { verbose: false })
2012 .expect("oneline succeeded"),
2013 EXPECTED_ONELINE
2014 );
2015 assert_eq!(
2016 test_list
2017 .to_string(OutputFormat::Oneline { verbose: true })
2018 .expect("oneline verbose succeeded"),
2019 EXPECTED_ONELINE_VERBOSE
2020 );
2021 }
2022
2023 #[test]
2024 fn apply_wrappers_examples() {
2025 cfg_if::cfg_if! {
2026 if #[cfg(windows)]
2027 {
2028 let workspace_root = Utf8Path::new("D:\\workspace\\root");
2029 let target_dir = Utf8Path::new("C:\\foo\\bar");
2030 } else {
2031 let workspace_root = Utf8Path::new("/workspace/root");
2032 let target_dir = Utf8Path::new("/foo/bar");
2033 }
2034 };
2035
2036 {
2038 let mut cli_no_wrappers = TestCommandCli::default();
2039 cli_no_wrappers.apply_wrappers(None, None, workspace_root, target_dir);
2040 cli_no_wrappers.extend(["binary", "arg"]);
2041 assert_eq!(cli_no_wrappers.to_owned_cli(), vec!["binary", "arg"]);
2042 }
2043
2044 {
2046 let runner = PlatformRunner::debug_new(
2047 "runner".into(),
2048 Vec::new(),
2049 PlatformRunnerSource::Env("fake".to_owned()),
2050 );
2051 let mut cli_runner_only = TestCommandCli::default();
2052 cli_runner_only.apply_wrappers(None, Some(&runner), workspace_root, target_dir);
2053 cli_runner_only.extend(["binary", "arg"]);
2054 assert_eq!(
2055 cli_runner_only.to_owned_cli(),
2056 vec!["runner", "binary", "arg"],
2057 );
2058 }
2059
2060 {
2062 let runner = PlatformRunner::debug_new(
2063 "runner".into(),
2064 Vec::new(),
2065 PlatformRunnerSource::Env("fake".to_owned()),
2066 );
2067 let wrapper_ignore = WrapperScriptConfig {
2068 command: ScriptCommand {
2069 program: "wrapper".into(),
2070 args: Vec::new(),
2071 relative_to: ScriptCommandRelativeTo::None,
2072 },
2073 target_runner: WrapperScriptTargetRunner::Ignore,
2074 };
2075 let mut cli_wrapper_ignore = TestCommandCli::default();
2076 cli_wrapper_ignore.apply_wrappers(
2077 Some(&wrapper_ignore),
2078 Some(&runner),
2079 workspace_root,
2080 target_dir,
2081 );
2082 cli_wrapper_ignore.extend(["binary", "arg"]);
2083 assert_eq!(
2084 cli_wrapper_ignore.to_owned_cli(),
2085 vec!["wrapper", "binary", "arg"],
2086 );
2087 }
2088
2089 {
2091 let runner = PlatformRunner::debug_new(
2092 "runner".into(),
2093 Vec::new(),
2094 PlatformRunnerSource::Env("fake".to_owned()),
2095 );
2096 let wrapper_around = WrapperScriptConfig {
2097 command: ScriptCommand {
2098 program: "wrapper".into(),
2099 args: Vec::new(),
2100 relative_to: ScriptCommandRelativeTo::None,
2101 },
2102 target_runner: WrapperScriptTargetRunner::AroundWrapper,
2103 };
2104 let mut cli_wrapper_around = TestCommandCli::default();
2105 cli_wrapper_around.apply_wrappers(
2106 Some(&wrapper_around),
2107 Some(&runner),
2108 workspace_root,
2109 target_dir,
2110 );
2111 cli_wrapper_around.extend(["binary", "arg"]);
2112 assert_eq!(
2113 cli_wrapper_around.to_owned_cli(),
2114 vec!["runner", "wrapper", "binary", "arg"],
2115 );
2116 }
2117
2118 {
2120 let runner = PlatformRunner::debug_new(
2121 "runner".into(),
2122 Vec::new(),
2123 PlatformRunnerSource::Env("fake".to_owned()),
2124 );
2125 let wrapper_within = WrapperScriptConfig {
2126 command: ScriptCommand {
2127 program: "wrapper".into(),
2128 args: Vec::new(),
2129 relative_to: ScriptCommandRelativeTo::None,
2130 },
2131 target_runner: WrapperScriptTargetRunner::WithinWrapper,
2132 };
2133 let mut cli_wrapper_within = TestCommandCli::default();
2134 cli_wrapper_within.apply_wrappers(
2135 Some(&wrapper_within),
2136 Some(&runner),
2137 workspace_root,
2138 target_dir,
2139 );
2140 cli_wrapper_within.extend(["binary", "arg"]);
2141 assert_eq!(
2142 cli_wrapper_within.to_owned_cli(),
2143 vec!["wrapper", "runner", "binary", "arg"],
2144 );
2145 }
2146
2147 {
2149 let runner = PlatformRunner::debug_new(
2150 "runner".into(),
2151 Vec::new(),
2152 PlatformRunnerSource::Env("fake".to_owned()),
2153 );
2154 let wrapper_overrides = WrapperScriptConfig {
2155 command: ScriptCommand {
2156 program: "wrapper".into(),
2157 args: Vec::new(),
2158 relative_to: ScriptCommandRelativeTo::None,
2159 },
2160 target_runner: WrapperScriptTargetRunner::OverridesWrapper,
2161 };
2162 let mut cli_wrapper_overrides = TestCommandCli::default();
2163 cli_wrapper_overrides.apply_wrappers(
2164 Some(&wrapper_overrides),
2165 Some(&runner),
2166 workspace_root,
2167 target_dir,
2168 );
2169 cli_wrapper_overrides.extend(["binary", "arg"]);
2170 assert_eq!(
2171 cli_wrapper_overrides.to_owned_cli(),
2172 vec!["runner", "binary", "arg"],
2173 );
2174 }
2175
2176 {
2178 let wrapper_with_args = WrapperScriptConfig {
2179 command: ScriptCommand {
2180 program: "wrapper".into(),
2181 args: vec!["--flag".to_string(), "value".to_string()],
2182 relative_to: ScriptCommandRelativeTo::None,
2183 },
2184 target_runner: WrapperScriptTargetRunner::Ignore,
2185 };
2186 let mut cli_wrapper_args = TestCommandCli::default();
2187 cli_wrapper_args.apply_wrappers(
2188 Some(&wrapper_with_args),
2189 None,
2190 workspace_root,
2191 target_dir,
2192 );
2193 cli_wrapper_args.extend(["binary", "arg"]);
2194 assert_eq!(
2195 cli_wrapper_args.to_owned_cli(),
2196 vec!["wrapper", "--flag", "value", "binary", "arg"],
2197 );
2198 }
2199
2200 {
2202 let runner_with_args = PlatformRunner::debug_new(
2203 "runner".into(),
2204 vec!["--runner-flag".into(), "value".into()],
2205 PlatformRunnerSource::Env("fake".to_owned()),
2206 );
2207 let mut cli_runner_args = TestCommandCli::default();
2208 cli_runner_args.apply_wrappers(
2209 None,
2210 Some(&runner_with_args),
2211 workspace_root,
2212 target_dir,
2213 );
2214 cli_runner_args.extend(["binary", "arg"]);
2215 assert_eq!(
2216 cli_runner_args.to_owned_cli(),
2217 vec!["runner", "--runner-flag", "value", "binary", "arg"],
2218 );
2219 }
2220
2221 {
2223 let wrapper_relative_to_workspace_root = WrapperScriptConfig {
2224 command: ScriptCommand {
2225 program: "abc/def/my-wrapper".into(),
2226 args: vec!["--verbose".to_string()],
2227 relative_to: ScriptCommandRelativeTo::WorkspaceRoot,
2228 },
2229 target_runner: WrapperScriptTargetRunner::Ignore,
2230 };
2231 let mut cli_wrapper_relative = TestCommandCli::default();
2232 cli_wrapper_relative.apply_wrappers(
2233 Some(&wrapper_relative_to_workspace_root),
2234 None,
2235 workspace_root,
2236 target_dir,
2237 );
2238 cli_wrapper_relative.extend(["binary", "arg"]);
2239
2240 cfg_if::cfg_if! {
2241 if #[cfg(windows)] {
2242 let wrapper_path = "D:\\workspace\\root\\abc\\def\\my-wrapper";
2243 } else {
2244 let wrapper_path = "/workspace/root/abc/def/my-wrapper";
2245 }
2246 }
2247 assert_eq!(
2248 cli_wrapper_relative.to_owned_cli(),
2249 vec![wrapper_path, "--verbose", "binary", "arg"],
2250 );
2251 }
2252
2253 {
2255 let wrapper_relative_to_target = WrapperScriptConfig {
2256 command: ScriptCommand {
2257 program: "abc/def/my-wrapper".into(),
2258 args: vec!["--verbose".to_string()],
2259 relative_to: ScriptCommandRelativeTo::Target,
2260 },
2261 target_runner: WrapperScriptTargetRunner::Ignore,
2262 };
2263 let mut cli_wrapper_relative = TestCommandCli::default();
2264 cli_wrapper_relative.apply_wrappers(
2265 Some(&wrapper_relative_to_target),
2266 None,
2267 workspace_root,
2268 target_dir,
2269 );
2270 cli_wrapper_relative.extend(["binary", "arg"]);
2271 cfg_if::cfg_if! {
2272 if #[cfg(windows)] {
2273 let wrapper_path = "C:\\foo\\bar\\abc\\def\\my-wrapper";
2274 } else {
2275 let wrapper_path = "/foo/bar/abc/def/my-wrapper";
2276 }
2277 }
2278 assert_eq!(
2279 cli_wrapper_relative.to_owned_cli(),
2280 vec![wrapper_path, "--verbose", "binary", "arg"],
2281 );
2282 }
2283 }
2284
2285 static PACKAGE_GRAPH_FIXTURE: LazyLock<PackageGraph> = LazyLock::new(|| {
2286 static FIXTURE_JSON: &str = include_str!("../../../fixtures/cargo-metadata.json");
2287 let metadata = CargoMetadata::parse_json(FIXTURE_JSON).expect("fixture is valid JSON");
2288 metadata
2289 .build_graph()
2290 .expect("fixture is valid PackageGraph")
2291 });
2292
2293 static PACKAGE_METADATA_ID: &str = "metadata-helper 0.1.0 (path+file:///Users/fakeuser/local/testcrates/metadata/metadata-helper)";
2294 fn package_metadata() -> PackageMetadata<'static> {
2295 PACKAGE_GRAPH_FIXTURE
2296 .metadata(&PackageId::new(PACKAGE_METADATA_ID))
2297 .expect("package ID is valid")
2298 }
2299
2300 #[test]
2301 fn test_parse_list_lines() {
2302 let binary_id = RustBinaryId::new("test-package::test-binary");
2303
2304 let input = indoc! {"
2306 simple_test: test
2307 module::nested_test: test
2308 deeply::nested::module::test_name: test
2309 "};
2310 let results: Vec<_> = parse_list_lines(&binary_id, input)
2311 .collect::<Result<_, _>>()
2312 .expect("parsed valid test output");
2313 insta::assert_debug_snapshot!("valid_tests", results);
2314
2315 let input = indoc! {"
2317 simple_bench: benchmark
2318 benches::module::my_benchmark: benchmark
2319 "};
2320 let results: Vec<_> = parse_list_lines(&binary_id, input)
2321 .collect::<Result<_, _>>()
2322 .expect("parsed valid benchmark output");
2323 insta::assert_debug_snapshot!("valid_benchmarks", results);
2324
2325 let input = indoc! {"
2327 test_one: test
2328 bench_one: benchmark
2329 test_two: test
2330 bench_two: benchmark
2331 "};
2332 let results: Vec<_> = parse_list_lines(&binary_id, input)
2333 .collect::<Result<_, _>>()
2334 .expect("parsed mixed output");
2335 insta::assert_debug_snapshot!("mixed_tests_and_benchmarks", results);
2336
2337 let input = indoc! {r#"
2339 test_with_underscore_123: test
2340 test::with::colons: test
2341 test_with_numbers_42: test
2342 "#};
2343 let results: Vec<_> = parse_list_lines(&binary_id, input)
2344 .collect::<Result<_, _>>()
2345 .expect("parsed tests with special characters");
2346 insta::assert_debug_snapshot!("special_characters", results);
2347
2348 let input = "";
2350 let results: Vec<_> = parse_list_lines(&binary_id, input)
2351 .collect::<Result<_, _>>()
2352 .expect("parsed empty output");
2353 insta::assert_debug_snapshot!("empty_input", results);
2354
2355 let input = "invalid_test: wrong_suffix";
2357 let result = parse_list_lines(&binary_id, input).collect::<Result<Vec<_>, _>>();
2358 assert!(result.is_err());
2359 insta::assert_snapshot!("invalid_suffix_error", result.unwrap_err());
2360
2361 let input = "test_without_suffix";
2363 let result = parse_list_lines(&binary_id, input).collect::<Result<Vec<_>, _>>();
2364 assert!(result.is_err());
2365 insta::assert_snapshot!("missing_suffix_error", result.unwrap_err());
2366
2367 let input = indoc! {"
2369 valid_test: test
2370 invalid_line
2371 another_valid: benchmark
2372 "};
2373 let result = parse_list_lines(&binary_id, input).collect::<Result<Vec<_>, _>>();
2374 assert!(result.is_err());
2375 insta::assert_snapshot!("partial_valid_error", result.unwrap_err());
2376
2377 let input = indoc! {"
2379 valid_test: test
2380 \rinvalid_line
2381 another_valid: benchmark
2382 "};
2383 let result = parse_list_lines(&binary_id, input).collect::<Result<Vec<_>, _>>();
2384 assert!(result.is_err());
2385 insta::assert_snapshot!("control_character_error", result.unwrap_err());
2386 }
2387
2388 #[proptest]
2391 fn test_instance_id_key_borrow_consistency(
2392 owned1: OwnedTestInstanceId,
2393 owned2: OwnedTestInstanceId,
2394 ) {
2395 let borrowed1: &dyn TestInstanceIdKey = &owned1;
2397 let borrowed2: &dyn TestInstanceIdKey = &owned2;
2398
2399 assert_eq!(
2401 owned1 == owned2,
2402 borrowed1 == borrowed2,
2403 "Eq must be consistent between OwnedTestInstanceId and dyn TestInstanceIdKey"
2404 );
2405
2406 assert_eq!(
2408 owned1.partial_cmp(&owned2),
2409 borrowed1.partial_cmp(borrowed2),
2410 "PartialOrd must be consistent between OwnedTestInstanceId and dyn TestInstanceIdKey"
2411 );
2412
2413 assert_eq!(
2415 owned1.cmp(&owned2),
2416 borrowed1.cmp(borrowed2),
2417 "Ord must be consistent between OwnedTestInstanceId and dyn TestInstanceIdKey"
2418 );
2419
2420 fn hash_value(x: &impl Hash) -> u64 {
2422 let mut hasher = DefaultHasher::new();
2423 x.hash(&mut hasher);
2424 hasher.finish()
2425 }
2426
2427 assert_eq!(
2428 hash_value(&owned1),
2429 hash_value(&borrowed1),
2430 "Hash must be consistent for owned1 and its borrowed form"
2431 );
2432 assert_eq!(
2433 hash_value(&owned2),
2434 hash_value(&borrowed2),
2435 "Hash must be consistent for owned2 and its borrowed form"
2436 );
2437 }
2438}