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