test_r_core/
internal.rs

1use crate::args::{Arguments, TimeThreshold};
2use crate::bench::Bencher;
3use crate::stats::Summary;
4use std::any::Any;
5use std::cmp::{max, Ordering};
6use std::fmt::{Debug, Display, Formatter};
7use std::future::Future;
8use std::hash::Hash;
9use std::pin::Pin;
10use std::process::ExitCode;
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, SystemTime};
13
14#[derive(Clone)]
15#[allow(clippy::type_complexity)]
16pub enum TestFunction {
17    Sync(
18        Arc<
19            dyn Fn(Arc<dyn DependencyView + Send + Sync>) -> Box<dyn TestReturnValue>
20                + Send
21                + Sync
22                + 'static,
23        >,
24    ),
25    SyncBench(
26        Arc<dyn Fn(&mut Bencher, Arc<dyn DependencyView + Send + Sync>) + Send + Sync + 'static>,
27    ),
28    #[cfg(feature = "tokio")]
29    Async(
30        Arc<
31            dyn (Fn(
32                    Arc<dyn DependencyView + Send + Sync>,
33                ) -> Pin<Box<dyn Future<Output = Box<dyn TestReturnValue>>>>)
34                + Send
35                + Sync
36                + 'static,
37        >,
38    ),
39    #[cfg(feature = "tokio")]
40    AsyncBench(
41        Arc<
42            dyn for<'a> Fn(
43                    &'a mut crate::bench::AsyncBencher,
44                    Arc<dyn DependencyView + Send + Sync>,
45                ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
46                + Send
47                + Sync
48                + 'static,
49        >,
50    ),
51}
52
53impl TestFunction {
54    #[cfg(not(feature = "tokio"))]
55    pub fn is_bench(&self) -> bool {
56        matches!(self, TestFunction::SyncBench(_))
57    }
58
59    #[cfg(feature = "tokio")]
60    pub fn is_bench(&self) -> bool {
61        matches!(
62            self,
63            TestFunction::SyncBench(_) | TestFunction::AsyncBench(_)
64        )
65    }
66}
67
68pub trait TestReturnValue {
69    fn as_result(&self) -> Result<(), String>;
70}
71
72impl TestReturnValue for () {
73    fn as_result(&self) -> Result<(), String> {
74        Ok(())
75    }
76}
77
78impl<T, E: Display> TestReturnValue for Result<T, E> {
79    fn as_result(&self) -> Result<(), String> {
80        self.as_ref().map(|_| ()).map_err(|e| e.to_string())
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum ShouldPanic {
86    No,
87    Yes,
88    WithMessage(String),
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum TestType {
93    UnitTest,
94    IntegrationTest,
95}
96
97impl TestType {
98    pub fn from_path(path: &str) -> Self {
99        if path.contains("/src/") {
100            TestType::UnitTest
101        } else {
102            TestType::IntegrationTest
103        }
104    }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum FlakinessControl {
109    None,
110    ProveNonFlaky(usize),
111    RetryKnownFlaky(usize),
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum CaptureControl {
116    Default,
117    AlwaysCapture,
118    NeverCapture,
119}
120
121impl CaptureControl {
122    pub fn requires_capturing(&self, default: bool) -> bool {
123        match self {
124            CaptureControl::Default => default,
125            CaptureControl::AlwaysCapture => true,
126            CaptureControl::NeverCapture => false,
127        }
128    }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum ReportTimeControl {
133    Default,
134    Enabled,
135    Disabled,
136}
137
138#[derive(Clone)]
139pub struct TestProperties {
140    pub should_panic: ShouldPanic,
141    pub test_type: TestType,
142    pub timeout: Option<Duration>,
143    pub flakiness_control: FlakinessControl,
144    pub capture_control: CaptureControl,
145    pub report_time_control: ReportTimeControl,
146    pub ensure_time_control: ReportTimeControl,
147    pub tags: Vec<String>,
148}
149
150impl TestProperties {
151    pub fn unit_test() -> Self {
152        TestProperties {
153            test_type: TestType::UnitTest,
154            ..Default::default()
155        }
156    }
157
158    pub fn integration_test() -> Self {
159        TestProperties {
160            test_type: TestType::IntegrationTest,
161            ..Default::default()
162        }
163    }
164}
165
166impl Default for TestProperties {
167    fn default() -> Self {
168        Self {
169            should_panic: ShouldPanic::No,
170            test_type: TestType::UnitTest,
171            timeout: None,
172            flakiness_control: FlakinessControl::None,
173            capture_control: CaptureControl::Default,
174            report_time_control: ReportTimeControl::Default,
175            ensure_time_control: ReportTimeControl::Default,
176            tags: Vec::new(),
177        }
178    }
179}
180
181#[derive(Clone)]
182pub struct RegisteredTest {
183    pub name: String,
184    pub crate_name: String,
185    pub module_path: String,
186    pub is_ignored: bool,
187    pub run: TestFunction,
188    pub props: TestProperties,
189}
190
191impl RegisteredTest {
192    pub fn filterable_name(&self) -> String {
193        if !self.module_path.is_empty() {
194            format!("{}::{}", self.module_path, self.name)
195        } else {
196            self.name.clone()
197        }
198    }
199
200    pub fn fully_qualified_name(&self) -> String {
201        [&self.crate_name, &self.module_path, &self.name]
202            .into_iter()
203            .filter(|s| !s.is_empty())
204            .cloned()
205            .collect::<Vec<String>>()
206            .join("::")
207    }
208
209    pub fn crate_and_module(&self) -> String {
210        [&self.crate_name, &self.module_path]
211            .into_iter()
212            .filter(|s| !s.is_empty())
213            .cloned()
214            .collect::<Vec<String>>()
215            .join("::")
216    }
217}
218
219impl Debug for RegisteredTest {
220    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
221        f.debug_struct("RegisteredTest")
222            .field("name", &self.name)
223            .field("crate_name", &self.crate_name)
224            .field("module_path", &self.module_path)
225            .finish()
226    }
227}
228
229pub static REGISTERED_TESTS: Mutex<Vec<RegisteredTest>> = Mutex::new(Vec::new());
230
231#[derive(Clone)]
232#[allow(clippy::type_complexity)]
233pub enum DependencyConstructor {
234    Sync(
235        Arc<
236            dyn (Fn(Arc<dyn DependencyView + Send + Sync>) -> Arc<dyn Any + Send + Sync + 'static>)
237                + Send
238                + Sync
239                + 'static,
240        >,
241    ),
242    Async(
243        Arc<
244            dyn (Fn(
245                    Arc<dyn DependencyView + Send + Sync>,
246                ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
247                + Send
248                + Sync
249                + 'static,
250        >,
251    ),
252}
253
254#[derive(Clone)]
255pub struct RegisteredDependency {
256    pub name: String, // TODO: Should we use TypeId here?
257    pub crate_name: String,
258    pub module_path: String,
259    pub constructor: DependencyConstructor,
260    pub dependencies: Vec<String>,
261}
262
263impl Debug for RegisteredDependency {
264    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
265        f.debug_struct("RegisteredDependency")
266            .field("name", &self.name)
267            .field("crate_name", &self.crate_name)
268            .field("module_path", &self.module_path)
269            .finish()
270    }
271}
272
273impl PartialEq for RegisteredDependency {
274    fn eq(&self, other: &Self) -> bool {
275        self.name == other.name
276    }
277}
278
279impl Eq for RegisteredDependency {}
280
281impl Hash for RegisteredDependency {
282    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
283        self.name.hash(state);
284    }
285}
286
287impl RegisteredDependency {
288    pub fn crate_and_module(&self) -> String {
289        [&self.crate_name, &self.module_path]
290            .into_iter()
291            .filter(|s| !s.is_empty())
292            .cloned()
293            .collect::<Vec<String>>()
294            .join("::")
295    }
296}
297
298pub static REGISTERED_DEPENDENCY_CONSTRUCTORS: Mutex<Vec<RegisteredDependency>> =
299    Mutex::new(Vec::new());
300
301#[derive(Debug, Clone)]
302pub enum RegisteredTestSuiteProperty {
303    Sequential {
304        name: String,
305        crate_name: String,
306        module_path: String,
307    },
308    Tag {
309        name: String,
310        crate_name: String,
311        module_path: String,
312        tag: String,
313    },
314}
315
316impl RegisteredTestSuiteProperty {
317    pub fn crate_name(&self) -> &String {
318        match self {
319            RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
320            RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
321        }
322    }
323
324    pub fn module_path(&self) -> &String {
325        match self {
326            RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
327            RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
328        }
329    }
330
331    pub fn name(&self) -> &String {
332        match self {
333            RegisteredTestSuiteProperty::Sequential { name, .. } => name,
334            RegisteredTestSuiteProperty::Tag { name, .. } => name,
335        }
336    }
337
338    pub fn crate_and_module(&self) -> String {
339        [self.crate_name(), self.module_path(), self.name()]
340            .into_iter()
341            .filter(|s| !s.is_empty())
342            .cloned()
343            .collect::<Vec<String>>()
344            .join("::")
345    }
346}
347
348pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
349    Mutex::new(Vec::new());
350
351#[derive(Clone)]
352#[allow(clippy::type_complexity)]
353pub enum TestGeneratorFunction {
354    Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
355    Async(
356        Arc<
357            dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
358                + Send
359                + Sync
360                + 'static,
361        >,
362    ),
363}
364
365pub struct DynamicTestRegistration {
366    tests: Vec<GeneratedTest>,
367}
368
369impl Default for DynamicTestRegistration {
370    fn default() -> Self {
371        Self::new()
372    }
373}
374
375impl DynamicTestRegistration {
376    pub fn new() -> Self {
377        Self { tests: Vec::new() }
378    }
379
380    pub fn to_vec(self) -> Vec<GeneratedTest> {
381        self.tests
382    }
383
384    pub fn add_sync_test<R: TestReturnValue + 'static>(
385        &mut self,
386        name: impl AsRef<str>,
387        props: TestProperties,
388        run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
389    ) {
390        self.tests.push(GeneratedTest {
391            name: name.as_ref().to_string(),
392            run: TestFunction::Sync(Arc::new(move |deps| {
393                Box::new(run(deps)) as Box<dyn TestReturnValue>
394            })),
395            props,
396        });
397    }
398
399    #[cfg(feature = "tokio")]
400    pub fn add_async_test<R: TestReturnValue + 'static>(
401        &mut self,
402        name: impl AsRef<str>,
403        props: TestProperties,
404        run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
405            + Send
406            + Sync
407            + Clone
408            + 'static,
409    ) {
410        self.tests.push(GeneratedTest {
411            name: name.as_ref().to_string(),
412            run: TestFunction::Async(Arc::new(move |deps| {
413                let run = run.clone();
414                Box::pin(async move {
415                    let r = run(deps).await;
416                    Box::new(r) as Box<dyn TestReturnValue>
417                })
418            })),
419            props,
420        });
421    }
422}
423
424#[derive(Clone)]
425pub struct GeneratedTest {
426    pub name: String,
427    pub run: TestFunction,
428    pub props: TestProperties,
429}
430
431#[derive(Clone)]
432pub struct RegisteredTestGenerator {
433    pub name: String,
434    pub crate_name: String,
435    pub module_path: String,
436    pub run: TestGeneratorFunction,
437    pub is_ignored: bool,
438}
439
440impl RegisteredTestGenerator {
441    pub fn crate_and_module(&self) -> String {
442        [&self.crate_name, &self.module_path]
443            .into_iter()
444            .filter(|s| !s.is_empty())
445            .cloned()
446            .collect::<Vec<String>>()
447            .join("::")
448    }
449}
450
451pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
452
453pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
454    if let Some(tag_list) = filter.strip_prefix(":tag:") {
455        if tag_list.is_empty() {
456            // Filtering for tags with NO TAGS
457            test.props.tags.is_empty()
458        } else {
459            let or_tags = tag_list.split('|').collect::<Vec<&str>>();
460            let mut result = false;
461            for or_tag in or_tags {
462                let and_tags = or_tag.split('&').collect::<Vec<&str>>();
463                let mut and_result = true;
464                for and_tag in and_tags {
465                    if !test.props.tags.contains(&and_tag.to_string()) {
466                        and_result = false;
467                        break;
468                    }
469                }
470                if and_result {
471                    result = true;
472                    break;
473                }
474            }
475            result
476        }
477    } else if exact {
478        test.filterable_name() == filter
479    } else {
480        test.filterable_name().contains(filter)
481    }
482}
483
484pub(crate) fn apply_suite_tags(
485    tests: &[RegisteredTest],
486    props: &[RegisteredTestSuiteProperty],
487) -> Vec<RegisteredTest> {
488    let tag_props = props
489        .iter()
490        .filter_map(|prop| match prop {
491            RegisteredTestSuiteProperty::Tag { tag, .. } => {
492                let prefix = prop.crate_and_module();
493                Some((prefix, tag.clone()))
494            }
495            _ => None,
496        })
497        .collect::<Vec<_>>();
498
499    let mut result = Vec::new();
500    for test in tests {
501        let mut test = test.clone();
502        for (prefix, tag) in &tag_props {
503            if &test.crate_and_module() == prefix {
504                test.props.tags.push(tag.clone());
505            }
506        }
507        result.push(test);
508    }
509    result
510}
511
512pub(crate) fn filter_registered_tests(
513    args: &Arguments,
514    registered_tests: &[RegisteredTest],
515) -> Vec<RegisteredTest> {
516    registered_tests
517        .iter()
518        .filter(|registered_test| {
519            args.skip
520                .iter()
521                .all(|skip| &registered_test.filterable_name() != skip)
522        })
523        .filter(|registered_test| {
524            args.filter.as_ref().is_none()
525                || args
526                    .filter
527                    .as_ref()
528                    .map(|filter| filter_test(registered_test, filter, args.exact))
529                    .unwrap_or(false)
530        })
531        .filter(|registered_tests| {
532            (args.bench && registered_tests.run.is_bench())
533                || (args.test && !registered_tests.run.is_bench())
534                || (!args.bench && !args.test)
535        })
536        .filter(|registered_test| {
537            !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
538        })
539        .cloned()
540        .collect::<Vec<_>>()
541}
542
543fn add_generated_tests(
544    target: &mut Vec<RegisteredTest>,
545    generator: &RegisteredTestGenerator,
546    generated: Vec<GeneratedTest>,
547) {
548    target.extend(generated.into_iter().map(|test| RegisteredTest {
549        name: format!("{}::{}", generator.name, test.name),
550        crate_name: generator.crate_name.clone(),
551        module_path: generator.module_path.clone(),
552        is_ignored: generator.is_ignored,
553        run: test.run,
554        props: test.props,
555    }));
556}
557
558#[cfg(feature = "tokio")]
559pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
560    let mut result = Vec::new();
561    for generator in generators {
562        match &generator.run {
563            TestGeneratorFunction::Sync(generator_fn) => {
564                let tests = generator_fn();
565                add_generated_tests(&mut result, generator, tests);
566            }
567            TestGeneratorFunction::Async(generator_fn) => {
568                let tests = generator_fn().await;
569                add_generated_tests(&mut result, generator, tests);
570            }
571        }
572    }
573    result
574}
575
576pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
577    let mut result = Vec::new();
578    for generator in generators {
579        match &generator.run {
580            TestGeneratorFunction::Sync(generator_fn) => {
581                let tests = generator_fn();
582                add_generated_tests(&mut result, generator, tests);
583            }
584            TestGeneratorFunction::Async(_) => {
585                panic!("Async test generators are not supported in sync mode")
586            }
587        }
588    }
589    result
590}
591
592pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
593    let should_ensure_time = match test.props.ensure_time_control {
594        ReportTimeControl::Default => args.ensure_time,
595        ReportTimeControl::Enabled => true,
596        ReportTimeControl::Disabled => false,
597    };
598    if should_ensure_time {
599        match test.props.test_type {
600            TestType::UnitTest => Some(args.unit_test_threshold()),
601            TestType::IntegrationTest => Some(args.integration_test_threshold()),
602        }
603    } else {
604        None
605    }
606}
607
608pub enum TestResult<Panic = Box<dyn Any + Send>> {
609    Passed {
610        captured: Vec<CapturedOutput>,
611        exec_time: Duration,
612    },
613    Benchmarked {
614        captured: Vec<CapturedOutput>,
615        exec_time: Duration,
616        ns_iter_summ: Summary,
617        mb_s: usize,
618    },
619    Failed {
620        panic: Panic,
621        captured: Vec<CapturedOutput>,
622        exec_time: Duration,
623    },
624    Ignored {
625        captured: Vec<CapturedOutput>,
626    },
627}
628
629impl<Panic> TestResult<Panic> {
630    pub fn passed(exec_time: Duration) -> Self {
631        TestResult::Passed {
632            captured: Vec::new(),
633            exec_time,
634        }
635    }
636
637    pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
638        TestResult::Benchmarked {
639            captured: Vec::new(),
640            exec_time,
641            ns_iter_summ,
642            mb_s,
643        }
644    }
645
646    pub fn failed(exec_time: Duration, panic: Panic) -> Self {
647        TestResult::Failed {
648            panic,
649            captured: Vec::new(),
650            exec_time,
651        }
652    }
653
654    pub fn ignored() -> Self {
655        TestResult::Ignored {
656            captured: Vec::new(),
657        }
658    }
659
660    pub(crate) fn is_passed(&self) -> bool {
661        matches!(self, TestResult::Passed { .. })
662    }
663
664    pub(crate) fn is_benchmarked(&self) -> bool {
665        matches!(self, TestResult::Benchmarked { .. })
666    }
667
668    pub(crate) fn is_failed(&self) -> bool {
669        matches!(self, TestResult::Failed { .. })
670    }
671
672    pub(crate) fn is_ignored(&self) -> bool {
673        matches!(self, TestResult::Ignored { .. })
674    }
675
676    pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
677        match self {
678            TestResult::Passed { captured, .. } => captured,
679            TestResult::Failed { captured, .. } => captured,
680            TestResult::Ignored { captured, .. } => captured,
681            TestResult::Benchmarked { captured, .. } => captured,
682        }
683    }
684
685    pub(crate) fn stats(&self) -> Option<&Summary> {
686        match self {
687            TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
688            _ => None,
689        }
690    }
691
692    pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
693        match self {
694            TestResult::Passed {
695                captured: captured_ref,
696                ..
697            } => *captured_ref = captured,
698            TestResult::Failed {
699                captured: captured_ref,
700                ..
701            } => *captured_ref = captured,
702            TestResult::Ignored {
703                captured: captured_ref,
704            } => *captured_ref = captured,
705            TestResult::Benchmarked {
706                captured: captured_ref,
707                ..
708            } => *captured_ref = captured,
709        }
710    }
711}
712
713impl TestResult<Box<dyn Any + Send>> {
714    #[allow(clippy::should_implement_trait)]
715    pub fn clone(&self) -> TestResult<String> {
716        match self {
717            TestResult::Passed {
718                captured,
719                exec_time,
720            } => TestResult::Passed {
721                captured: captured.clone(),
722                exec_time: *exec_time,
723            },
724            TestResult::Benchmarked {
725                captured,
726                exec_time,
727                ns_iter_summ,
728                mb_s,
729            } => TestResult::Benchmarked {
730                captured: captured.clone(),
731                exec_time: *exec_time,
732                ns_iter_summ: *ns_iter_summ,
733                mb_s: *mb_s,
734            },
735            TestResult::Failed {
736                captured,
737                exec_time,
738                ..
739            } => {
740                let failure_message = self.failure_message().unwrap_or("").to_string();
741                TestResult::Failed {
742                    panic: failure_message,
743                    captured: captured.clone(),
744                    exec_time: *exec_time,
745                }
746            }
747            TestResult::Ignored { captured } => TestResult::Ignored {
748                captured: captured.clone(),
749            },
750        }
751    }
752
753    pub(crate) fn failure_message(&self) -> Option<&str> {
754        match self {
755            TestResult::Failed { panic, .. } => panic
756                .downcast_ref::<String>()
757                .map(|s| s.as_str())
758                .or(panic.downcast_ref::<&str>().copied()),
759            _ => None,
760        }
761    }
762
763    pub(crate) fn from_result<A>(
764        should_panic: &ShouldPanic,
765        elapsed: Duration,
766        result: Result<A, Box<dyn Any + Send>>,
767    ) -> Self {
768        match result {
769            Ok(_) => {
770                if should_panic == &ShouldPanic::No {
771                    TestResult::passed(elapsed)
772                } else {
773                    TestResult::failed(elapsed, Box::new("Test did not panic as expected"))
774                }
775            }
776            Err(panic) => Self::from_panic(should_panic, elapsed, panic),
777        }
778    }
779
780    pub(crate) fn from_summary(
781        should_panic: &ShouldPanic,
782        elapsed: Duration,
783        result: Result<Summary, Box<dyn Any + Send>>,
784        bytes: u64,
785    ) -> Self {
786        match result {
787            Ok(summary) => {
788                let ns_iter = max(summary.median as u64, 1);
789                let mb_s = bytes * 1000 / ns_iter;
790                TestResult::benchmarked(elapsed, summary, mb_s as usize)
791            }
792            Err(panic) => Self::from_panic(should_panic, elapsed, panic),
793        }
794    }
795
796    fn from_panic(
797        should_panic: &ShouldPanic,
798        elapsed: Duration,
799        panic: Box<dyn Any + Send>,
800    ) -> Self {
801        match should_panic {
802            ShouldPanic::WithMessage(expected) => {
803                let failure = TestResult::failed(elapsed, panic);
804                let message = failure.failure_message();
805
806                match message {
807                    Some(message) if message.contains(expected) => TestResult::passed(elapsed),
808                    _ => TestResult::failed(
809                        elapsed,
810                        Box::new(format!(
811                            "Test panicked with unexpected message: {}",
812                            message.unwrap_or_default()
813                        )),
814                    ),
815                }
816            }
817            ShouldPanic::Yes => TestResult::passed(elapsed),
818            ShouldPanic::No => TestResult::failed(elapsed, panic),
819        }
820    }
821}
822
823impl TestResult<String> {
824    pub(crate) fn failure_message(&self) -> Option<&str> {
825        match self {
826            TestResult::Failed { panic, .. } => Some(panic),
827            _ => None,
828        }
829    }
830}
831
832pub struct SuiteResult {
833    pub passed: usize,
834    pub failed: usize,
835    pub ignored: usize,
836    pub measured: usize,
837    pub filtered_out: usize,
838    pub exec_time: Duration,
839}
840
841impl SuiteResult {
842    pub fn from_test_results<Panic>(
843        registered_tests: &[RegisteredTest],
844        results: &[(RegisteredTest, TestResult<Panic>)],
845        exec_time: Duration,
846    ) -> Self {
847        let passed = results
848            .iter()
849            .filter(|(_, result)| result.is_passed())
850            .count();
851        let measured = results
852            .iter()
853            .filter(|(_, result)| result.is_benchmarked())
854            .count();
855        let failed = results
856            .iter()
857            .filter(|(_, result)| result.is_failed())
858            .count();
859        let ignored = results
860            .iter()
861            .filter(|(_, result)| result.is_ignored())
862            .count();
863        let filtered_out = registered_tests.len() - results.len();
864
865        Self {
866            passed,
867            failed,
868            ignored,
869            measured,
870            filtered_out,
871            exec_time,
872        }
873    }
874
875    pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
876        if results.iter().any(|(_, result)| result.is_failed()) {
877            ExitCode::from(101)
878        } else {
879            ExitCode::SUCCESS
880        }
881    }
882}
883
884pub trait DependencyView: Debug {
885    fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
886}
887
888impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
889    fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
890        self.as_ref().get(name)
891    }
892}
893
894#[derive(Debug, Clone, Eq, PartialEq)]
895pub enum CapturedOutput {
896    Stdout { timestamp: SystemTime, line: String },
897    Stderr { timestamp: SystemTime, line: String },
898}
899
900impl CapturedOutput {
901    pub fn stdout(line: String) -> Self {
902        CapturedOutput::Stdout {
903            timestamp: SystemTime::now(),
904            line,
905        }
906    }
907
908    pub fn stderr(line: String) -> Self {
909        CapturedOutput::Stderr {
910            timestamp: SystemTime::now(),
911            line,
912        }
913    }
914
915    pub fn timestamp(&self) -> SystemTime {
916        match self {
917            CapturedOutput::Stdout { timestamp, .. } => *timestamp,
918            CapturedOutput::Stderr { timestamp, .. } => *timestamp,
919        }
920    }
921
922    pub fn line(&self) -> &str {
923        match self {
924            CapturedOutput::Stdout { line, .. } => line,
925            CapturedOutput::Stderr { line, .. } => line,
926        }
927    }
928}
929
930impl PartialOrd for CapturedOutput {
931    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
932        Some(self.cmp(other))
933    }
934}
935
936impl Ord for CapturedOutput {
937    fn cmp(&self, other: &Self) -> Ordering {
938        self.timestamp().cmp(&other.timestamp())
939    }
940}