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, 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 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| ®istered_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}