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 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}