1use crate::args::{Arguments, TimeThreshold};
2use crate::bench::Bencher;
3use crate::stats::Summary;
4use std::any::{Any, TypeId};
5use std::backtrace::Backtrace;
6use std::cmp::{max, Ordering};
7use std::fmt::{Debug, Display, Formatter};
8use std::future::Future;
9use std::hash::Hash;
10use std::pin::Pin;
11use std::process::ExitCode;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, SystemTime};
14
15#[derive(Clone)]
16#[allow(clippy::type_complexity)]
17pub enum TestFunction {
18 Sync(
19 Arc<
20 dyn Fn(Arc<dyn DependencyView + Send + Sync>) -> Box<dyn TestReturnValue>
21 + Send
22 + Sync
23 + 'static,
24 >,
25 ),
26 SyncBench(
27 Arc<dyn Fn(&mut Bencher, Arc<dyn DependencyView + Send + Sync>) + Send + Sync + 'static>,
28 ),
29 #[cfg(feature = "tokio")]
30 Async(
31 Arc<
32 dyn (Fn(
33 Arc<dyn DependencyView + Send + Sync>,
34 ) -> Pin<Box<dyn Future<Output = Box<dyn TestReturnValue>>>>)
35 + Send
36 + Sync
37 + 'static,
38 >,
39 ),
40 #[cfg(feature = "tokio")]
41 AsyncBench(
42 Arc<
43 dyn for<'a> Fn(
44 &'a mut crate::bench::AsyncBencher,
45 Arc<dyn DependencyView + Send + Sync>,
46 ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
47 + Send
48 + Sync
49 + 'static,
50 >,
51 ),
52}
53
54impl TestFunction {
55 #[cfg(not(feature = "tokio"))]
56 pub fn is_bench(&self) -> bool {
57 matches!(self, TestFunction::SyncBench(_))
58 }
59
60 #[cfg(feature = "tokio")]
61 pub fn is_bench(&self) -> bool {
62 matches!(
63 self,
64 TestFunction::SyncBench(_) | TestFunction::AsyncBench(_)
65 )
66 }
67}
68
69pub trait TestReturnValue {
70 fn into_result(self: Box<Self>) -> Result<(), FailureCause>;
71}
72
73impl TestReturnValue for () {
74 fn into_result(self: Box<Self>) -> Result<(), FailureCause> {
75 Ok(())
76 }
77}
78
79impl<T, E: Display + Debug + Send + Sync + 'static> TestReturnValue for Result<T, E> {
80 fn into_result(self: Box<Self>) -> Result<(), FailureCause> {
81 match *self {
82 Ok(_) => Ok(()),
83 Err(e) => Err(FailureCause::from_error(e)),
84 }
85 }
86}
87
88#[derive(Clone)]
89pub enum FailureCause {
90 ReturnedError {
93 display: String,
94 debug: String,
95 prefer_debug: bool,
96 error: Arc<dyn Any + Send + Sync>,
97 },
98 ReturnedMessage(String),
100 Panic(PanicCause),
102 HarnessError(String),
104}
105
106#[derive(Debug, Clone)]
107pub struct PanicCause {
108 pub message: Option<String>,
109 pub location: Option<PanicLocation>,
110 pub backtrace: Option<Arc<Backtrace>>,
111}
112
113#[derive(Debug, Clone)]
114pub struct PanicLocation {
115 pub file: String,
116 pub line: u32,
117 pub column: u32,
118}
119
120impl std::fmt::Debug for FailureCause {
121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122 match self {
123 FailureCause::ReturnedError { display, .. } => {
124 f.debug_tuple("ReturnedError").field(display).finish()
125 }
126 FailureCause::ReturnedMessage(s) => f.debug_tuple("ReturnedMessage").field(s).finish(),
127 FailureCause::Panic(p) => f.debug_tuple("Panic").field(p).finish(),
128 FailureCause::HarnessError(s) => f.debug_tuple("HarnessError").field(s).finish(),
129 }
130 }
131}
132
133impl FailureCause {
134 pub fn from_error<E: Display + Debug + Send + Sync + 'static>(e: E) -> Self {
135 if TypeId::of::<E>() == TypeId::of::<String>() {
136 let any: Box<dyn Any + Send + Sync> = Box::new(e);
137 return FailureCause::ReturnedMessage(*any.downcast::<String>().unwrap());
138 }
139
140 let mut _prefer_debug = false;
141 #[cfg(feature = "anyhow")]
142 {
143 _prefer_debug = TypeId::of::<E>() == TypeId::of::<anyhow::Error>();
144 }
145
146 FailureCause::ReturnedError {
147 display: format!("{e:#}"),
148 debug: format!("{e:?}"),
149 prefer_debug: _prefer_debug,
150 error: Arc::new(e),
151 }
152 }
153
154 pub fn render(&self) -> String {
155 match self {
156 FailureCause::ReturnedError {
157 display,
158 debug,
159 prefer_debug,
160 ..
161 } => {
162 if *prefer_debug {
163 debug.clone()
164 } else {
165 display.clone()
166 }
167 }
168 FailureCause::ReturnedMessage(s) => s.clone(),
169 FailureCause::Panic(p) => p.render(),
170 FailureCause::HarnessError(s) => s.clone(),
171 }
172 }
173
174 pub fn panic_message(&self) -> Option<&str> {
176 match self {
177 FailureCause::Panic(p) => p.message.as_deref(),
178 _ => None,
179 }
180 }
181}
182
183impl PanicCause {
184 pub fn render(&self) -> String {
185 let mut out = self.message.clone().unwrap_or_default();
186 if let Some(loc) = &self.location {
187 out.push_str(&format!("\n at {}:{}:{}", loc.file, loc.line, loc.column));
188 }
189 if let Some(bt) = &self.backtrace {
190 let bt_str = format!("{bt}");
191 if !bt_str.is_empty() && bt_str != "disabled backtrace" {
192 out.push_str(&format!("\n\nStack backtrace:\n{bt}"));
193 }
194 }
195 out
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
200pub enum ShouldPanic {
201 No,
202 Yes,
203 WithMessage(String),
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum TestType {
208 UnitTest,
209 IntegrationTest,
210}
211
212impl TestType {
213 pub fn from_path(path: &str) -> Self {
214 if path.contains("/src/") {
215 TestType::UnitTest
216 } else {
217 TestType::IntegrationTest
218 }
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum FlakinessControl {
224 None,
225 ProveNonFlaky(usize),
226 RetryKnownFlaky(usize),
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub enum DetachedPanicPolicy {
231 FailTest,
232 Ignore,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub enum CaptureControl {
237 Default,
238 AlwaysCapture,
239 NeverCapture,
240}
241
242impl CaptureControl {
243 pub fn requires_capturing(&self, default: bool) -> bool {
244 match self {
245 CaptureControl::Default => default,
246 CaptureControl::AlwaysCapture => true,
247 CaptureControl::NeverCapture => false,
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum ReportTimeControl {
254 Default,
255 Enabled,
256 Disabled,
257}
258
259#[derive(Clone)]
260pub struct TestProperties {
261 pub should_panic: ShouldPanic,
262 pub test_type: TestType,
263 pub timeout: Option<Duration>,
264 pub flakiness_control: FlakinessControl,
265 pub capture_control: CaptureControl,
266 pub report_time_control: ReportTimeControl,
267 pub ensure_time_control: ReportTimeControl,
268 pub tags: Vec<String>,
269 pub is_ignored: bool,
270 pub detached_panic_policy: DetachedPanicPolicy,
271}
272
273impl TestProperties {
274 pub fn unit_test() -> Self {
275 TestProperties {
276 test_type: TestType::UnitTest,
277 ..Default::default()
278 }
279 }
280
281 pub fn integration_test() -> Self {
282 TestProperties {
283 test_type: TestType::IntegrationTest,
284 ..Default::default()
285 }
286 }
287}
288
289impl Default for TestProperties {
290 fn default() -> Self {
291 Self {
292 should_panic: ShouldPanic::No,
293 test_type: TestType::UnitTest,
294 timeout: None,
295 flakiness_control: FlakinessControl::None,
296 capture_control: CaptureControl::Default,
297 report_time_control: ReportTimeControl::Default,
298 ensure_time_control: ReportTimeControl::Default,
299 tags: Vec::new(),
300 is_ignored: false,
301 detached_panic_policy: DetachedPanicPolicy::FailTest,
302 }
303 }
304}
305
306#[derive(Clone)]
307pub struct RegisteredTest {
308 pub name: String,
309 pub crate_name: String,
310 pub module_path: String,
311 pub run: TestFunction,
312 pub props: TestProperties,
313 pub dependencies: Option<Vec<String>>,
314}
315
316impl RegisteredTest {
317 pub fn filterable_name(&self) -> String {
318 if !self.module_path.is_empty() {
319 format!("{}::{}", self.module_path, self.name)
320 } else {
321 self.name.clone()
322 }
323 }
324
325 pub fn fully_qualified_name(&self) -> String {
326 [&self.crate_name, &self.module_path, &self.name]
327 .into_iter()
328 .filter(|s| !s.is_empty())
329 .cloned()
330 .collect::<Vec<String>>()
331 .join("::")
332 }
333
334 pub fn crate_and_module(&self) -> String {
335 [&self.crate_name, &self.module_path]
336 .into_iter()
337 .filter(|s| !s.is_empty())
338 .cloned()
339 .collect::<Vec<String>>()
340 .join("::")
341 }
342}
343
344impl Debug for RegisteredTest {
345 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
346 f.debug_struct("RegisteredTest")
347 .field("name", &self.name)
348 .field("crate_name", &self.crate_name)
349 .field("module_path", &self.module_path)
350 .finish()
351 }
352}
353
354pub static REGISTERED_TESTS: Mutex<Vec<RegisteredTest>> = Mutex::new(Vec::new());
355
356#[derive(Clone)]
357#[allow(clippy::type_complexity)]
358pub enum DependencyConstructor {
359 Sync(
360 Arc<
361 dyn (Fn(Arc<dyn DependencyView + Send + Sync>) -> Arc<dyn Any + Send + Sync + 'static>)
362 + Send
363 + Sync
364 + 'static,
365 >,
366 ),
367 Async(
368 Arc<
369 dyn (Fn(
370 Arc<dyn DependencyView + Send + Sync>,
371 ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
372 + Send
373 + Sync
374 + 'static,
375 >,
376 ),
377}
378
379#[derive(Clone)]
380pub struct RegisteredDependency {
381 pub name: String, pub crate_name: String,
383 pub module_path: String,
384 pub constructor: DependencyConstructor,
385 pub dependencies: Vec<String>,
386}
387
388impl Debug for RegisteredDependency {
389 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
390 f.debug_struct("RegisteredDependency")
391 .field("name", &self.name)
392 .field("crate_name", &self.crate_name)
393 .field("module_path", &self.module_path)
394 .finish()
395 }
396}
397
398impl PartialEq for RegisteredDependency {
399 fn eq(&self, other: &Self) -> bool {
400 self.name == other.name
401 }
402}
403
404impl Eq for RegisteredDependency {}
405
406impl Hash for RegisteredDependency {
407 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
408 self.name.hash(state);
409 }
410}
411
412impl RegisteredDependency {
413 pub fn crate_and_module(&self) -> String {
414 [&self.crate_name, &self.module_path]
415 .into_iter()
416 .filter(|s| !s.is_empty())
417 .cloned()
418 .collect::<Vec<String>>()
419 .join("::")
420 }
421}
422
423pub static REGISTERED_DEPENDENCY_CONSTRUCTORS: Mutex<Vec<RegisteredDependency>> =
424 Mutex::new(Vec::new());
425
426#[derive(Debug, Clone)]
427pub enum RegisteredTestSuiteProperty {
428 Sequential {
429 name: String,
430 crate_name: String,
431 module_path: String,
432 },
433 Tag {
434 name: String,
435 crate_name: String,
436 module_path: String,
437 tag: String,
438 },
439 Timeout {
440 name: String,
441 crate_name: String,
442 module_path: String,
443 timeout: Duration,
444 },
445}
446
447impl RegisteredTestSuiteProperty {
448 pub fn crate_name(&self) -> &String {
449 match self {
450 RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
451 RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
452 RegisteredTestSuiteProperty::Timeout { crate_name, .. } => crate_name,
453 }
454 }
455
456 pub fn module_path(&self) -> &String {
457 match self {
458 RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
459 RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
460 RegisteredTestSuiteProperty::Timeout { module_path, .. } => module_path,
461 }
462 }
463
464 pub fn name(&self) -> &String {
465 match self {
466 RegisteredTestSuiteProperty::Sequential { name, .. } => name,
467 RegisteredTestSuiteProperty::Tag { name, .. } => name,
468 RegisteredTestSuiteProperty::Timeout { name, .. } => name,
469 }
470 }
471
472 pub fn crate_and_module(&self) -> String {
473 [self.crate_name(), self.module_path(), self.name()]
474 .into_iter()
475 .filter(|s| !s.is_empty())
476 .cloned()
477 .collect::<Vec<String>>()
478 .join("::")
479 }
480}
481
482pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
483 Mutex::new(Vec::new());
484
485#[derive(Clone)]
486#[allow(clippy::type_complexity)]
487pub enum TestGeneratorFunction {
488 Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
489 Async(
490 Arc<
491 dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
492 + Send
493 + Sync
494 + 'static,
495 >,
496 ),
497}
498
499pub struct DynamicTestRegistration {
500 tests: Vec<GeneratedTest>,
501}
502
503impl Default for DynamicTestRegistration {
504 fn default() -> Self {
505 Self::new()
506 }
507}
508
509impl DynamicTestRegistration {
510 pub fn new() -> Self {
511 Self { tests: Vec::new() }
512 }
513
514 pub fn to_vec(self) -> Vec<GeneratedTest> {
515 self.tests
516 }
517
518 pub fn add_sync_test<R: TestReturnValue + 'static>(
519 &mut self,
520 name: impl AsRef<str>,
521 props: TestProperties,
522 dependencies: Option<Vec<String>>,
523 run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
524 ) {
525 self.tests.push(GeneratedTest {
526 name: name.as_ref().to_string(),
527 run: TestFunction::Sync(Arc::new(move |deps| {
528 Box::new(run(deps)) as Box<dyn TestReturnValue>
529 })),
530 props,
531 dependencies,
532 });
533 }
534
535 #[cfg(feature = "tokio")]
536 pub fn add_async_test<R: TestReturnValue + 'static>(
537 &mut self,
538 name: impl AsRef<str>,
539 props: TestProperties,
540 dependencies: Option<Vec<String>>,
541 run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
542 + Send
543 + Sync
544 + Clone
545 + 'static,
546 ) {
547 self.tests.push(GeneratedTest {
548 name: name.as_ref().to_string(),
549 run: TestFunction::Async(Arc::new(move |deps| {
550 let run = run.clone();
551 Box::pin(async move {
552 let r = run(deps).await;
553 Box::new(r) as Box<dyn TestReturnValue>
554 })
555 })),
556 props,
557 dependencies,
558 });
559 }
560}
561
562#[derive(Clone)]
563pub struct GeneratedTest {
564 pub name: String,
565 pub run: TestFunction,
566 pub props: TestProperties,
567 pub dependencies: Option<Vec<String>>,
568}
569
570#[derive(Clone)]
571pub struct RegisteredTestGenerator {
572 pub name: String,
573 pub crate_name: String,
574 pub module_path: String,
575 pub run: TestGeneratorFunction,
576 pub is_ignored: bool,
577}
578
579impl RegisteredTestGenerator {
580 pub fn crate_and_module(&self) -> String {
581 [&self.crate_name, &self.module_path]
582 .into_iter()
583 .filter(|s| !s.is_empty())
584 .cloned()
585 .collect::<Vec<String>>()
586 .join("::")
587 }
588}
589
590pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
591
592pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
593 if let Some(tag_list) = filter.strip_prefix(":tag:") {
594 if tag_list.is_empty() {
595 test.props.tags.is_empty()
597 } else {
598 let or_tags = tag_list.split('|').collect::<Vec<&str>>();
599 let mut result = false;
600 for or_tag in or_tags {
601 let and_tags = or_tag.split('&').collect::<Vec<&str>>();
602 let mut and_result = true;
603 for and_tag in and_tags {
604 if !test.props.tags.contains(&and_tag.to_string()) {
605 and_result = false;
606 break;
607 }
608 }
609 if and_result {
610 result = true;
611 break;
612 }
613 }
614 result
615 }
616 } else if exact {
617 test.filterable_name() == filter
618 } else {
619 test.filterable_name().contains(filter)
620 }
621}
622
623pub(crate) fn apply_suite_tags(
624 tests: &[RegisteredTest],
625 props: &[RegisteredTestSuiteProperty],
626) -> Vec<RegisteredTest> {
627 let tag_props = props
628 .iter()
629 .filter_map(|prop| match prop {
630 RegisteredTestSuiteProperty::Tag { tag, .. } => {
631 let prefix = prop.crate_and_module();
632 Some((prefix, tag.clone()))
633 }
634 _ => None,
635 })
636 .collect::<Vec<_>>();
637
638 let mut result = Vec::new();
639 for test in tests {
640 let mut test = test.clone();
641 for (prefix, tag) in &tag_props {
642 if &test.crate_and_module() == prefix {
643 test.props.tags.push(tag.clone());
644 }
645 }
646 result.push(test);
647 }
648 result
649}
650
651pub(crate) fn apply_suite_timeouts(
652 tests: &[RegisteredTest],
653 props: &[RegisteredTestSuiteProperty],
654) -> Vec<RegisteredTest> {
655 let timeout_props = props
656 .iter()
657 .filter_map(|prop| match prop {
658 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
659 let prefix = prop.crate_and_module();
660 Some((prefix, *timeout))
661 }
662 _ => None,
663 })
664 .collect::<Vec<_>>();
665
666 let mut result = Vec::new();
667 for test in tests {
668 let mut test = test.clone();
669 for (prefix, timeout) in &timeout_props {
670 if &test.crate_and_module() == prefix && test.props.timeout.is_none() {
671 test.props.timeout = Some(*timeout);
672 }
673 }
674 result.push(test);
675 }
676 result
677}
678
679pub(crate) fn filter_registered_tests(
680 args: &Arguments,
681 registered_tests: &[RegisteredTest],
682) -> Vec<RegisteredTest> {
683 registered_tests
684 .iter()
685 .filter(|registered_test| {
686 !args
687 .skip
688 .iter()
689 .any(|skip| filter_test(registered_test, skip, args.exact))
690 })
691 .filter(|registered_test| {
692 args.filter.is_empty()
693 || args
694 .filter
695 .iter()
696 .any(|filter| filter_test(registered_test, filter, args.exact))
697 })
698 .filter(|registered_tests| {
699 (args.bench && registered_tests.run.is_bench())
700 || (args.test && !registered_tests.run.is_bench())
701 || (!args.bench && !args.test)
702 })
703 .filter(|registered_test| {
704 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
705 })
706 .cloned()
707 .collect::<Vec<_>>()
708}
709
710fn add_generated_tests(
711 target: &mut Vec<RegisteredTest>,
712 generator: &RegisteredTestGenerator,
713 generated: Vec<GeneratedTest>,
714) {
715 target.extend(generated.into_iter().map(|mut test| {
716 test.props.is_ignored |= generator.is_ignored;
717 RegisteredTest {
718 name: format!("{}::{}", generator.name, test.name),
719 crate_name: generator.crate_name.clone(),
720 module_path: generator.module_path.clone(),
721 run: test.run,
722 props: test.props,
723 dependencies: test.dependencies,
724 }
725 }));
726}
727
728#[cfg(feature = "tokio")]
729pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
730 let mut result = Vec::new();
731 for generator in generators {
732 match &generator.run {
733 TestGeneratorFunction::Sync(generator_fn) => {
734 let tests = generator_fn();
735 add_generated_tests(&mut result, generator, tests);
736 }
737 TestGeneratorFunction::Async(generator_fn) => {
738 let tests = generator_fn().await;
739 add_generated_tests(&mut result, generator, tests);
740 }
741 }
742 }
743 result
744}
745
746pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
747 let mut result = Vec::new();
748 for generator in generators {
749 match &generator.run {
750 TestGeneratorFunction::Sync(generator_fn) => {
751 let tests = generator_fn();
752 add_generated_tests(&mut result, generator, tests);
753 }
754 TestGeneratorFunction::Async(_) => {
755 panic!("Async test generators are not supported in sync mode")
756 }
757 }
758 }
759 result
760}
761
762pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
763 let should_ensure_time = match test.props.ensure_time_control {
764 ReportTimeControl::Default => args.ensure_time,
765 ReportTimeControl::Enabled => true,
766 ReportTimeControl::Disabled => false,
767 };
768 if should_ensure_time {
769 match test.props.test_type {
770 TestType::UnitTest => Some(args.unit_test_threshold()),
771 TestType::IntegrationTest => Some(args.integration_test_threshold()),
772 }
773 } else {
774 None
775 }
776}
777
778#[derive(Clone)]
779pub enum TestResult {
780 Passed {
781 captured: Vec<CapturedOutput>,
782 exec_time: Duration,
783 },
784 Benchmarked {
785 captured: Vec<CapturedOutput>,
786 exec_time: Duration,
787 ns_iter_summ: Summary,
788 mb_s: usize,
789 },
790 Failed {
791 cause: FailureCause,
792 captured: Vec<CapturedOutput>,
793 exec_time: Duration,
794 },
795 Ignored {
796 captured: Vec<CapturedOutput>,
797 },
798}
799
800impl TestResult {
801 pub fn passed(exec_time: Duration) -> Self {
802 TestResult::Passed {
803 captured: Vec::new(),
804 exec_time,
805 }
806 }
807
808 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
809 TestResult::Benchmarked {
810 captured: Vec::new(),
811 exec_time,
812 ns_iter_summ,
813 mb_s,
814 }
815 }
816
817 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
818 TestResult::Failed {
819 cause,
820 captured: Vec::new(),
821 exec_time,
822 }
823 }
824
825 pub fn ignored() -> Self {
826 TestResult::Ignored {
827 captured: Vec::new(),
828 }
829 }
830
831 pub(crate) fn is_passed(&self) -> bool {
832 matches!(self, TestResult::Passed { .. })
833 }
834
835 pub(crate) fn is_benchmarked(&self) -> bool {
836 matches!(self, TestResult::Benchmarked { .. })
837 }
838
839 pub(crate) fn is_failed(&self) -> bool {
840 matches!(self, TestResult::Failed { .. })
841 }
842
843 pub(crate) fn is_ignored(&self) -> bool {
844 matches!(self, TestResult::Ignored { .. })
845 }
846
847 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
848 match self {
849 TestResult::Passed { captured, .. } => captured,
850 TestResult::Failed { captured, .. } => captured,
851 TestResult::Ignored { captured, .. } => captured,
852 TestResult::Benchmarked { captured, .. } => captured,
853 }
854 }
855
856 pub(crate) fn stats(&self) -> Option<&Summary> {
857 match self {
858 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
859 _ => None,
860 }
861 }
862
863 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
864 match self {
865 TestResult::Passed {
866 captured: captured_ref,
867 ..
868 } => *captured_ref = captured,
869 TestResult::Failed {
870 captured: captured_ref,
871 ..
872 } => *captured_ref = captured,
873 TestResult::Ignored {
874 captured: captured_ref,
875 } => *captured_ref = captured,
876 TestResult::Benchmarked {
877 captured: captured_ref,
878 ..
879 } => *captured_ref = captured,
880 }
881 }
882
883 pub(crate) fn from_result<A>(
884 should_panic: &ShouldPanic,
885 elapsed: Duration,
886 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
887 ) -> Self {
888 match result {
889 Ok(Ok(_)) => {
890 if should_panic == &ShouldPanic::No {
891 TestResult::passed(elapsed)
892 } else {
893 TestResult::failed(
894 elapsed,
895 FailureCause::HarnessError("Test did not panic as expected".to_string()),
896 )
897 }
898 }
899 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
900 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
901 }
902 }
903
904 pub(crate) fn from_summary(
905 should_panic: &ShouldPanic,
906 elapsed: Duration,
907 result: Result<Summary, Box<dyn Any + Send>>,
908 bytes: u64,
909 ) -> Self {
910 match result {
911 Ok(summary) => {
912 let ns_iter = max(summary.median as u64, 1);
913 let mb_s = bytes * 1000 / ns_iter;
914 TestResult::benchmarked(elapsed, summary, mb_s as usize)
915 }
916 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
917 }
918 }
919
920 fn from_panic(
921 should_panic: &ShouldPanic,
922 elapsed: Duration,
923 panic: Box<dyn Any + Send>,
924 ) -> Self {
925 let captured = crate::panic_hook::take_current_panic_capture();
926
927 let panic_cause = if let Some(cause) = captured {
928 cause
929 } else {
930 let message = panic
931 .downcast_ref::<String>()
932 .cloned()
933 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
934 PanicCause {
935 message,
936 location: None,
937 backtrace: None,
938 }
939 };
940
941 match should_panic {
942 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
943 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
944 _ => TestResult::failed(
945 elapsed,
946 FailureCause::Panic(PanicCause {
947 message: Some(format!(
948 "Test panicked with unexpected message: {}",
949 panic_cause.message.as_deref().unwrap_or_default()
950 )),
951 location: None,
952 backtrace: None,
953 }),
954 ),
955 },
956 ShouldPanic::Yes => TestResult::passed(elapsed),
957 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
958 }
959 }
960
961 pub(crate) fn failure_message(&self) -> Option<String> {
962 self.failure_cause().map(|c| c.render())
963 }
964
965 pub fn failure_cause(&self) -> Option<&FailureCause> {
966 match self {
967 TestResult::Failed { cause, .. } => Some(cause),
968 _ => None,
969 }
970 }
971}
972
973pub struct SuiteResult {
974 pub passed: usize,
975 pub failed: usize,
976 pub ignored: usize,
977 pub measured: usize,
978 pub filtered_out: usize,
979 pub exec_time: Duration,
980}
981
982impl SuiteResult {
983 pub fn from_test_results(
984 registered_tests: &[RegisteredTest],
985 results: &[(RegisteredTest, TestResult)],
986 exec_time: Duration,
987 ) -> Self {
988 let passed = results
989 .iter()
990 .filter(|(_, result)| result.is_passed())
991 .count();
992 let measured = results
993 .iter()
994 .filter(|(_, result)| result.is_benchmarked())
995 .count();
996 let failed = results
997 .iter()
998 .filter(|(_, result)| result.is_failed())
999 .count();
1000 let ignored = results
1001 .iter()
1002 .filter(|(_, result)| result.is_ignored())
1003 .count();
1004 let filtered_out = registered_tests.len() - results.len();
1005
1006 Self {
1007 passed,
1008 failed,
1009 ignored,
1010 measured,
1011 filtered_out,
1012 exec_time,
1013 }
1014 }
1015
1016 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
1017 if results.iter().any(|(_, result)| result.is_failed()) {
1018 ExitCode::from(101)
1019 } else {
1020 ExitCode::SUCCESS
1021 }
1022 }
1023}
1024
1025pub trait DependencyView: Debug {
1026 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
1027}
1028
1029impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
1030 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
1031 self.as_ref().get(name)
1032 }
1033}
1034
1035#[derive(Debug, Clone, Eq, PartialEq)]
1036pub enum CapturedOutput {
1037 Stdout { timestamp: SystemTime, line: String },
1038 Stderr { timestamp: SystemTime, line: String },
1039}
1040
1041impl CapturedOutput {
1042 pub fn stdout(line: String) -> Self {
1043 CapturedOutput::Stdout {
1044 timestamp: SystemTime::now(),
1045 line,
1046 }
1047 }
1048
1049 pub fn stderr(line: String) -> Self {
1050 CapturedOutput::Stderr {
1051 timestamp: SystemTime::now(),
1052 line,
1053 }
1054 }
1055
1056 pub fn timestamp(&self) -> SystemTime {
1057 match self {
1058 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
1059 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
1060 }
1061 }
1062
1063 pub fn line(&self) -> &str {
1064 match self {
1065 CapturedOutput::Stdout { line, .. } => line,
1066 CapturedOutput::Stderr { line, .. } => line,
1067 }
1068 }
1069}
1070
1071impl PartialOrd for CapturedOutput {
1072 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1073 Some(self.cmp(other))
1074 }
1075}
1076
1077impl Ord for CapturedOutput {
1078 fn cmp(&self, other: &Self) -> Ordering {
1079 self.timestamp().cmp(&other.timestamp())
1080 }
1081}
1082
1083#[cfg(test)]
1084mod error_reporting_tests {
1085 use super::*;
1086 use std::panic::{catch_unwind, AssertUnwindSafe};
1087 use std::time::Duration;
1088
1089 fn simulate_runner(
1090 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
1091 ) -> TestResult {
1092 crate::panic_hook::install_panic_hook();
1093 let test_id = crate::panic_hook::next_test_id();
1094 crate::panic_hook::set_current_test_id(test_id);
1095 let result = catch_unwind(AssertUnwindSafe(move || {
1096 let ret = test_fn();
1097 ret.into_result()?;
1098 Ok(())
1099 }));
1100 let test_result =
1101 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
1102 crate::panic_hook::clear_current_test_id();
1103 test_result
1104 }
1105
1106 #[test]
1107 fn panic_with_assert_eq() {
1108 let result = simulate_runner(|| {
1109 assert_eq!(1, 2);
1110 Box::new(())
1111 });
1112 assert!(result.is_failed());
1113 let msg = result.failure_message().unwrap();
1114 println!("=== panic assert_eq failure message ===\n{msg}\n===");
1115 assert!(
1116 msg.contains("assertion `left == right` failed"),
1117 "Expected assertion message, got: {msg}"
1118 );
1119 assert!(
1120 msg.contains("at "),
1121 "Expected location info in message, got: {msg}"
1122 );
1123 }
1124
1125 #[test]
1126 fn string_error() {
1127 let result = simulate_runner(|| {
1128 let r: Result<(), String> = Err("something went wrong".to_string());
1129 Box::new(r)
1130 });
1131 assert!(result.is_failed());
1132 let msg = result.failure_message().unwrap();
1133 println!("=== string error failure message ===\n{msg}\n===");
1134 assert_eq!(msg, "something went wrong");
1135 }
1136
1137 #[test]
1138 fn anyhow_error() {
1139 let result = simulate_runner(|| {
1140 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
1141 let err = anyhow::anyhow!(inner).context("operation failed");
1142 let r: Result<(), anyhow::Error> = Err(err);
1143 Box::new(r)
1144 });
1145 assert!(result.is_failed());
1146 let msg = result.failure_message().unwrap();
1147 println!("=== anyhow error failure message ===\n{msg}\n===");
1148 assert!(
1149 msg.contains("operation failed"),
1150 "Expected 'operation failed', got: {msg}"
1151 );
1152 assert!(
1153 msg.contains("file not found"),
1154 "Expected 'file not found', got: {msg}"
1155 );
1156 }
1157
1158 #[test]
1159 fn std_io_error() {
1160 let result = simulate_runner(|| {
1161 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
1162 std::io::ErrorKind::NotFound,
1163 "file not found",
1164 ));
1165 Box::new(r)
1166 });
1167 assert!(result.is_failed());
1168 let msg = result.failure_message().unwrap();
1169 println!("=== std io error failure message ===\n{msg}\n===");
1170 assert_eq!(msg, "file not found");
1172 }
1173
1174 #[test]
1175 fn panic_with_location_info() {
1176 let result = simulate_runner(|| {
1177 panic!("test panic with location");
1178 #[allow(unreachable_code)]
1179 Box::new(())
1180 });
1181 assert!(result.is_failed());
1182 let cause = result.failure_cause().unwrap();
1183 match cause {
1184 FailureCause::Panic(p) => {
1185 assert!(p.location.is_some(), "Expected location info");
1186 let loc = p.location.as_ref().unwrap();
1187 assert!(
1188 loc.file.contains("internal.rs"),
1189 "Expected file to contain internal.rs, got: {}",
1190 loc.file
1191 );
1192 assert!(loc.line > 0, "Expected non-zero line number");
1193 }
1194 other => panic!("Expected Panic cause, got: {other:?}"),
1195 }
1196 }
1197
1198 #[test]
1199 fn panic_render_includes_location() {
1200 let result = simulate_runner(|| {
1201 panic!("location test");
1202 #[allow(unreachable_code)]
1203 Box::new(())
1204 });
1205 let msg = result.failure_message().unwrap();
1206 assert!(
1207 msg.contains("location test"),
1208 "Expected panic message, got: {msg}"
1209 );
1210 assert!(
1211 msg.contains("\n at "),
1212 "Expected location line in render, got: {msg}"
1213 );
1214 }
1215
1216 #[test]
1217 fn should_panic_with_message_matching() {
1218 crate::panic_hook::install_panic_hook();
1219 let test_id = crate::panic_hook::next_test_id();
1220 crate::panic_hook::set_current_test_id(test_id);
1221 let result = catch_unwind(AssertUnwindSafe(|| {
1222 panic!("expected panic message");
1223 }));
1224 let test_result = TestResult::from_result(
1225 &ShouldPanic::WithMessage("expected panic".to_string()),
1226 Duration::from_millis(1),
1227 result.map(|_| Ok(())),
1228 );
1229 crate::panic_hook::clear_current_test_id();
1230 assert!(
1231 test_result.is_passed(),
1232 "Expected test to pass with matching panic message"
1233 );
1234 }
1235
1236 #[test]
1237 fn should_panic_with_wrong_message() {
1238 crate::panic_hook::install_panic_hook();
1239 let test_id = crate::panic_hook::next_test_id();
1240 crate::panic_hook::set_current_test_id(test_id);
1241 let result = catch_unwind(AssertUnwindSafe(|| {
1242 panic!("actual panic message");
1243 }));
1244 let test_result = TestResult::from_result(
1245 &ShouldPanic::WithMessage("completely different".to_string()),
1246 Duration::from_millis(1),
1247 result.map(|_| Ok(())),
1248 );
1249 crate::panic_hook::clear_current_test_id();
1250 assert!(
1251 test_result.is_failed(),
1252 "Expected test to fail with wrong panic message"
1253 );
1254 let msg = test_result.failure_message().unwrap();
1255 assert!(
1256 msg.contains("unexpected message"),
1257 "Expected 'unexpected message' in: {msg}"
1258 );
1259 }
1260
1261 #[test]
1262 fn pretty_assertions_diff() {
1263 let result = simulate_runner(|| {
1264 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
1265 Box::new(())
1266 });
1267 assert!(result.is_failed());
1268 let cause = result.failure_cause().unwrap();
1269
1270 let panic_cause = match cause {
1272 FailureCause::Panic(p) => p,
1273 other => panic!("Expected Panic cause, got: {other:?}"),
1274 };
1275
1276 let message = panic_cause.message.as_deref().unwrap();
1278 println!("=== pretty_assertions failure message ===\n{message}\n===");
1279 assert!(
1280 message.contains("foo") && message.contains("baz"),
1281 "Expected diff with 'foo' and 'baz', got: {message}"
1282 );
1283
1284 assert!(panic_cause.location.is_some(), "Expected location info");
1286
1287 let rendered = cause.render();
1289 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
1290 assert!(
1291 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
1292 "Expected no backtrace noise in rendered output, got: {rendered}"
1293 );
1294 assert!(
1296 rendered.contains("\n at "),
1297 "Expected location in rendered output, got: {rendered}"
1298 );
1299 }
1300
1301 #[test]
1302 fn detached_thread_panic_detected() {
1303 crate::panic_hook::install_panic_hook();
1304 let test_id = crate::panic_hook::next_test_id();
1305 crate::panic_hook::set_current_test_id(test_id);
1306 crate::panic_hook::create_detached_collector(test_id);
1307
1308 let result = catch_unwind(AssertUnwindSafe(|| {
1309 let handle = crate::spawn::spawn_thread(|| {
1310 panic!("background thread panic");
1311 });
1312 let _ = handle.join();
1313 }));
1314
1315 let mut test_result = TestResult::from_result(
1316 &ShouldPanic::No,
1317 Duration::from_millis(1),
1318 result.map(|_| Ok(())),
1319 );
1320
1321 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
1322 let panics = match collector.lock() {
1323 Ok(p) => p,
1324 Err(poisoned) => poisoned.into_inner(),
1325 };
1326 if !panics.is_empty() && test_result.is_passed() {
1327 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
1328 test_result = TestResult::failed(
1329 Duration::from_millis(1),
1330 FailureCause::Panic(PanicCause {
1331 message: Some(format!(
1332 "Detached task(s) panicked:\n{}",
1333 messages.join("\n---\n")
1334 )),
1335 location: panics.first().and_then(|p| p.location.clone()),
1336 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
1337 }),
1338 );
1339 }
1340 }
1341
1342 crate::panic_hook::clear_current_test_id();
1343
1344 assert!(
1345 test_result.is_failed(),
1346 "Expected test to fail due to detached panic"
1347 );
1348 let msg = test_result.failure_message().unwrap();
1349 assert!(
1350 msg.contains("Detached task(s) panicked"),
1351 "Expected detached panic message, got: {msg}"
1352 );
1353 assert!(
1354 msg.contains("background thread panic"),
1355 "Expected original panic message, got: {msg}"
1356 );
1357 }
1358
1359 #[test]
1360 fn detached_thread_panic_ignored_with_policy() {
1361 crate::panic_hook::install_panic_hook();
1362 let test_id = crate::panic_hook::next_test_id();
1363 crate::panic_hook::set_current_test_id(test_id);
1364 crate::panic_hook::create_detached_collector(test_id);
1365
1366 let result = catch_unwind(AssertUnwindSafe(|| {
1367 let handle = crate::spawn::spawn_thread(|| {
1368 panic!("ignored thread panic");
1369 });
1370 let _ = handle.join();
1371 }));
1372
1373 let test_result = TestResult::from_result(
1374 &ShouldPanic::No,
1375 Duration::from_millis(1),
1376 result.map(|_| Ok(())),
1377 );
1378
1379 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
1380 let panics = match collector.lock() {
1381 Ok(p) => p,
1382 Err(poisoned) => poisoned.into_inner(),
1383 };
1384 assert!(
1386 !panics.is_empty(),
1387 "Expected panics in collector even with Ignore policy"
1388 );
1389 }
1390
1391 crate::panic_hook::clear_current_test_id();
1392
1393 assert!(
1394 test_result.is_passed(),
1395 "Expected test to pass with Ignore policy"
1396 );
1397 }
1398
1399 #[cfg(feature = "tokio")]
1400 #[test]
1401 fn detached_task_panic_detected() {
1402 let rt = tokio::runtime::Runtime::new().unwrap();
1403 rt.block_on(async {
1404 crate::panic_hook::install_panic_hook();
1405 let test_id = crate::panic_hook::next_test_id();
1406 crate::panic_hook::set_current_test_id(test_id);
1407 crate::panic_hook::create_detached_collector(test_id);
1408
1409 let handle = crate::spawn::spawn(async {
1410 panic!("detached task panic");
1411 });
1412 let _ = handle.await;
1413
1414 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
1415 let panics = collector.lock().unwrap();
1416
1417 assert_eq!(panics.len(), 1);
1418 assert!(
1419 panics[0]
1420 .message
1421 .as_ref()
1422 .unwrap()
1423 .contains("detached task panic"),
1424 "Expected panic message, got: {:?}",
1425 panics[0].message
1426 );
1427
1428 crate::panic_hook::clear_current_test_id();
1429 });
1430 }
1431
1432 #[test]
1433 fn failure_cause_variants() {
1434 let cause = FailureCause::ReturnedMessage("simple message".to_string());
1436 assert_eq!(cause.render(), "simple message");
1437 assert!(cause.panic_message().is_none());
1438
1439 let cause = FailureCause::ReturnedError {
1441 display: "display text".to_string(),
1442 debug: "debug text".to_string(),
1443 prefer_debug: false,
1444 error: Arc::new("display text".to_string()),
1445 };
1446 assert_eq!(cause.render(), "display text");
1447
1448 let cause = FailureCause::ReturnedError {
1450 display: "display text".to_string(),
1451 debug: "debug text".to_string(),
1452 prefer_debug: true,
1453 error: Arc::new("debug text".to_string()),
1454 };
1455 assert_eq!(cause.render(), "debug text");
1456
1457 let cause = FailureCause::HarnessError("harness error".to_string());
1459 assert_eq!(cause.render(), "harness error");
1460
1461 let cause = FailureCause::Panic(PanicCause {
1463 message: Some("panic msg".to_string()),
1464 location: None,
1465 backtrace: None,
1466 });
1467 assert_eq!(cause.render(), "panic msg");
1468 assert_eq!(cause.panic_message(), Some("panic msg"));
1469 }
1470}
1471
1472#[cfg(test)]
1473mod filter_tests {
1474 use super::*;
1475
1476 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
1477 RegisteredTest {
1478 name: name.to_string(),
1479 crate_name: "mycrate".to_string(),
1480 module_path: module_path.to_string(),
1481 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
1482 props: TestProperties::default(),
1483 dependencies: None,
1484 }
1485 }
1486
1487 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
1488 let mut test = make_test(name, module_path);
1489 test.props.tags = tags.into_iter().map(String::from).collect();
1490 test
1491 }
1492
1493 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
1494 Arguments {
1495 filter: filters.into_iter().map(String::from).collect(),
1496 skip: skip.into_iter().map(String::from).collect(),
1497 exact,
1498 ..Default::default()
1499 }
1500 }
1501
1502 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
1503 filter_registered_tests(args, tests)
1504 .into_iter()
1505 .map(|t| t.filterable_name())
1506 .collect()
1507 }
1508
1509 #[test]
1512 fn filter_test_substring_match() {
1513 let test = make_test("hello_world", "mod1");
1514 assert!(filter_test(&test, "hello", false));
1515 assert!(filter_test(&test, "world", false));
1516 assert!(filter_test(&test, "mod1::hello", false));
1517 assert!(!filter_test(&test, "nonexistent", false));
1518 }
1519
1520 #[test]
1521 fn filter_test_exact_match() {
1522 let test = make_test("hello_world", "mod1");
1523 assert!(filter_test(&test, "mod1::hello_world", true));
1524 assert!(!filter_test(&test, "hello_world", true));
1525 assert!(!filter_test(&test, "hello", true));
1526 }
1527
1528 #[test]
1529 fn filter_test_tag_match() {
1530 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
1531 assert!(filter_test(&test, ":tag:fast", false));
1532 assert!(filter_test(&test, ":tag:unit", false));
1533 assert!(!filter_test(&test, ":tag:slow", false));
1534 }
1535
1536 #[test]
1537 fn filter_test_tag_empty_matches_untagged() {
1538 let untagged = make_test("t1", "mod1");
1539 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
1540 assert!(filter_test(&untagged, ":tag:", false));
1541 assert!(!filter_test(&tagged, ":tag:", false));
1542 }
1543
1544 #[test]
1547 fn no_filters_includes_all() {
1548 let tests = vec![make_test("a", "m"), make_test("b", "m")];
1549 let args = make_args(vec![], vec![], false);
1550 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
1551 }
1552
1553 #[test]
1554 fn single_filter_substring() {
1555 let tests = vec![
1556 make_test("alpha", "m"),
1557 make_test("beta", "m"),
1558 make_test("alphabet", "m"),
1559 ];
1560 let args = make_args(vec!["alpha"], vec![], false);
1561 assert_eq!(
1562 filtered_names(&args, &tests),
1563 vec!["m::alpha", "m::alphabet"]
1564 );
1565 }
1566
1567 #[test]
1568 fn multiple_filters_or_semantics() {
1569 let tests = vec![
1570 make_test("alpha", "m"),
1571 make_test("beta", "m"),
1572 make_test("gamma", "m"),
1573 ];
1574 let args = make_args(vec!["alpha", "gamma"], vec![], false);
1575 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
1576 }
1577
1578 #[test]
1579 fn multiple_filters_exact() {
1580 let tests = vec![
1581 make_test("alpha", "m"),
1582 make_test("alphabet", "m"),
1583 make_test("beta", "m"),
1584 ];
1585 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
1586 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
1587 }
1588
1589 #[test]
1592 fn skip_substring_match() {
1593 let tests = vec![
1594 make_test("fast_test", "m"),
1595 make_test("slow_test", "m"),
1596 make_test("slower_test", "m"),
1597 ];
1598 let args = make_args(vec![], vec!["slow"], false);
1599 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
1600 }
1601
1602 #[test]
1603 fn skip_exact_match() {
1604 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
1605 let args = make_args(vec![], vec!["m::slow_test"], true);
1606 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
1607 }
1608
1609 #[test]
1610 fn skip_with_tag() {
1611 let tests = vec![
1612 make_tagged_test("t1", "m", vec!["slow"]),
1613 make_tagged_test("t2", "m", vec!["fast"]),
1614 make_test("t3", "m"),
1615 ];
1616 let args = make_args(vec![], vec![":tag:slow"], false);
1617 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
1618 }
1619
1620 #[test]
1623 fn include_and_skip_combined() {
1624 let tests = vec![
1625 make_test("alpha_fast", "m"),
1626 make_test("alpha_slow", "m"),
1627 make_test("beta_fast", "m"),
1628 ];
1629 let args = make_args(vec!["alpha"], vec!["slow"], false);
1631 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
1632 }
1633
1634 #[test]
1635 fn skip_wins_over_include() {
1636 let tests = vec![make_test("target", "m")];
1637 let args = make_args(vec!["target"], vec!["target"], false);
1639 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
1640 }
1641
1642 #[test]
1645 fn filter_test_tag_or_expression() {
1646 let test_a = make_tagged_test("t1", "m", vec!["a"]);
1648 let test_b = make_tagged_test("t2", "m", vec!["b"]);
1649 let test_c = make_tagged_test("t3", "m", vec!["c"]);
1650 assert!(filter_test(&test_a, ":tag:a|b", false));
1651 assert!(filter_test(&test_b, ":tag:a|b", false));
1652 assert!(!filter_test(&test_c, ":tag:a|b", false));
1653 }
1654
1655 #[test]
1656 fn filter_test_tag_and_expression() {
1657 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
1659 let test_a = make_tagged_test("t2", "m", vec!["a"]);
1660 let test_b = make_tagged_test("t3", "m", vec!["b"]);
1661 assert!(filter_test(&test_ab, ":tag:a&b", false));
1662 assert!(!filter_test(&test_a, ":tag:a&b", false));
1663 assert!(!filter_test(&test_b, ":tag:a&b", false));
1664 }
1665
1666 #[test]
1667 fn filter_test_tag_mixed_and_or() {
1668 let test_a = make_tagged_test("t1", "m", vec!["a"]);
1670 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
1671 let test_b = make_tagged_test("t3", "m", vec!["b"]);
1672 let test_c = make_tagged_test("t4", "m", vec!["c"]);
1673 let test_none = make_test("t5", "m");
1674 assert!(filter_test(&test_a, ":tag:a|b&c", false));
1675 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
1676 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
1677 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
1678 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
1679 }
1680
1681 #[test]
1682 fn filter_test_tag_exact_flag_does_not_affect_tags() {
1683 let test = make_tagged_test("t1", "m", vec!["fast"]);
1685 assert!(filter_test(&test, ":tag:fast", true));
1686 assert!(!filter_test(&test, ":tag:slow", true));
1687 }
1688
1689 #[test]
1690 fn include_by_tag_or_expression() {
1691 let tests = vec![
1692 make_tagged_test("t1", "m", vec!["unit"]),
1693 make_tagged_test("t2", "m", vec!["integration"]),
1694 make_tagged_test("t3", "m", vec!["e2e"]),
1695 ];
1696 let args = make_args(vec![":tag:unit|integration"], vec![], false);
1697 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
1698 }
1699
1700 #[test]
1701 fn skip_by_tag_and_expression() {
1702 let tests = vec![
1703 make_tagged_test("t1", "m", vec!["slow", "network"]),
1704 make_tagged_test("t2", "m", vec!["slow"]),
1705 make_tagged_test("t3", "m", vec!["network"]),
1706 make_test("t4", "m"),
1707 ];
1708 let args = make_args(vec![], vec![":tag:slow&network"], false);
1710 assert_eq!(
1711 filtered_names(&args, &tests),
1712 vec!["m::t2", "m::t3", "m::t4"]
1713 );
1714 }
1715}