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_props_to_tests(
624 tests: &[RegisteredTest],
625 props: &[RegisteredTestSuiteProperty],
626) -> Vec<RegisteredTest> {
627 let props_with_prefix = props
628 .iter()
629 .map(|prop| (prop.crate_and_module(), prop))
630 .collect::<Vec<_>>();
631
632 let mut result = Vec::new();
633 for test in tests {
634 let mut test = test.clone();
635 for (prefix, prop) in &props_with_prefix {
636 if test.crate_and_module().starts_with(prefix) {
637 match prop {
638 RegisteredTestSuiteProperty::Tag { tag, .. } => {
639 test.props.tags.push(tag.clone());
640 }
641 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
642 if test.props.timeout.is_none() {
643 test.props.timeout = Some(*timeout);
644 }
645 }
646 RegisteredTestSuiteProperty::Sequential { .. } => {
647 }
649 }
650 }
651 }
652 result.push(test);
653 }
654 result
655}
656
657pub(crate) fn filter_registered_tests(
658 args: &Arguments,
659 registered_tests: &[RegisteredTest],
660) -> Vec<RegisteredTest> {
661 registered_tests
662 .iter()
663 .filter(|registered_test| {
664 !args
665 .skip
666 .iter()
667 .any(|skip| filter_test(registered_test, skip, args.exact))
668 })
669 .filter(|registered_test| {
670 args.filter.is_empty()
671 || args
672 .filter
673 .iter()
674 .any(|filter| filter_test(registered_test, filter, args.exact))
675 })
676 .filter(|registered_tests| {
677 (args.bench && registered_tests.run.is_bench())
678 || (args.test && !registered_tests.run.is_bench())
679 || (!args.bench && !args.test)
680 })
681 .filter(|registered_test| {
682 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
683 })
684 .cloned()
685 .collect::<Vec<_>>()
686}
687
688fn add_generated_tests(
689 target: &mut Vec<RegisteredTest>,
690 generator: &RegisteredTestGenerator,
691 generated: Vec<GeneratedTest>,
692) {
693 target.extend(generated.into_iter().map(|mut test| {
694 test.props.is_ignored |= generator.is_ignored;
695 RegisteredTest {
696 name: format!("{}::{}", generator.name, test.name),
697 crate_name: generator.crate_name.clone(),
698 module_path: generator.module_path.clone(),
699 run: test.run,
700 props: test.props,
701 dependencies: test.dependencies,
702 }
703 }));
704}
705
706#[cfg(feature = "tokio")]
707pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
708 let mut result = Vec::new();
709 for generator in generators {
710 match &generator.run {
711 TestGeneratorFunction::Sync(generator_fn) => {
712 let tests = generator_fn();
713 add_generated_tests(&mut result, generator, tests);
714 }
715 TestGeneratorFunction::Async(generator_fn) => {
716 let tests = generator_fn().await;
717 add_generated_tests(&mut result, generator, tests);
718 }
719 }
720 }
721 result
722}
723
724pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
725 let mut result = Vec::new();
726 for generator in generators {
727 match &generator.run {
728 TestGeneratorFunction::Sync(generator_fn) => {
729 let tests = generator_fn();
730 add_generated_tests(&mut result, generator, tests);
731 }
732 TestGeneratorFunction::Async(_) => {
733 panic!("Async test generators are not supported in sync mode")
734 }
735 }
736 }
737 result
738}
739
740pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
741 let should_ensure_time = match test.props.ensure_time_control {
742 ReportTimeControl::Default => args.ensure_time,
743 ReportTimeControl::Enabled => true,
744 ReportTimeControl::Disabled => false,
745 };
746 if should_ensure_time {
747 match test.props.test_type {
748 TestType::UnitTest => Some(args.unit_test_threshold()),
749 TestType::IntegrationTest => Some(args.integration_test_threshold()),
750 }
751 } else {
752 None
753 }
754}
755
756#[derive(Clone)]
757pub enum TestResult {
758 Passed {
759 captured: Vec<CapturedOutput>,
760 exec_time: Duration,
761 },
762 Benchmarked {
763 captured: Vec<CapturedOutput>,
764 exec_time: Duration,
765 ns_iter_summ: Summary,
766 mb_s: usize,
767 },
768 Failed {
769 cause: FailureCause,
770 captured: Vec<CapturedOutput>,
771 exec_time: Duration,
772 },
773 Ignored {
774 captured: Vec<CapturedOutput>,
775 },
776}
777
778impl TestResult {
779 pub fn passed(exec_time: Duration) -> Self {
780 TestResult::Passed {
781 captured: Vec::new(),
782 exec_time,
783 }
784 }
785
786 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
787 TestResult::Benchmarked {
788 captured: Vec::new(),
789 exec_time,
790 ns_iter_summ,
791 mb_s,
792 }
793 }
794
795 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
796 TestResult::Failed {
797 cause,
798 captured: Vec::new(),
799 exec_time,
800 }
801 }
802
803 pub fn ignored() -> Self {
804 TestResult::Ignored {
805 captured: Vec::new(),
806 }
807 }
808
809 pub(crate) fn is_passed(&self) -> bool {
810 matches!(self, TestResult::Passed { .. })
811 }
812
813 pub(crate) fn is_benchmarked(&self) -> bool {
814 matches!(self, TestResult::Benchmarked { .. })
815 }
816
817 pub(crate) fn is_failed(&self) -> bool {
818 matches!(self, TestResult::Failed { .. })
819 }
820
821 pub(crate) fn is_ignored(&self) -> bool {
822 matches!(self, TestResult::Ignored { .. })
823 }
824
825 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
826 match self {
827 TestResult::Passed { captured, .. } => captured,
828 TestResult::Failed { captured, .. } => captured,
829 TestResult::Ignored { captured, .. } => captured,
830 TestResult::Benchmarked { captured, .. } => captured,
831 }
832 }
833
834 pub(crate) fn stats(&self) -> Option<&Summary> {
835 match self {
836 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
837 _ => None,
838 }
839 }
840
841 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
842 match self {
843 TestResult::Passed {
844 captured: captured_ref,
845 ..
846 } => *captured_ref = captured,
847 TestResult::Failed {
848 captured: captured_ref,
849 ..
850 } => *captured_ref = captured,
851 TestResult::Ignored {
852 captured: captured_ref,
853 } => *captured_ref = captured,
854 TestResult::Benchmarked {
855 captured: captured_ref,
856 ..
857 } => *captured_ref = captured,
858 }
859 }
860
861 pub(crate) fn from_result<A>(
862 should_panic: &ShouldPanic,
863 elapsed: Duration,
864 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
865 ) -> Self {
866 match result {
867 Ok(Ok(_)) => {
868 if should_panic == &ShouldPanic::No {
869 TestResult::passed(elapsed)
870 } else {
871 TestResult::failed(
872 elapsed,
873 FailureCause::HarnessError("Test did not panic as expected".to_string()),
874 )
875 }
876 }
877 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
878 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
879 }
880 }
881
882 pub(crate) fn from_summary(
883 should_panic: &ShouldPanic,
884 elapsed: Duration,
885 result: Result<Summary, Box<dyn Any + Send>>,
886 bytes: u64,
887 ) -> Self {
888 match result {
889 Ok(summary) => {
890 let ns_iter = max(summary.median as u64, 1);
891 let mb_s = bytes * 1000 / ns_iter;
892 TestResult::benchmarked(elapsed, summary, mb_s as usize)
893 }
894 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
895 }
896 }
897
898 fn from_panic(
899 should_panic: &ShouldPanic,
900 elapsed: Duration,
901 panic: Box<dyn Any + Send>,
902 ) -> Self {
903 let captured = crate::panic_hook::take_current_panic_capture();
904
905 let panic_cause = if let Some(cause) = captured {
906 cause
907 } else {
908 let message = panic
909 .downcast_ref::<String>()
910 .cloned()
911 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
912 PanicCause {
913 message,
914 location: None,
915 backtrace: None,
916 }
917 };
918
919 match should_panic {
920 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
921 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
922 _ => TestResult::failed(
923 elapsed,
924 FailureCause::Panic(PanicCause {
925 message: Some(format!(
926 "Test panicked with unexpected message: {}",
927 panic_cause.message.as_deref().unwrap_or_default()
928 )),
929 location: None,
930 backtrace: None,
931 }),
932 ),
933 },
934 ShouldPanic::Yes => TestResult::passed(elapsed),
935 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
936 }
937 }
938
939 pub(crate) fn failure_message(&self) -> Option<String> {
940 self.failure_cause().map(|c| c.render())
941 }
942
943 pub fn failure_cause(&self) -> Option<&FailureCause> {
944 match self {
945 TestResult::Failed { cause, .. } => Some(cause),
946 _ => None,
947 }
948 }
949}
950
951pub struct SuiteResult {
952 pub passed: usize,
953 pub failed: usize,
954 pub ignored: usize,
955 pub measured: usize,
956 pub filtered_out: usize,
957 pub exec_time: Duration,
958}
959
960impl SuiteResult {
961 pub fn from_test_results(
962 registered_tests: &[RegisteredTest],
963 results: &[(RegisteredTest, TestResult)],
964 exec_time: Duration,
965 ) -> Self {
966 let passed = results
967 .iter()
968 .filter(|(_, result)| result.is_passed())
969 .count();
970 let measured = results
971 .iter()
972 .filter(|(_, result)| result.is_benchmarked())
973 .count();
974 let failed = results
975 .iter()
976 .filter(|(_, result)| result.is_failed())
977 .count();
978 let ignored = results
979 .iter()
980 .filter(|(_, result)| result.is_ignored())
981 .count();
982 let filtered_out = registered_tests.len() - results.len();
983
984 Self {
985 passed,
986 failed,
987 ignored,
988 measured,
989 filtered_out,
990 exec_time,
991 }
992 }
993
994 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
995 if results.iter().any(|(_, result)| result.is_failed()) {
996 ExitCode::from(101)
997 } else {
998 ExitCode::SUCCESS
999 }
1000 }
1001}
1002
1003pub trait DependencyView: Debug {
1004 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
1005}
1006
1007impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
1008 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
1009 self.as_ref().get(name)
1010 }
1011}
1012
1013#[derive(Debug, Clone, Eq, PartialEq)]
1014pub enum CapturedOutput {
1015 Stdout { timestamp: SystemTime, line: String },
1016 Stderr { timestamp: SystemTime, line: String },
1017}
1018
1019impl CapturedOutput {
1020 pub fn stdout(line: String) -> Self {
1021 CapturedOutput::Stdout {
1022 timestamp: SystemTime::now(),
1023 line,
1024 }
1025 }
1026
1027 pub fn stderr(line: String) -> Self {
1028 CapturedOutput::Stderr {
1029 timestamp: SystemTime::now(),
1030 line,
1031 }
1032 }
1033
1034 pub fn timestamp(&self) -> SystemTime {
1035 match self {
1036 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
1037 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
1038 }
1039 }
1040
1041 pub fn line(&self) -> &str {
1042 match self {
1043 CapturedOutput::Stdout { line, .. } => line,
1044 CapturedOutput::Stderr { line, .. } => line,
1045 }
1046 }
1047}
1048
1049impl PartialOrd for CapturedOutput {
1050 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1051 Some(self.cmp(other))
1052 }
1053}
1054
1055impl Ord for CapturedOutput {
1056 fn cmp(&self, other: &Self) -> Ordering {
1057 self.timestamp().cmp(&other.timestamp())
1058 }
1059}
1060
1061#[cfg(test)]
1062mod error_reporting_tests {
1063 use super::*;
1064 use std::panic::{catch_unwind, AssertUnwindSafe};
1065 use std::time::Duration;
1066
1067 fn simulate_runner(
1068 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
1069 ) -> TestResult {
1070 crate::panic_hook::install_panic_hook();
1071 let test_id = crate::panic_hook::next_test_id();
1072 crate::panic_hook::set_current_test_id(test_id);
1073 let result = catch_unwind(AssertUnwindSafe(move || {
1074 let ret = test_fn();
1075 ret.into_result()?;
1076 Ok(())
1077 }));
1078 let test_result =
1079 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
1080 crate::panic_hook::clear_current_test_id();
1081 test_result
1082 }
1083
1084 #[test]
1085 fn panic_with_assert_eq() {
1086 let result = simulate_runner(|| {
1087 assert_eq!(1, 2);
1088 Box::new(())
1089 });
1090 assert!(result.is_failed());
1091 let msg = result.failure_message().unwrap();
1092 println!("=== panic assert_eq failure message ===\n{msg}\n===");
1093 assert!(
1094 msg.contains("assertion `left == right` failed"),
1095 "Expected assertion message, got: {msg}"
1096 );
1097 assert!(
1098 msg.contains("at "),
1099 "Expected location info in message, got: {msg}"
1100 );
1101 }
1102
1103 #[test]
1104 fn string_error() {
1105 let result = simulate_runner(|| {
1106 let r: Result<(), String> = Err("something went wrong".to_string());
1107 Box::new(r)
1108 });
1109 assert!(result.is_failed());
1110 let msg = result.failure_message().unwrap();
1111 println!("=== string error failure message ===\n{msg}\n===");
1112 assert_eq!(msg, "something went wrong");
1113 }
1114
1115 #[test]
1116 fn anyhow_error() {
1117 let result = simulate_runner(|| {
1118 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
1119 let err = anyhow::anyhow!(inner).context("operation failed");
1120 let r: Result<(), anyhow::Error> = Err(err);
1121 Box::new(r)
1122 });
1123 assert!(result.is_failed());
1124 let msg = result.failure_message().unwrap();
1125 println!("=== anyhow error failure message ===\n{msg}\n===");
1126 assert!(
1127 msg.contains("operation failed"),
1128 "Expected 'operation failed', got: {msg}"
1129 );
1130 assert!(
1131 msg.contains("file not found"),
1132 "Expected 'file not found', got: {msg}"
1133 );
1134 }
1135
1136 #[test]
1137 fn std_io_error() {
1138 let result = simulate_runner(|| {
1139 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
1140 std::io::ErrorKind::NotFound,
1141 "file not found",
1142 ));
1143 Box::new(r)
1144 });
1145 assert!(result.is_failed());
1146 let msg = result.failure_message().unwrap();
1147 println!("=== std io error failure message ===\n{msg}\n===");
1148 assert_eq!(msg, "file not found");
1150 }
1151
1152 #[test]
1153 fn panic_with_location_info() {
1154 let result = simulate_runner(|| {
1155 panic!("test panic with location");
1156 #[allow(unreachable_code)]
1157 Box::new(())
1158 });
1159 assert!(result.is_failed());
1160 let cause = result.failure_cause().unwrap();
1161 match cause {
1162 FailureCause::Panic(p) => {
1163 assert!(p.location.is_some(), "Expected location info");
1164 let loc = p.location.as_ref().unwrap();
1165 assert!(
1166 loc.file.contains("internal.rs"),
1167 "Expected file to contain internal.rs, got: {}",
1168 loc.file
1169 );
1170 assert!(loc.line > 0, "Expected non-zero line number");
1171 }
1172 other => panic!("Expected Panic cause, got: {other:?}"),
1173 }
1174 }
1175
1176 #[test]
1177 fn panic_render_includes_location() {
1178 let result = simulate_runner(|| {
1179 panic!("location test");
1180 #[allow(unreachable_code)]
1181 Box::new(())
1182 });
1183 let msg = result.failure_message().unwrap();
1184 assert!(
1185 msg.contains("location test"),
1186 "Expected panic message, got: {msg}"
1187 );
1188 assert!(
1189 msg.contains("\n at "),
1190 "Expected location line in render, got: {msg}"
1191 );
1192 }
1193
1194 #[test]
1195 fn should_panic_with_message_matching() {
1196 crate::panic_hook::install_panic_hook();
1197 let test_id = crate::panic_hook::next_test_id();
1198 crate::panic_hook::set_current_test_id(test_id);
1199 let result = catch_unwind(AssertUnwindSafe(|| {
1200 panic!("expected panic message");
1201 }));
1202 let test_result = TestResult::from_result(
1203 &ShouldPanic::WithMessage("expected panic".to_string()),
1204 Duration::from_millis(1),
1205 result.map(|_| Ok(())),
1206 );
1207 crate::panic_hook::clear_current_test_id();
1208 assert!(
1209 test_result.is_passed(),
1210 "Expected test to pass with matching panic message"
1211 );
1212 }
1213
1214 #[test]
1215 fn should_panic_with_wrong_message() {
1216 crate::panic_hook::install_panic_hook();
1217 let test_id = crate::panic_hook::next_test_id();
1218 crate::panic_hook::set_current_test_id(test_id);
1219 let result = catch_unwind(AssertUnwindSafe(|| {
1220 panic!("actual panic message");
1221 }));
1222 let test_result = TestResult::from_result(
1223 &ShouldPanic::WithMessage("completely different".to_string()),
1224 Duration::from_millis(1),
1225 result.map(|_| Ok(())),
1226 );
1227 crate::panic_hook::clear_current_test_id();
1228 assert!(
1229 test_result.is_failed(),
1230 "Expected test to fail with wrong panic message"
1231 );
1232 let msg = test_result.failure_message().unwrap();
1233 assert!(
1234 msg.contains("unexpected message"),
1235 "Expected 'unexpected message' in: {msg}"
1236 );
1237 }
1238
1239 #[test]
1240 fn pretty_assertions_diff() {
1241 let result = simulate_runner(|| {
1242 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
1243 Box::new(())
1244 });
1245 assert!(result.is_failed());
1246 let cause = result.failure_cause().unwrap();
1247
1248 let panic_cause = match cause {
1250 FailureCause::Panic(p) => p,
1251 other => panic!("Expected Panic cause, got: {other:?}"),
1252 };
1253
1254 let message = panic_cause.message.as_deref().unwrap();
1256 println!("=== pretty_assertions failure message ===\n{message}\n===");
1257 assert!(
1258 message.contains("foo") && message.contains("baz"),
1259 "Expected diff with 'foo' and 'baz', got: {message}"
1260 );
1261
1262 assert!(panic_cause.location.is_some(), "Expected location info");
1264
1265 let rendered = cause.render();
1267 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
1268 assert!(
1269 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
1270 "Expected no backtrace noise in rendered output, got: {rendered}"
1271 );
1272 assert!(
1274 rendered.contains("\n at "),
1275 "Expected location in rendered output, got: {rendered}"
1276 );
1277 }
1278
1279 #[test]
1280 fn detached_thread_panic_detected() {
1281 crate::panic_hook::install_panic_hook();
1282 let test_id = crate::panic_hook::next_test_id();
1283 crate::panic_hook::set_current_test_id(test_id);
1284 crate::panic_hook::create_detached_collector(test_id);
1285
1286 let result = catch_unwind(AssertUnwindSafe(|| {
1287 let handle = crate::spawn::spawn_thread(|| {
1288 panic!("background thread panic");
1289 });
1290 let _ = handle.join();
1291 }));
1292
1293 let mut test_result = TestResult::from_result(
1294 &ShouldPanic::No,
1295 Duration::from_millis(1),
1296 result.map(|_| Ok(())),
1297 );
1298
1299 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
1300 let panics = match collector.lock() {
1301 Ok(p) => p,
1302 Err(poisoned) => poisoned.into_inner(),
1303 };
1304 if !panics.is_empty() && test_result.is_passed() {
1305 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
1306 test_result = TestResult::failed(
1307 Duration::from_millis(1),
1308 FailureCause::Panic(PanicCause {
1309 message: Some(format!(
1310 "Detached task(s) panicked:\n{}",
1311 messages.join("\n---\n")
1312 )),
1313 location: panics.first().and_then(|p| p.location.clone()),
1314 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
1315 }),
1316 );
1317 }
1318 }
1319
1320 crate::panic_hook::clear_current_test_id();
1321
1322 assert!(
1323 test_result.is_failed(),
1324 "Expected test to fail due to detached panic"
1325 );
1326 let msg = test_result.failure_message().unwrap();
1327 assert!(
1328 msg.contains("Detached task(s) panicked"),
1329 "Expected detached panic message, got: {msg}"
1330 );
1331 assert!(
1332 msg.contains("background thread panic"),
1333 "Expected original panic message, got: {msg}"
1334 );
1335 }
1336
1337 #[test]
1338 fn detached_thread_panic_ignored_with_policy() {
1339 crate::panic_hook::install_panic_hook();
1340 let test_id = crate::panic_hook::next_test_id();
1341 crate::panic_hook::set_current_test_id(test_id);
1342 crate::panic_hook::create_detached_collector(test_id);
1343
1344 let result = catch_unwind(AssertUnwindSafe(|| {
1345 let handle = crate::spawn::spawn_thread(|| {
1346 panic!("ignored thread panic");
1347 });
1348 let _ = handle.join();
1349 }));
1350
1351 let test_result = TestResult::from_result(
1352 &ShouldPanic::No,
1353 Duration::from_millis(1),
1354 result.map(|_| Ok(())),
1355 );
1356
1357 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
1358 let panics = match collector.lock() {
1359 Ok(p) => p,
1360 Err(poisoned) => poisoned.into_inner(),
1361 };
1362 assert!(
1364 !panics.is_empty(),
1365 "Expected panics in collector even with Ignore policy"
1366 );
1367 }
1368
1369 crate::panic_hook::clear_current_test_id();
1370
1371 assert!(
1372 test_result.is_passed(),
1373 "Expected test to pass with Ignore policy"
1374 );
1375 }
1376
1377 #[cfg(feature = "tokio")]
1378 #[test]
1379 fn detached_task_panic_detected() {
1380 let rt = tokio::runtime::Runtime::new().unwrap();
1381 rt.block_on(async {
1382 crate::panic_hook::install_panic_hook();
1383 let test_id = crate::panic_hook::next_test_id();
1384 crate::panic_hook::set_current_test_id(test_id);
1385 crate::panic_hook::create_detached_collector(test_id);
1386
1387 let handle = crate::spawn::spawn(async {
1388 panic!("detached task panic");
1389 });
1390 let _ = handle.await;
1391
1392 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
1393 let panics = collector.lock().unwrap();
1394
1395 assert_eq!(panics.len(), 1);
1396 assert!(
1397 panics[0]
1398 .message
1399 .as_ref()
1400 .unwrap()
1401 .contains("detached task panic"),
1402 "Expected panic message, got: {:?}",
1403 panics[0].message
1404 );
1405
1406 crate::panic_hook::clear_current_test_id();
1407 });
1408 }
1409
1410 #[test]
1411 fn failure_cause_variants() {
1412 let cause = FailureCause::ReturnedMessage("simple message".to_string());
1414 assert_eq!(cause.render(), "simple message");
1415 assert!(cause.panic_message().is_none());
1416
1417 let cause = FailureCause::ReturnedError {
1419 display: "display text".to_string(),
1420 debug: "debug text".to_string(),
1421 prefer_debug: false,
1422 error: Arc::new("display text".to_string()),
1423 };
1424 assert_eq!(cause.render(), "display text");
1425
1426 let cause = FailureCause::ReturnedError {
1428 display: "display text".to_string(),
1429 debug: "debug text".to_string(),
1430 prefer_debug: true,
1431 error: Arc::new("debug text".to_string()),
1432 };
1433 assert_eq!(cause.render(), "debug text");
1434
1435 let cause = FailureCause::HarnessError("harness error".to_string());
1437 assert_eq!(cause.render(), "harness error");
1438
1439 let cause = FailureCause::Panic(PanicCause {
1441 message: Some("panic msg".to_string()),
1442 location: None,
1443 backtrace: None,
1444 });
1445 assert_eq!(cause.render(), "panic msg");
1446 assert_eq!(cause.panic_message(), Some("panic msg"));
1447 }
1448}
1449
1450#[cfg(test)]
1451mod filter_tests {
1452 use super::*;
1453
1454 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
1455 RegisteredTest {
1456 name: name.to_string(),
1457 crate_name: "mycrate".to_string(),
1458 module_path: module_path.to_string(),
1459 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
1460 props: TestProperties::default(),
1461 dependencies: None,
1462 }
1463 }
1464
1465 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
1466 let mut test = make_test(name, module_path);
1467 test.props.tags = tags.into_iter().map(String::from).collect();
1468 test
1469 }
1470
1471 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
1472 Arguments {
1473 filter: filters.into_iter().map(String::from).collect(),
1474 skip: skip.into_iter().map(String::from).collect(),
1475 exact,
1476 ..Default::default()
1477 }
1478 }
1479
1480 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
1481 filter_registered_tests(args, tests)
1482 .into_iter()
1483 .map(|t| t.filterable_name())
1484 .collect()
1485 }
1486
1487 #[test]
1490 fn filter_test_substring_match() {
1491 let test = make_test("hello_world", "mod1");
1492 assert!(filter_test(&test, "hello", false));
1493 assert!(filter_test(&test, "world", false));
1494 assert!(filter_test(&test, "mod1::hello", false));
1495 assert!(!filter_test(&test, "nonexistent", false));
1496 }
1497
1498 #[test]
1499 fn filter_test_exact_match() {
1500 let test = make_test("hello_world", "mod1");
1501 assert!(filter_test(&test, "mod1::hello_world", true));
1502 assert!(!filter_test(&test, "hello_world", true));
1503 assert!(!filter_test(&test, "hello", true));
1504 }
1505
1506 #[test]
1507 fn filter_test_tag_match() {
1508 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
1509 assert!(filter_test(&test, ":tag:fast", false));
1510 assert!(filter_test(&test, ":tag:unit", false));
1511 assert!(!filter_test(&test, ":tag:slow", false));
1512 }
1513
1514 #[test]
1515 fn filter_test_tag_empty_matches_untagged() {
1516 let untagged = make_test("t1", "mod1");
1517 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
1518 assert!(filter_test(&untagged, ":tag:", false));
1519 assert!(!filter_test(&tagged, ":tag:", false));
1520 }
1521
1522 #[test]
1525 fn no_filters_includes_all() {
1526 let tests = vec![make_test("a", "m"), make_test("b", "m")];
1527 let args = make_args(vec![], vec![], false);
1528 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
1529 }
1530
1531 #[test]
1532 fn single_filter_substring() {
1533 let tests = vec![
1534 make_test("alpha", "m"),
1535 make_test("beta", "m"),
1536 make_test("alphabet", "m"),
1537 ];
1538 let args = make_args(vec!["alpha"], vec![], false);
1539 assert_eq!(
1540 filtered_names(&args, &tests),
1541 vec!["m::alpha", "m::alphabet"]
1542 );
1543 }
1544
1545 #[test]
1546 fn multiple_filters_or_semantics() {
1547 let tests = vec![
1548 make_test("alpha", "m"),
1549 make_test("beta", "m"),
1550 make_test("gamma", "m"),
1551 ];
1552 let args = make_args(vec!["alpha", "gamma"], vec![], false);
1553 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
1554 }
1555
1556 #[test]
1557 fn multiple_filters_exact() {
1558 let tests = vec![
1559 make_test("alpha", "m"),
1560 make_test("alphabet", "m"),
1561 make_test("beta", "m"),
1562 ];
1563 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
1564 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
1565 }
1566
1567 #[test]
1570 fn skip_substring_match() {
1571 let tests = vec![
1572 make_test("fast_test", "m"),
1573 make_test("slow_test", "m"),
1574 make_test("slower_test", "m"),
1575 ];
1576 let args = make_args(vec![], vec!["slow"], false);
1577 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
1578 }
1579
1580 #[test]
1581 fn skip_exact_match() {
1582 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
1583 let args = make_args(vec![], vec!["m::slow_test"], true);
1584 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
1585 }
1586
1587 #[test]
1588 fn skip_with_tag() {
1589 let tests = vec![
1590 make_tagged_test("t1", "m", vec!["slow"]),
1591 make_tagged_test("t2", "m", vec!["fast"]),
1592 make_test("t3", "m"),
1593 ];
1594 let args = make_args(vec![], vec![":tag:slow"], false);
1595 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
1596 }
1597
1598 #[test]
1601 fn include_and_skip_combined() {
1602 let tests = vec![
1603 make_test("alpha_fast", "m"),
1604 make_test("alpha_slow", "m"),
1605 make_test("beta_fast", "m"),
1606 ];
1607 let args = make_args(vec!["alpha"], vec!["slow"], false);
1609 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
1610 }
1611
1612 #[test]
1613 fn skip_wins_over_include() {
1614 let tests = vec![make_test("target", "m")];
1615 let args = make_args(vec!["target"], vec!["target"], false);
1617 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
1618 }
1619
1620 #[test]
1623 fn filter_test_tag_or_expression() {
1624 let test_a = make_tagged_test("t1", "m", vec!["a"]);
1626 let test_b = make_tagged_test("t2", "m", vec!["b"]);
1627 let test_c = make_tagged_test("t3", "m", vec!["c"]);
1628 assert!(filter_test(&test_a, ":tag:a|b", false));
1629 assert!(filter_test(&test_b, ":tag:a|b", false));
1630 assert!(!filter_test(&test_c, ":tag:a|b", false));
1631 }
1632
1633 #[test]
1634 fn filter_test_tag_and_expression() {
1635 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
1637 let test_a = make_tagged_test("t2", "m", vec!["a"]);
1638 let test_b = make_tagged_test("t3", "m", vec!["b"]);
1639 assert!(filter_test(&test_ab, ":tag:a&b", false));
1640 assert!(!filter_test(&test_a, ":tag:a&b", false));
1641 assert!(!filter_test(&test_b, ":tag:a&b", false));
1642 }
1643
1644 #[test]
1645 fn filter_test_tag_mixed_and_or() {
1646 let test_a = make_tagged_test("t1", "m", vec!["a"]);
1648 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
1649 let test_b = make_tagged_test("t3", "m", vec!["b"]);
1650 let test_c = make_tagged_test("t4", "m", vec!["c"]);
1651 let test_none = make_test("t5", "m");
1652 assert!(filter_test(&test_a, ":tag:a|b&c", false));
1653 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
1654 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
1655 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
1656 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
1657 }
1658
1659 #[test]
1660 fn filter_test_tag_exact_flag_does_not_affect_tags() {
1661 let test = make_tagged_test("t1", "m", vec!["fast"]);
1663 assert!(filter_test(&test, ":tag:fast", true));
1664 assert!(!filter_test(&test, ":tag:slow", true));
1665 }
1666
1667 #[test]
1668 fn include_by_tag_or_expression() {
1669 let tests = vec![
1670 make_tagged_test("t1", "m", vec!["unit"]),
1671 make_tagged_test("t2", "m", vec!["integration"]),
1672 make_tagged_test("t3", "m", vec!["e2e"]),
1673 ];
1674 let args = make_args(vec![":tag:unit|integration"], vec![], false);
1675 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
1676 }
1677
1678 #[test]
1679 fn skip_by_tag_and_expression() {
1680 let tests = vec![
1681 make_tagged_test("t1", "m", vec!["slow", "network"]),
1682 make_tagged_test("t2", "m", vec!["slow"]),
1683 make_tagged_test("t3", "m", vec!["network"]),
1684 make_test("t4", "m"),
1685 ];
1686 let args = make_args(vec![], vec![":tag:slow&network"], false);
1688 assert_eq!(
1689 filtered_names(&args, &tests),
1690 vec!["m::t2", "m::t3", "m::t4"]
1691 );
1692 }
1693}