1use crate::{
10 config::elements::FlakyResult,
11 errors::RecordReadError,
12 list::OwnedTestInstanceId,
13 record::{
14 CoreEventKind, OutputEventKind, PortableRecording, RecordReader, StoreReader,
15 TestEventKindSummary,
16 format::{RerunInfo, RerunRootInfo, RerunTestSuiteInfo},
17 },
18 reporter::events::ExecutionDescription,
19};
20use iddqd::IdOrdMap;
21use nextest_metadata::{
22 FilterMatch, MismatchReason, RustBinaryId, RustTestSuiteStatusSummary, TestCaseName,
23 TestListSummary,
24};
25use quick_junit::ReportUuid;
26use std::{
27 borrow::Borrow,
28 collections::{BTreeSet, HashMap},
29};
30
31pub(crate) trait TestListInfo {
36 type BinaryIter<'a>: Iterator<Item = (&'a RustBinaryId, BinaryInfo<'a>)>
38 where
39 Self: 'a;
40
41 fn binaries(&self) -> Self::BinaryIter<'_>;
43}
44
45pub(crate) enum BinaryInfo<'a> {
47 Listed {
49 test_cases: Box<dyn Iterator<Item = (&'a TestCaseName, FilterMatch)> + 'a>,
51 },
52 Skipped,
54}
55
56impl TestListInfo for TestListSummary {
57 type BinaryIter<'a> = TestListSummaryBinaryIter<'a>;
58
59 fn binaries(&self) -> Self::BinaryIter<'_> {
60 TestListSummaryBinaryIter {
61 inner: self.rust_suites.iter(),
62 }
63 }
64}
65
66pub(crate) struct TestListSummaryBinaryIter<'a> {
68 inner:
69 std::collections::btree_map::Iter<'a, RustBinaryId, nextest_metadata::RustTestSuiteSummary>,
70}
71
72impl<'a> Iterator for TestListSummaryBinaryIter<'a> {
73 type Item = (&'a RustBinaryId, BinaryInfo<'a>);
74
75 fn next(&mut self) -> Option<Self::Item> {
76 self.inner.next().map(|(binary_id, suite)| {
77 let info = if suite.status == RustTestSuiteStatusSummary::LISTED {
78 BinaryInfo::Listed {
79 test_cases: Box::new(
80 suite
81 .test_cases
82 .iter()
83 .map(|(name, tc)| (name, tc.filter_match)),
84 ),
85 }
86 } else {
87 BinaryInfo::Skipped
88 };
89 (binary_id, info)
90 })
91 }
92}
93
94pub(crate) fn compute_outstanding_pure(
96 prev_info: Option<&IdOrdMap<RerunTestSuiteInfo>>,
97 test_list: &impl TestListInfo,
98 outcomes: &HashMap<OwnedTestInstanceId, TestOutcome>,
99) -> IdOrdMap<RerunTestSuiteInfo> {
100 let mut new_outstanding = IdOrdMap::new();
101
102 let mut binaries_in_test_list = BTreeSet::new();
106
107 for (binary_id, binary_info) in test_list.binaries() {
108 binaries_in_test_list.insert(binary_id.clone());
109
110 match binary_info {
111 BinaryInfo::Listed { test_cases } => {
112 let prev = prev_info.and_then(|p| p.get(binary_id));
115
116 let mut curr = RerunTestSuiteInfo::new(binary_id.clone());
117 for (test_name, filter_match) in test_cases {
118 match filter_match {
119 FilterMatch::Matches => {
120 let key = OwnedTestInstanceId {
122 binary_id: binary_id.clone(),
123 test_name: test_name.clone(),
124 };
125 match outcomes.get(&key) {
126 Some(TestOutcome::Passed) => {
127 curr.passing.insert(test_name.clone());
129 }
130 Some(TestOutcome::Failed) => {
131 curr.outstanding.insert(test_name.clone());
133 }
134 Some(TestOutcome::Skipped(skipped)) => {
135 handle_skipped(test_name, *skipped, prev, &mut curr);
139 }
140 None => {
141 curr.outstanding.insert(test_name.clone());
144 }
145 }
146 }
147 FilterMatch::Mismatch { reason } => {
148 handle_skipped(
149 test_name,
150 TestOutcomeSkipped::from_mismatch_reason(reason),
151 prev,
152 &mut curr,
153 );
154 }
155 }
156 }
157
158 if let Some(prev) = prev {
162 for t in &prev.outstanding {
163 if !curr.passing.contains(t) && !curr.outstanding.contains(t) {
164 curr.outstanding.insert(t.clone());
165 }
166 }
167 }
168
169 if !curr.passing.is_empty() || !curr.outstanding.is_empty() {
176 new_outstanding
177 .insert_unique(curr)
178 .expect("binaries iterator should not yield duplicates");
179 }
180 }
181 BinaryInfo::Skipped => {
182 if let Some(prev_outstanding) = prev_info
192 && let Some(outstanding) = prev_outstanding.get(binary_id)
193 {
194 new_outstanding
196 .insert_unique(outstanding.clone())
197 .expect("binaries iterator should not yield duplicates");
198 }
199 }
204 }
205 }
206
207 if let Some(prev) = prev_info {
210 for prev_suite in prev.iter() {
211 if !binaries_in_test_list.contains(&prev_suite.binary_id) {
212 new_outstanding
213 .insert_unique(prev_suite.clone())
214 .expect("binary not in test list, so this should succeed");
215 }
216 }
217 }
218
219 new_outstanding
220}
221
222#[derive(Clone, Debug)]
224pub struct ComputedRerunInfo {
225 pub test_suites: IdOrdMap<RerunTestSuiteInfo>,
229}
230
231impl ComputedRerunInfo {
232 pub fn expected_test_ids(&self) -> BTreeSet<OwnedTestInstanceId> {
236 self.test_suites
237 .iter()
238 .flat_map(|suite| {
239 suite.outstanding.iter().map(|name| OwnedTestInstanceId {
240 binary_id: suite.binary_id.clone(),
241 test_name: name.clone(),
242 })
243 })
244 .collect()
245 }
246
247 pub fn compute(
254 parent_run_id: ReportUuid,
255 reader: &mut RecordReader,
256 ) -> Result<(Self, RerunRootInfo), RecordReadError> {
257 let rerun_info = reader.read_rerun_info()?;
258 let (prev_test_suites, root_info) =
259 prev_suites_and_root_info(parent_run_id, rerun_info, reader)?;
260 let test_list = reader.read_test_list()?;
261 let outcomes = TestEventOutcomes::collect(reader)?;
262
263 let new_test_suites =
264 compute_outstanding_pure(prev_test_suites.as_ref(), &test_list, &outcomes.outcomes);
265
266 Ok((
267 Self {
268 test_suites: new_test_suites,
269 },
270 root_info,
271 ))
272 }
273
274 pub fn compute_from_archive(
281 archive: &mut PortableRecording,
282 ) -> Result<(Self, RerunRootInfo), RecordReadError> {
283 let parent_run_id = archive.run_info().run_id;
284 let mut store = archive
285 .open_store()
286 .map_err(RecordReadError::PortableRecording)?;
287 let rerun_info = store.read_rerun_info()?;
288 let (prev_test_suites, root_info) =
289 prev_suites_and_root_info(parent_run_id, rerun_info, &mut store)?;
290 let test_list = store.read_test_list()?;
291 let run_log = archive
295 .read_run_log()
296 .map_err(RecordReadError::PortableRecording)?;
297 let outcomes = collect_from_events(run_log.events()?.map(|r| r.map(|e| e.kind)))?;
298
299 let new_test_suites =
300 compute_outstanding_pure(prev_test_suites.as_ref(), &test_list, &outcomes);
301
302 Ok((
303 Self {
304 test_suites: new_test_suites,
305 },
306 root_info,
307 ))
308 }
309
310 pub fn into_rerun_info(self, parent_run_id: ReportUuid, root_info: RerunRootInfo) -> RerunInfo {
312 RerunInfo {
313 parent_run_id,
314 root_info,
315 test_suites: self.test_suites,
316 }
317 }
318}
319
320fn prev_suites_and_root_info(
326 parent_run_id: ReportUuid,
327 rerun_info: Option<RerunInfo>,
328 store: &mut impl StoreReader,
329) -> Result<(Option<IdOrdMap<RerunTestSuiteInfo>>, RerunRootInfo), RecordReadError> {
330 match rerun_info {
331 Some(RerunInfo {
332 parent_run_id: _,
333 root_info,
334 test_suites,
335 }) => Ok((Some(test_suites), root_info)),
336 None => {
337 let build_scope = store.read_build_scope()?;
338 Ok((None, RerunRootInfo::new(parent_run_id, build_scope)))
339 }
340 }
341}
342
343fn handle_skipped(
344 test_name: &TestCaseName,
345 skipped: TestOutcomeSkipped,
346 prev: Option<&RerunTestSuiteInfo>,
347 curr: &mut RerunTestSuiteInfo,
348) {
349 match skipped {
350 TestOutcomeSkipped::Rerun => {
351 curr.passing.insert(test_name.clone());
358 }
359 TestOutcomeSkipped::Explicit => {
360 if let Some(prev) = prev {
374 if prev.outstanding.contains(test_name) {
375 curr.outstanding.insert(test_name.clone());
376 } else if prev.passing.contains(test_name) {
377 curr.passing.insert(test_name.clone());
378 }
379 } else {
380 }
383 }
384 }
385}
386
387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
389pub(crate) enum TestOutcomeSkipped {
390 Explicit,
392
393 Rerun,
395}
396
397impl TestOutcomeSkipped {
398 fn from_mismatch_reason(reason: MismatchReason) -> Self {
400 match reason {
401 MismatchReason::NotBenchmark
402 | MismatchReason::Ignored
403 | MismatchReason::String
404 | MismatchReason::Expression
405 | MismatchReason::Partition
406 | MismatchReason::DefaultFilter => TestOutcomeSkipped::Explicit,
407 MismatchReason::RerunAlreadyPassed => TestOutcomeSkipped::Rerun,
408 other => unreachable!("all known match arms are covered, found {other:?}"),
409 }
410 }
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
415pub(crate) enum TestOutcome {
416 Passed,
418
419 Skipped(TestOutcomeSkipped),
421
422 Failed,
424}
425
426#[derive(Clone, Debug)]
430struct TestEventOutcomes {
431 outcomes: HashMap<OwnedTestInstanceId, TestOutcome>,
433}
434
435impl TestEventOutcomes {
436 fn collect(reader: &mut RecordReader) -> Result<Self, RecordReadError> {
441 reader.load_dictionaries()?;
442 let outcomes = collect_from_events(reader.events()?.map(|r| r.map(|e| e.kind)))?;
443
444 Ok(Self { outcomes })
445 }
446}
447
448fn collect_from_events<K, S: crate::output_spec::OutputSpec, E>(
454 events: impl Iterator<Item = Result<K, E>>,
455) -> Result<HashMap<OwnedTestInstanceId, TestOutcome>, E>
456where
457 K: Borrow<TestEventKindSummary<S>>,
458{
459 let mut outcomes = HashMap::new();
460
461 for kind_result in events {
462 let kind = kind_result?;
463 match kind.borrow() {
464 TestEventKindSummary::Output(OutputEventKind::TestFinished {
465 test_instance,
466 run_statuses,
467 ..
468 }) => {
469 let outcome = match run_statuses.describe() {
474 ExecutionDescription::Success { .. } => TestOutcome::Passed,
475 ExecutionDescription::Flaky {
476 result: FlakyResult::Pass,
477 ..
478 } => TestOutcome::Passed,
479 ExecutionDescription::Flaky {
480 result: FlakyResult::Fail,
481 ..
482 } => TestOutcome::Failed,
483 ExecutionDescription::Failure { .. } => TestOutcome::Failed,
484 };
485
486 outcomes
493 .entry(test_instance.clone())
494 .and_modify(|existing| {
495 if outcome == TestOutcome::Failed {
496 *existing = TestOutcome::Failed;
497 }
498 })
499 .or_insert(outcome);
500 }
501 TestEventKindSummary::Core(CoreEventKind::TestSkipped {
502 test_instance,
503 reason,
504 ..
505 }) => {
506 let skipped_reason = TestOutcomeSkipped::from_mismatch_reason(*reason);
507 outcomes.insert(test_instance.clone(), TestOutcome::Skipped(skipped_reason));
508 }
509 _ => {}
510 }
511 }
512
513 Ok(outcomes)
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use crate::{
520 config::elements::JunitFlakyFailStatus,
521 output_spec::RecordingSpec,
522 record::{
523 OutputEventKind, StressIndexSummary, TestEventKindSummary, ZipStoreOutputDescription,
524 },
525 reporter::{
526 TestOutputDisplay,
527 events::{
528 ChildExecutionOutputDescription, ExecuteStatus, ExecutionResultDescription,
529 ExecutionStatuses, FailureDescription, RetryData, RunStats,
530 },
531 },
532 };
533 use chrono::Utc;
534 use proptest::prelude::*;
535 use std::{
536 collections::{BTreeMap, btree_map},
537 convert::Infallible,
538 num::NonZero,
539 sync::OnceLock,
540 time::Duration,
541 };
542 use test_strategy::proptest;
543
544 #[proptest(cases = 200)]
550 fn sut_matches_oracle(#[strategy(arb_rerun_model())] model: RerunModel) {
551 let expected = model.compute_rerun_info_decision_table();
552 let actual = run_sut(&model);
553 prop_assert_eq!(actual, expected);
554 }
555
556 #[proptest(cases = 200)]
558 fn passing_and_outstanding_disjoint(#[strategy(arb_rerun_model())] model: RerunModel) {
559 let result = run_sut(&model);
560 for suite in result.iter() {
561 let intersection: BTreeSet<_> =
562 suite.passing.intersection(&suite.outstanding).collect();
563 prop_assert!(
564 intersection.is_empty(),
565 "passing and outstanding should be disjoint for {}: {:?}",
566 suite.binary_id,
567 intersection
568 );
569 }
570 }
571
572 #[proptest(cases = 200)]
578 fn matching_tests_with_outcomes_are_tracked(#[strategy(arb_rerun_model())] model: RerunModel) {
579 let result = run_sut(&model);
580
581 let final_step = model.reruns.last().unwrap_or(&model.initial);
583
584 for (binary_id, binary_model) in &final_step.test_list.binaries {
585 if let BinaryModel::Listed { tests } = binary_model {
586 let rust_binary_id = binary_id.rust_binary_id();
587
588 for (test_name, filter_match) in tests {
589 if matches!(filter_match, FilterMatch::Matches) {
590 let key = (*binary_id, *test_name);
591 let outcome = final_step.outcomes.get(&key);
592
593 let should_be_tracked = match outcome {
598 Some(TestOutcome::Passed)
599 | Some(TestOutcome::Failed)
600 | Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun))
601 | None => true,
602 Some(TestOutcome::Skipped(TestOutcomeSkipped::Explicit)) => false,
603 };
604
605 if should_be_tracked {
606 let tcn = test_name.test_case_name();
607 let suite = result.get(&rust_binary_id);
608 let in_passing = suite.is_some_and(|s| s.passing.contains(tcn));
609 let in_outstanding = suite.is_some_and(|s| s.outstanding.contains(tcn));
610 prop_assert!(
611 in_passing || in_outstanding,
612 "matching test {:?}::{:?} with outcome {:?} should be in passing or outstanding",
613 binary_id,
614 test_name,
615 outcome
616 );
617 }
618 }
619 }
620 }
621 }
622 }
623
624 #[test]
626 fn decide_test_outcome_truth_table() {
627 use Decision as D;
628 use FilterMatchResult as F;
629 use PrevStatus as P;
630
631 assert_eq!(
633 decide_test_outcome(P::Passing, F::BinaryNotPresent, None),
634 D::Passing
635 );
636 assert_eq!(
637 decide_test_outcome(P::Outstanding, F::BinaryNotPresent, None),
638 D::Outstanding
639 );
640 assert_eq!(
641 decide_test_outcome(P::Unknown, F::BinaryNotPresent, None),
642 D::NotTracked
643 );
644
645 assert_eq!(
647 decide_test_outcome(P::Passing, F::BinarySkipped, None),
648 D::Passing
649 );
650 assert_eq!(
651 decide_test_outcome(P::Outstanding, F::BinarySkipped, None),
652 D::Outstanding
653 );
654 assert_eq!(
655 decide_test_outcome(P::Unknown, F::BinarySkipped, None),
656 D::NotTracked
657 );
658
659 assert_eq!(
661 decide_test_outcome(P::Passing, F::TestNotInList, None),
662 D::NotTracked
663 );
664 assert_eq!(
665 decide_test_outcome(P::Outstanding, F::TestNotInList, None),
666 D::Outstanding
667 );
668 assert_eq!(
669 decide_test_outcome(P::Unknown, F::TestNotInList, None),
670 D::NotTracked
671 );
672
673 let matches = F::HasMatch(FilterMatch::Matches);
675
676 assert_eq!(
678 decide_test_outcome(P::Unknown, matches, Some(TestOutcome::Passed)),
679 D::Passing
680 );
681 assert_eq!(
682 decide_test_outcome(P::Passing, matches, Some(TestOutcome::Passed)),
683 D::Passing
684 );
685 assert_eq!(
686 decide_test_outcome(P::Outstanding, matches, Some(TestOutcome::Passed)),
687 D::Passing
688 );
689
690 assert_eq!(
692 decide_test_outcome(P::Unknown, matches, Some(TestOutcome::Failed)),
693 D::Outstanding
694 );
695 assert_eq!(
696 decide_test_outcome(P::Passing, matches, Some(TestOutcome::Failed)),
697 D::Outstanding
698 );
699 assert_eq!(
700 decide_test_outcome(P::Outstanding, matches, Some(TestOutcome::Failed)),
701 D::Outstanding
702 );
703
704 assert_eq!(
706 decide_test_outcome(P::Unknown, matches, None),
707 D::Outstanding
708 );
709 assert_eq!(
710 decide_test_outcome(P::Passing, matches, None),
711 D::Outstanding
712 );
713 assert_eq!(
714 decide_test_outcome(P::Outstanding, matches, None),
715 D::Outstanding
716 );
717
718 let rerun_skipped = Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun));
720 assert_eq!(
721 decide_test_outcome(P::Unknown, matches, rerun_skipped),
722 D::Passing
723 );
724 assert_eq!(
725 decide_test_outcome(P::Passing, matches, rerun_skipped),
726 D::Passing
727 );
728 assert_eq!(
729 decide_test_outcome(P::Outstanding, matches, rerun_skipped),
730 D::Passing
731 );
732
733 let explicit_skipped = Some(TestOutcome::Skipped(TestOutcomeSkipped::Explicit));
735 assert_eq!(
736 decide_test_outcome(P::Unknown, matches, explicit_skipped),
737 D::NotTracked
738 );
739 assert_eq!(
740 decide_test_outcome(P::Passing, matches, explicit_skipped),
741 D::Passing
742 );
743 assert_eq!(
744 decide_test_outcome(P::Outstanding, matches, explicit_skipped),
745 D::Outstanding
746 );
747
748 let rerun_mismatch = F::HasMatch(FilterMatch::Mismatch {
750 reason: MismatchReason::RerunAlreadyPassed,
751 });
752 assert_eq!(
753 decide_test_outcome(P::Unknown, rerun_mismatch, None),
754 D::Passing
755 );
756 assert_eq!(
757 decide_test_outcome(P::Passing, rerun_mismatch, None),
758 D::Passing
759 );
760 assert_eq!(
761 decide_test_outcome(P::Outstanding, rerun_mismatch, None),
762 D::Passing
763 );
764
765 let explicit_mismatch = F::HasMatch(FilterMatch::Mismatch {
767 reason: MismatchReason::Ignored,
768 });
769 assert_eq!(
770 decide_test_outcome(P::Unknown, explicit_mismatch, None),
771 D::NotTracked
772 );
773 assert_eq!(
774 decide_test_outcome(P::Passing, explicit_mismatch, None),
775 D::Passing
776 );
777 assert_eq!(
778 decide_test_outcome(P::Outstanding, explicit_mismatch, None),
779 D::Outstanding
780 );
781 }
782
783 const ALL_PREV_STATUSES: [PrevStatus; 3] = [
792 PrevStatus::Passing,
793 PrevStatus::Outstanding,
794 PrevStatus::Unknown,
795 ];
796
797 fn all_outcomes() -> [Option<TestOutcome>; 5] {
799 [
800 None,
801 Some(TestOutcome::Passed),
802 Some(TestOutcome::Failed),
803 Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun)),
804 Some(TestOutcome::Skipped(TestOutcomeSkipped::Explicit)),
805 ]
806 }
807
808 fn all_in_list_filter_results() -> Vec<FilterMatchResult> {
810 let mut results = vec![FilterMatchResult::HasMatch(FilterMatch::Matches)];
811 for &reason in MismatchReason::ALL_VARIANTS {
812 results.push(FilterMatchResult::HasMatch(FilterMatch::Mismatch {
813 reason,
814 }));
815 }
816 results
817 }
818
819 #[test]
827 fn spec_property_passing_monotonicity() {
828 let non_regressing_outcomes = [
829 Some(TestOutcome::Passed),
830 Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun)),
831 Some(TestOutcome::Skipped(TestOutcomeSkipped::Explicit)),
832 ];
833
834 for filter in all_in_list_filter_results() {
835 for outcome in non_regressing_outcomes {
836 let decision = decide_test_outcome(PrevStatus::Passing, filter, outcome);
837 assert_eq!(
838 decision,
839 Decision::Passing,
840 "monotonicity violated: Passing + {:?} + {:?} -> {:?}",
841 filter,
842 outcome,
843 decision
844 );
845 }
846 }
847 }
848
849 #[test]
854 fn spec_property_outstanding_to_passing_on_pass() {
855 let passing_outcomes = [
856 Some(TestOutcome::Passed),
857 Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun)),
858 ];
859
860 for outcome in passing_outcomes {
861 let decision = decide_test_outcome(
862 PrevStatus::Outstanding,
863 FilterMatchResult::HasMatch(FilterMatch::Matches),
864 outcome,
865 );
866 assert_eq!(
867 decision,
868 Decision::Passing,
869 "convergence violated: Outstanding + Matches + {:?} -> {:?}",
870 outcome,
871 decision
872 );
873 }
874 }
875
876 #[test]
880 fn spec_property_failed_becomes_outstanding() {
881 let failing_outcomes = [None, Some(TestOutcome::Failed)];
882
883 for prev in ALL_PREV_STATUSES {
884 for outcome in failing_outcomes {
885 let decision = decide_test_outcome(
886 prev,
887 FilterMatchResult::HasMatch(FilterMatch::Matches),
888 outcome,
889 );
890 assert_eq!(
891 decision,
892 Decision::Outstanding,
893 "FAILED->OUTSTANDING VIOLATED: {:?} + Matches + {:?} -> {:?}",
894 prev,
895 outcome,
896 decision
897 );
898 }
899 }
900 }
901
902 #[test]
909 fn spec_property_test_not_in_list_behavior() {
910 for outcome in all_outcomes() {
911 assert_eq!(
913 decide_test_outcome(
914 PrevStatus::Outstanding,
915 FilterMatchResult::TestNotInList,
916 outcome
917 ),
918 Decision::Outstanding,
919 );
920 assert_eq!(
922 decide_test_outcome(
923 PrevStatus::Passing,
924 FilterMatchResult::TestNotInList,
925 outcome
926 ),
927 Decision::NotTracked,
928 );
929 assert_eq!(
931 decide_test_outcome(
932 PrevStatus::Unknown,
933 FilterMatchResult::TestNotInList,
934 outcome
935 ),
936 Decision::NotTracked,
937 );
938 }
939 }
940
941 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
949 enum ModelBinaryId {
950 A,
951 B,
952 C,
953 D,
954 }
955
956 impl ModelBinaryId {
957 fn rust_binary_id(self) -> &'static RustBinaryId {
958 match self {
959 Self::A => {
960 static ID: OnceLock<RustBinaryId> = OnceLock::new();
961 ID.get_or_init(|| RustBinaryId::new("binary-a"))
962 }
963 Self::B => {
964 static ID: OnceLock<RustBinaryId> = OnceLock::new();
965 ID.get_or_init(|| RustBinaryId::new("binary-b"))
966 }
967 Self::C => {
968 static ID: OnceLock<RustBinaryId> = OnceLock::new();
969 ID.get_or_init(|| RustBinaryId::new("binary-c"))
970 }
971 Self::D => {
972 static ID: OnceLock<RustBinaryId> = OnceLock::new();
973 ID.get_or_init(|| RustBinaryId::new("binary-d"))
974 }
975 }
976 }
977 }
978
979 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
981 enum ModelTestName {
982 Test1,
983 Test2,
984 Test3,
985 Test4,
986 Test5,
987 }
988
989 impl ModelTestName {
990 fn test_case_name(self) -> &'static TestCaseName {
991 match self {
992 Self::Test1 => {
993 static NAME: OnceLock<TestCaseName> = OnceLock::new();
994 NAME.get_or_init(|| TestCaseName::new("test_1"))
995 }
996 Self::Test2 => {
997 static NAME: OnceLock<TestCaseName> = OnceLock::new();
998 NAME.get_or_init(|| TestCaseName::new("test_2"))
999 }
1000 Self::Test3 => {
1001 static NAME: OnceLock<TestCaseName> = OnceLock::new();
1002 NAME.get_or_init(|| TestCaseName::new("test_3"))
1003 }
1004 Self::Test4 => {
1005 static NAME: OnceLock<TestCaseName> = OnceLock::new();
1006 NAME.get_or_init(|| TestCaseName::new("test_4"))
1007 }
1008 Self::Test5 => {
1009 static NAME: OnceLock<TestCaseName> = OnceLock::new();
1010 NAME.get_or_init(|| TestCaseName::new("test_5"))
1011 }
1012 }
1013 }
1014 }
1015
1016 #[derive(Clone, Debug)]
1018 enum BinaryModel {
1019 Listed {
1021 tests: BTreeMap<ModelTestName, FilterMatch>,
1022 },
1023 Skipped,
1025 }
1026
1027 #[derive(Clone, Debug)]
1029 struct TestListModel {
1030 binaries: BTreeMap<ModelBinaryId, BinaryModel>,
1031 }
1032
1033 #[derive(Clone, Debug)]
1035 struct RunStep {
1036 test_list: TestListModel,
1038 outcomes: BTreeMap<(ModelBinaryId, ModelTestName), TestOutcome>,
1040 }
1041
1042 #[derive(Clone, Debug)]
1044 struct RerunModel {
1045 initial: RunStep,
1047 reruns: Vec<RunStep>,
1049 }
1050
1051 impl TestListInfo for TestListModel {
1052 type BinaryIter<'a> = TestListModelBinaryIter<'a>;
1053
1054 fn binaries(&self) -> Self::BinaryIter<'_> {
1055 TestListModelBinaryIter {
1056 inner: self.binaries.iter(),
1057 }
1058 }
1059 }
1060
1061 struct TestListModelBinaryIter<'a> {
1063 inner: btree_map::Iter<'a, ModelBinaryId, BinaryModel>,
1064 }
1065
1066 impl<'a> Iterator for TestListModelBinaryIter<'a> {
1067 type Item = (&'a RustBinaryId, BinaryInfo<'a>);
1068
1069 fn next(&mut self) -> Option<Self::Item> {
1070 self.inner.next().map(|(model_id, binary_model)| {
1071 let rust_id = model_id.rust_binary_id();
1072 let info = match binary_model {
1073 BinaryModel::Listed { tests } => BinaryInfo::Listed {
1074 test_cases: Box::new(
1075 tests.iter().map(|(name, fm)| (name.test_case_name(), *fm)),
1076 ),
1077 },
1078 BinaryModel::Skipped => BinaryInfo::Skipped,
1079 };
1080 (rust_id, info)
1081 })
1082 }
1083 }
1084
1085 fn arb_model_binary_id() -> impl Strategy<Value = ModelBinaryId> {
1090 prop_oneof![
1091 Just(ModelBinaryId::A),
1092 Just(ModelBinaryId::B),
1093 Just(ModelBinaryId::C),
1094 Just(ModelBinaryId::D),
1095 ]
1096 }
1097
1098 fn arb_model_test_name() -> impl Strategy<Value = ModelTestName> {
1099 prop_oneof![
1100 Just(ModelTestName::Test1),
1101 Just(ModelTestName::Test2),
1102 Just(ModelTestName::Test3),
1103 Just(ModelTestName::Test4),
1104 Just(ModelTestName::Test5),
1105 ]
1106 }
1107
1108 fn arb_filter_match() -> impl Strategy<Value = FilterMatch> {
1109 prop_oneof![
1110 4 => Just(FilterMatch::Matches),
1111 1 => any::<MismatchReason>().prop_map(|reason| FilterMatch::Mismatch { reason }),
1112 ]
1113 }
1114
1115 fn arb_test_outcome() -> impl Strategy<Value = TestOutcome> {
1116 prop_oneof![
1117 4 => Just(TestOutcome::Passed),
1118 2 => Just(TestOutcome::Failed),
1119 1 => Just(TestOutcome::Skipped(TestOutcomeSkipped::Explicit)),
1120 1 => Just(TestOutcome::Skipped(TestOutcomeSkipped::Rerun)),
1121 ]
1122 }
1123
1124 fn arb_test_map() -> impl Strategy<Value = BTreeMap<ModelTestName, FilterMatch>> {
1125 proptest::collection::btree_map(arb_model_test_name(), arb_filter_match(), 0..5)
1126 }
1127
1128 fn arb_binary_model() -> impl Strategy<Value = BinaryModel> {
1129 prop_oneof![
1130 8 => arb_test_map().prop_map(|tests| BinaryModel::Listed { tests }),
1131 2 => Just(BinaryModel::Skipped),
1132 ]
1133 }
1134
1135 fn arb_test_list_model() -> impl Strategy<Value = TestListModel> {
1136 proptest::collection::btree_map(arb_model_binary_id(), arb_binary_model(), 0..4)
1137 .prop_map(|binaries| TestListModel { binaries })
1138 }
1139
1140 fn arb_outcomes_for_matching_tests(
1145 matching_tests: Vec<(ModelBinaryId, ModelTestName)>,
1146 ) -> BoxedStrategy<BTreeMap<(ModelBinaryId, ModelTestName), TestOutcome>> {
1147 if matching_tests.is_empty() {
1148 Just(BTreeMap::new()).boxed()
1149 } else {
1150 let len = matching_tests.len();
1151 proptest::collection::btree_map(
1152 proptest::sample::select(matching_tests),
1153 arb_test_outcome(),
1154 0..=len,
1155 )
1156 .boxed()
1157 }
1158 }
1159
1160 fn extract_matching_tests(test_list: &TestListModel) -> Vec<(ModelBinaryId, ModelTestName)> {
1162 test_list
1163 .binaries
1164 .iter()
1165 .filter_map(|(binary_id, model)| match model {
1166 BinaryModel::Listed { tests } => Some(
1167 tests
1168 .iter()
1169 .filter(|(_, fm)| matches!(fm, FilterMatch::Matches))
1170 .map(move |(tn, _)| (*binary_id, *tn)),
1171 ),
1172 BinaryModel::Skipped => None,
1173 })
1174 .flatten()
1175 .collect()
1176 }
1177
1178 fn arb_run_step() -> impl Strategy<Value = RunStep> {
1179 arb_test_list_model().prop_flat_map(|test_list| {
1180 let matching_tests = extract_matching_tests(&test_list);
1181 arb_outcomes_for_matching_tests(matching_tests).prop_map(move |outcomes| RunStep {
1182 test_list: test_list.clone(),
1183 outcomes,
1184 })
1185 })
1186 }
1187
1188 fn arb_rerun_model() -> impl Strategy<Value = RerunModel> {
1189 (
1190 arb_run_step(),
1191 proptest::collection::vec(arb_run_step(), 0..5),
1192 )
1193 .prop_map(|(initial, reruns)| RerunModel { initial, reruns })
1194 }
1195
1196 fn model_outcomes_to_hashmap(
1201 outcomes: &BTreeMap<(ModelBinaryId, ModelTestName), TestOutcome>,
1202 ) -> HashMap<OwnedTestInstanceId, TestOutcome> {
1203 outcomes
1204 .iter()
1205 .map(|((binary_id, test_name), outcome)| {
1206 let id = OwnedTestInstanceId {
1207 binary_id: binary_id.rust_binary_id().clone(),
1208 test_name: test_name.test_case_name().clone(),
1209 };
1210 (id, *outcome)
1211 })
1212 .collect()
1213 }
1214
1215 fn run_sut(model: &RerunModel) -> IdOrdMap<RerunTestSuiteInfo> {
1221 let outcomes = model_outcomes_to_hashmap(&model.initial.outcomes);
1222 let mut result = compute_outstanding_pure(None, &model.initial.test_list, &outcomes);
1223
1224 for rerun in &model.reruns {
1225 let outcomes = model_outcomes_to_hashmap(&rerun.outcomes);
1226 result = compute_outstanding_pure(Some(&result), &rerun.test_list, &outcomes);
1227 }
1228
1229 result
1230 }
1231
1232 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1242 enum PrevStatus {
1243 Passing,
1245 Outstanding,
1247 Unknown,
1249 }
1250
1251 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1253 enum Decision {
1254 Passing,
1256 Outstanding,
1258 NotTracked,
1260 }
1261
1262 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1267 enum FilterMatchResult {
1268 BinaryNotPresent,
1271 BinarySkipped,
1274 TestNotInList,
1277 HasMatch(FilterMatch),
1279 }
1280
1281 fn decide_test_outcome(
1286 prev: PrevStatus,
1287 filter_result: FilterMatchResult,
1288 outcome: Option<TestOutcome>,
1289 ) -> Decision {
1290 match filter_result {
1291 FilterMatchResult::BinaryNotPresent | FilterMatchResult::BinarySkipped => {
1292 match prev {
1294 PrevStatus::Passing => Decision::Passing,
1295 PrevStatus::Outstanding => Decision::Outstanding,
1296 PrevStatus::Unknown => Decision::NotTracked,
1297 }
1298 }
1299 FilterMatchResult::TestNotInList => {
1300 match prev {
1305 PrevStatus::Outstanding => Decision::Outstanding,
1306 PrevStatus::Passing | PrevStatus::Unknown => Decision::NotTracked,
1307 }
1308 }
1309 FilterMatchResult::HasMatch(FilterMatch::Matches) => {
1310 match outcome {
1311 Some(TestOutcome::Passed) => Decision::Passing,
1312 Some(TestOutcome::Failed) => Decision::Outstanding,
1313 None => {
1314 Decision::Outstanding
1316 }
1317 Some(TestOutcome::Skipped(TestOutcomeSkipped::Rerun)) => Decision::Passing,
1318 Some(TestOutcome::Skipped(TestOutcomeSkipped::Explicit)) => {
1319 match prev {
1321 PrevStatus::Passing => Decision::Passing,
1322 PrevStatus::Outstanding => Decision::Outstanding,
1323 PrevStatus::Unknown => Decision::NotTracked,
1324 }
1325 }
1326 }
1327 }
1328 FilterMatchResult::HasMatch(FilterMatch::Mismatch { reason }) => {
1329 match TestOutcomeSkipped::from_mismatch_reason(reason) {
1330 TestOutcomeSkipped::Rerun => Decision::Passing,
1331 TestOutcomeSkipped::Explicit => {
1332 match prev {
1334 PrevStatus::Passing => Decision::Passing,
1335 PrevStatus::Outstanding => Decision::Outstanding,
1336 PrevStatus::Unknown => Decision::NotTracked,
1337 }
1338 }
1339 }
1340 }
1341 }
1342 }
1343
1344 impl RerunModel {
1345 fn compute_rerun_info_decision_table(&self) -> IdOrdMap<RerunTestSuiteInfo> {
1351 let mut prev_state: HashMap<(ModelBinaryId, ModelTestName), PrevStatus> =
1353 HashMap::new();
1354
1355 self.update_state_from_step(&mut prev_state, &self.initial);
1357
1358 for rerun in &self.reruns {
1360 self.update_state_from_step(&mut prev_state, rerun);
1361 }
1362
1363 self.collect_final_state(&prev_state)
1365 }
1366
1367 fn update_state_from_step(
1368 &self,
1369 state: &mut HashMap<(ModelBinaryId, ModelTestName), PrevStatus>,
1370 step: &RunStep,
1371 ) {
1372 let all_tests = self.enumerate_all_tests(state, step);
1376
1377 for (binary_id, test_name) in all_tests {
1378 let prev = state
1379 .get(&(binary_id, test_name))
1380 .copied()
1381 .unwrap_or(PrevStatus::Unknown);
1382
1383 let filter_result = self.get_filter_match_result(step, binary_id, test_name);
1384 let outcome = step.outcomes.get(&(binary_id, test_name)).copied();
1385
1386 let decision = decide_test_outcome(prev, filter_result, outcome);
1387
1388 match decision {
1390 Decision::Passing => {
1391 state.insert((binary_id, test_name), PrevStatus::Passing);
1392 }
1393 Decision::Outstanding => {
1394 state.insert((binary_id, test_name), PrevStatus::Outstanding);
1395 }
1396 Decision::NotTracked => {
1397 state.remove(&(binary_id, test_name));
1398 }
1399 }
1400 }
1401 }
1402
1403 fn get_filter_match_result(
1408 &self,
1409 step: &RunStep,
1410 binary_id: ModelBinaryId,
1411 test_name: ModelTestName,
1412 ) -> FilterMatchResult {
1413 match step.test_list.binaries.get(&binary_id) {
1414 None => FilterMatchResult::BinaryNotPresent,
1415 Some(BinaryModel::Skipped) => FilterMatchResult::BinarySkipped,
1416 Some(BinaryModel::Listed { tests }) => match tests.get(&test_name) {
1417 Some(filter_match) => FilterMatchResult::HasMatch(*filter_match),
1418 None => FilterMatchResult::TestNotInList,
1419 },
1420 }
1421 }
1422
1423 fn enumerate_all_tests(
1428 &self,
1429 prev_state: &HashMap<(ModelBinaryId, ModelTestName), PrevStatus>,
1430 step: &RunStep,
1431 ) -> BTreeSet<(ModelBinaryId, ModelTestName)> {
1432 let mut tests = BTreeSet::new();
1433
1434 for (binary_id, binary_model) in &step.test_list.binaries {
1436 if let BinaryModel::Listed { tests: test_map } = binary_model {
1437 for test_name in test_map.keys() {
1438 tests.insert((*binary_id, *test_name));
1439 }
1440 }
1441 }
1442
1443 for (binary_id, test_name) in prev_state.keys() {
1445 tests.insert((*binary_id, *test_name));
1446 }
1447
1448 tests
1449 }
1450
1451 fn collect_final_state(
1453 &self,
1454 state: &HashMap<(ModelBinaryId, ModelTestName), PrevStatus>,
1455 ) -> IdOrdMap<RerunTestSuiteInfo> {
1456 let mut result: BTreeMap<ModelBinaryId, RerunTestSuiteInfo> = BTreeMap::new();
1457
1458 for ((binary_id, test_name), status) in state {
1459 let suite = result
1460 .entry(*binary_id)
1461 .or_insert_with(|| RerunTestSuiteInfo::new(binary_id.rust_binary_id().clone()));
1462
1463 match status {
1464 PrevStatus::Passing => {
1465 suite.passing.insert(test_name.test_case_name().clone());
1466 }
1467 PrevStatus::Outstanding => {
1468 suite.outstanding.insert(test_name.test_case_name().clone());
1469 }
1470 PrevStatus::Unknown => {
1471 }
1473 }
1474 }
1475
1476 let mut id_map = IdOrdMap::new();
1477 for (_, suite) in result {
1478 id_map.insert_unique(suite).expect("unique binaries");
1479 }
1480 id_map
1481 }
1482 }
1483
1484 fn make_test_finished(
1493 test_instance: OwnedTestInstanceId,
1494 stress_index: Option<(u32, Option<u32>)>,
1495 passed: bool,
1496 ) -> TestEventKindSummary<RecordingSpec> {
1497 let result = if passed {
1498 ExecutionResultDescription::Pass
1499 } else {
1500 ExecutionResultDescription::Fail {
1501 failure: FailureDescription::ExitCode { code: 1 },
1502 leaked: false,
1503 }
1504 };
1505
1506 let execute_status = ExecuteStatus {
1507 retry_data: RetryData {
1508 attempt: 1,
1509 total_attempts: 1,
1510 },
1511 output: ChildExecutionOutputDescription::Output {
1512 result: Some(result.clone()),
1513 output: ZipStoreOutputDescription::Split {
1514 stdout: None,
1515 stderr: None,
1516 },
1517 errors: None,
1518 },
1519 result,
1520 start_time: Utc::now().into(),
1521 time_taken: Duration::from_millis(100),
1522 is_slow: false,
1523 delay_before_start: Duration::ZERO,
1524 error_summary: None,
1525 output_error_slice: None,
1526 };
1527
1528 TestEventKindSummary::Output(OutputEventKind::TestFinished {
1529 stress_index: stress_index.map(|(current, total)| StressIndexSummary {
1530 current,
1531 total: total.and_then(NonZero::new),
1532 }),
1533 test_instance,
1534 success_output: TestOutputDisplay::Never,
1535 failure_output: TestOutputDisplay::Never,
1536 junit_store_success_output: false,
1537 junit_store_failure_output: false,
1538 junit_flaky_fail_status: JunitFlakyFailStatus::default(),
1539 run_statuses: ExecutionStatuses::new(vec![execute_status], FlakyResult::default()),
1540 current_stats: RunStats::default(),
1541 running: 0,
1542 })
1543 }
1544
1545 fn make_flaky_test_finished(
1548 test_instance: OwnedTestInstanceId,
1549 flaky_result: FlakyResult,
1550 ) -> TestEventKindSummary<RecordingSpec> {
1551 let fail_result = ExecutionResultDescription::Fail {
1552 failure: FailureDescription::ExitCode { code: 1 },
1553 leaked: false,
1554 };
1555 let pass_result = ExecutionResultDescription::Pass;
1556
1557 let fail_status = ExecuteStatus {
1558 retry_data: RetryData {
1559 attempt: 1,
1560 total_attempts: 2,
1561 },
1562 output: ChildExecutionOutputDescription::Output {
1563 result: Some(fail_result.clone()),
1564 output: ZipStoreOutputDescription::Split {
1565 stdout: None,
1566 stderr: None,
1567 },
1568 errors: None,
1569 },
1570 result: fail_result,
1571 start_time: Utc::now().into(),
1572 time_taken: Duration::from_millis(100),
1573 is_slow: false,
1574 delay_before_start: Duration::ZERO,
1575 error_summary: None,
1576 output_error_slice: None,
1577 };
1578
1579 let pass_status = ExecuteStatus {
1580 retry_data: RetryData {
1581 attempt: 2,
1582 total_attempts: 2,
1583 },
1584 output: ChildExecutionOutputDescription::Output {
1585 result: Some(pass_result.clone()),
1586 output: ZipStoreOutputDescription::Split {
1587 stdout: None,
1588 stderr: None,
1589 },
1590 errors: None,
1591 },
1592 result: pass_result,
1593 start_time: Utc::now().into(),
1594 time_taken: Duration::from_millis(100),
1595 is_slow: false,
1596 delay_before_start: Duration::ZERO,
1597 error_summary: None,
1598 output_error_slice: None,
1599 };
1600
1601 TestEventKindSummary::Output(OutputEventKind::TestFinished {
1602 stress_index: None,
1603 test_instance,
1604 success_output: TestOutputDisplay::Never,
1605 failure_output: TestOutputDisplay::Never,
1606 junit_store_success_output: false,
1607 junit_store_failure_output: false,
1608 junit_flaky_fail_status: JunitFlakyFailStatus::default(),
1609 run_statuses: ExecutionStatuses::new(vec![fail_status, pass_status], flaky_result),
1610 current_stats: RunStats::default(),
1611 running: 0,
1612 })
1613 }
1614
1615 #[test]
1622 fn flaky_result_affects_rerun_outcome() {
1623 let test_flaky_fail = OwnedTestInstanceId {
1624 binary_id: RustBinaryId::new("test-binary"),
1625 test_name: TestCaseName::new("flaky_fail"),
1626 };
1627 let test_flaky_pass = OwnedTestInstanceId {
1628 binary_id: RustBinaryId::new("test-binary"),
1629 test_name: TestCaseName::new("flaky_pass"),
1630 };
1631
1632 let events = [
1633 make_flaky_test_finished(test_flaky_fail.clone(), FlakyResult::Fail),
1634 make_flaky_test_finished(test_flaky_pass.clone(), FlakyResult::Pass),
1635 ];
1636
1637 let outcomes = collect_from_events(events.iter().map(Ok::<_, Infallible>)).unwrap();
1638
1639 assert_eq!(
1640 outcomes.get(&test_flaky_fail),
1641 Some(&TestOutcome::Failed),
1642 "flaky test with FlakyResult::Fail should be Failed for rerun"
1643 );
1644 assert_eq!(
1645 outcomes.get(&test_flaky_pass),
1646 Some(&TestOutcome::Passed),
1647 "flaky test with FlakyResult::Pass should be Passed for rerun"
1648 );
1649 }
1650
1651 #[test]
1657 fn stress_run_accumulation() {
1658 let test_pass_fail_pass = OwnedTestInstanceId {
1660 binary_id: RustBinaryId::new("test-binary"),
1661 test_name: TestCaseName::new("pass_fail_pass"),
1662 };
1663
1664 let test_all_pass = OwnedTestInstanceId {
1666 binary_id: RustBinaryId::new("test-binary"),
1667 test_name: TestCaseName::new("all_pass"),
1668 };
1669
1670 let test_all_fail = OwnedTestInstanceId {
1672 binary_id: RustBinaryId::new("test-binary"),
1673 test_name: TestCaseName::new("all_fail"),
1674 };
1675
1676 let test_fail_first = OwnedTestInstanceId {
1678 binary_id: RustBinaryId::new("test-binary"),
1679 test_name: TestCaseName::new("fail_first"),
1680 };
1681
1682 let test_regular_pass = OwnedTestInstanceId {
1684 binary_id: RustBinaryId::new("test-binary"),
1685 test_name: TestCaseName::new("regular_pass"),
1686 };
1687
1688 let test_regular_fail = OwnedTestInstanceId {
1690 binary_id: RustBinaryId::new("test-binary"),
1691 test_name: TestCaseName::new("regular_fail"),
1692 };
1693
1694 let events = [
1696 make_test_finished(test_pass_fail_pass.clone(), Some((0, Some(3))), true),
1698 make_test_finished(test_pass_fail_pass.clone(), Some((1, Some(3))), false),
1699 make_test_finished(test_pass_fail_pass.clone(), Some((2, Some(3))), true),
1700 make_test_finished(test_all_pass.clone(), Some((0, Some(3))), true),
1702 make_test_finished(test_all_pass.clone(), Some((1, Some(3))), true),
1703 make_test_finished(test_all_pass.clone(), Some((2, Some(3))), true),
1704 make_test_finished(test_all_fail.clone(), Some((0, Some(3))), false),
1706 make_test_finished(test_all_fail.clone(), Some((1, Some(3))), false),
1707 make_test_finished(test_all_fail.clone(), Some((2, Some(3))), false),
1708 make_test_finished(test_fail_first.clone(), Some((0, Some(3))), false),
1710 make_test_finished(test_fail_first.clone(), Some((1, Some(3))), true),
1711 make_test_finished(test_fail_first.clone(), Some((2, Some(3))), true),
1712 make_test_finished(test_regular_pass.clone(), None, true),
1714 make_test_finished(test_regular_fail.clone(), None, false),
1716 ];
1717
1718 let outcomes = collect_from_events(events.iter().map(Ok::<_, Infallible>)).unwrap();
1719
1720 assert_eq!(
1721 outcomes.get(&test_pass_fail_pass),
1722 Some(&TestOutcome::Failed),
1723 "[Pass, Fail, Pass] should be Failed"
1724 );
1725 assert_eq!(
1726 outcomes.get(&test_all_pass),
1727 Some(&TestOutcome::Passed),
1728 "[Pass, Pass, Pass] should be Passed"
1729 );
1730 assert_eq!(
1731 outcomes.get(&test_all_fail),
1732 Some(&TestOutcome::Failed),
1733 "[Fail, Fail, Fail] should be Failed"
1734 );
1735 assert_eq!(
1736 outcomes.get(&test_fail_first),
1737 Some(&TestOutcome::Failed),
1738 "[Fail, Pass, Pass] should be Failed"
1739 );
1740 assert_eq!(
1741 outcomes.get(&test_regular_pass),
1742 Some(&TestOutcome::Passed),
1743 "regular pass should be Passed"
1744 );
1745 assert_eq!(
1746 outcomes.get(&test_regular_fail),
1747 Some(&TestOutcome::Failed),
1748 "regular fail should be Failed"
1749 );
1750 }
1751
1752 #[test]
1757 fn stress_run_multiple_tests_independent() {
1758 let test_a = OwnedTestInstanceId {
1759 binary_id: RustBinaryId::new("test-binary"),
1760 test_name: TestCaseName::new("test_a"),
1761 };
1762 let test_b = OwnedTestInstanceId {
1763 binary_id: RustBinaryId::new("test-binary"),
1764 test_name: TestCaseName::new("test_b"),
1765 };
1766
1767 let events = [
1771 make_test_finished(test_a.clone(), Some((0, Some(2))), true),
1772 make_test_finished(test_b.clone(), Some((0, Some(2))), true),
1773 make_test_finished(test_a.clone(), Some((1, Some(2))), true),
1774 make_test_finished(test_b.clone(), Some((1, Some(2))), false),
1775 ];
1776
1777 let outcomes = collect_from_events(events.iter().map(Ok::<_, Infallible>)).unwrap();
1778
1779 assert_eq!(
1780 outcomes.get(&test_a),
1781 Some(&TestOutcome::Passed),
1782 "test_a [Pass, Pass] should be Passed"
1783 );
1784 assert_eq!(
1785 outcomes.get(&test_b),
1786 Some(&TestOutcome::Failed),
1787 "test_b [Pass, Fail] should be Failed"
1788 );
1789 }
1790}