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