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::collections::HashMap;
8use std::fmt::{Debug, Display, Formatter};
9use std::future::Future;
10use std::hash::Hash;
11use std::pin::Pin;
12use std::process::ExitCode;
13use std::sync::{Arc, Mutex};
14use std::time::{Duration, SystemTime};
15
16#[derive(Clone)]
17#[allow(clippy::type_complexity)]
18pub enum TestFunction {
19 Sync(
20 Arc<
21 dyn Fn(Arc<dyn DependencyView + Send + Sync>) -> Box<dyn TestReturnValue>
22 + Send
23 + Sync
24 + 'static,
25 >,
26 ),
27 SyncBench(
28 Arc<dyn Fn(&mut Bencher, Arc<dyn DependencyView + Send + Sync>) + Send + Sync + 'static>,
29 ),
30 #[cfg(feature = "tokio")]
31 Async(
32 Arc<
33 dyn (Fn(
34 Arc<dyn DependencyView + Send + Sync>,
35 ) -> Pin<Box<dyn Future<Output = Box<dyn TestReturnValue>>>>)
36 + Send
37 + Sync
38 + 'static,
39 >,
40 ),
41 #[cfg(feature = "tokio")]
42 AsyncBench(
43 Arc<
44 dyn for<'a> Fn(
45 &'a mut crate::bench::AsyncBencher,
46 Arc<dyn DependencyView + Send + Sync>,
47 ) -> Pin<Box<dyn Future<Output = ()> + 'a>>
48 + Send
49 + Sync
50 + 'static,
51 >,
52 ),
53}
54
55impl TestFunction {
56 #[cfg(not(feature = "tokio"))]
57 pub fn is_bench(&self) -> bool {
58 matches!(self, TestFunction::SyncBench(_))
59 }
60
61 #[cfg(feature = "tokio")]
62 pub fn is_bench(&self) -> bool {
63 matches!(
64 self,
65 TestFunction::SyncBench(_) | TestFunction::AsyncBench(_)
66 )
67 }
68}
69
70pub trait TestReturnValue {
71 fn into_result(self: Box<Self>) -> Result<(), FailureCause>;
72}
73
74impl TestReturnValue for () {
75 fn into_result(self: Box<Self>) -> Result<(), FailureCause> {
76 Ok(())
77 }
78}
79
80impl<T, E: Display + Debug + Send + Sync + 'static> TestReturnValue for Result<T, E> {
81 fn into_result(self: Box<Self>) -> Result<(), FailureCause> {
82 match *self {
83 Ok(_) => Ok(()),
84 Err(e) => Err(FailureCause::from_error(e)),
85 }
86 }
87}
88
89#[derive(Clone)]
90pub enum FailureCause {
91 ReturnedError {
94 display: String,
95 debug: String,
96 prefer_debug: bool,
97 error: Arc<dyn Any + Send + Sync>,
98 },
99 ReturnedMessage(String),
101 Panic(PanicCause),
103 HarnessError(String),
105}
106
107#[derive(Debug, Clone)]
108pub struct PanicCause {
109 pub message: Option<String>,
110 pub location: Option<PanicLocation>,
111 pub backtrace: Option<Arc<Backtrace>>,
112}
113
114#[derive(Debug, Clone)]
115pub struct PanicLocation {
116 pub file: String,
117 pub line: u32,
118 pub column: u32,
119}
120
121impl std::fmt::Debug for FailureCause {
122 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123 match self {
124 FailureCause::ReturnedError { display, .. } => {
125 f.debug_tuple("ReturnedError").field(display).finish()
126 }
127 FailureCause::ReturnedMessage(s) => f.debug_tuple("ReturnedMessage").field(s).finish(),
128 FailureCause::Panic(p) => f.debug_tuple("Panic").field(p).finish(),
129 FailureCause::HarnessError(s) => f.debug_tuple("HarnessError").field(s).finish(),
130 }
131 }
132}
133
134impl FailureCause {
135 pub fn from_error<E: Display + Debug + Send + Sync + 'static>(e: E) -> Self {
136 if TypeId::of::<E>() == TypeId::of::<String>() {
137 let any: Box<dyn Any + Send + Sync> = Box::new(e);
138 return FailureCause::ReturnedMessage(*any.downcast::<String>().unwrap());
139 }
140
141 let mut _prefer_debug = false;
142 #[cfg(feature = "anyhow")]
143 {
144 _prefer_debug = TypeId::of::<E>() == TypeId::of::<anyhow::Error>();
145 }
146
147 FailureCause::ReturnedError {
148 display: format!("{e:#}"),
149 debug: format!("{e:?}"),
150 prefer_debug: _prefer_debug,
151 error: Arc::new(e),
152 }
153 }
154
155 pub fn render(&self) -> String {
156 match self {
157 FailureCause::ReturnedError {
158 display,
159 debug,
160 prefer_debug,
161 ..
162 } => {
163 if *prefer_debug {
164 debug.clone()
165 } else {
166 display.clone()
167 }
168 }
169 FailureCause::ReturnedMessage(s) => s.clone(),
170 FailureCause::Panic(p) => p.render(),
171 FailureCause::HarnessError(s) => s.clone(),
172 }
173 }
174
175 pub fn panic_message(&self) -> Option<&str> {
177 match self {
178 FailureCause::Panic(p) => p.message.as_deref(),
179 _ => None,
180 }
181 }
182}
183
184impl PanicCause {
185 pub fn render(&self) -> String {
186 let mut out = self.message.clone().unwrap_or_default();
187 if let Some(loc) = &self.location {
188 out.push_str(&format!("\n at {}:{}:{}", loc.file, loc.line, loc.column));
189 }
190 if let Some(bt) = &self.backtrace {
191 let bt_str = format!("{bt}");
192 if !bt_str.is_empty() && bt_str != "disabled backtrace" {
193 out.push_str(&format!("\n\nStack backtrace:\n{bt}"));
194 }
195 }
196 out
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub enum ShouldPanic {
202 No,
203 Yes,
204 WithMessage(String),
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub enum TestType {
209 UnitTest,
210 IntegrationTest,
211}
212
213impl TestType {
214 pub fn from_path(path: &str) -> Self {
215 if path.contains("/src/") {
216 TestType::UnitTest
217 } else {
218 TestType::IntegrationTest
219 }
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub enum FlakinessControl {
225 None,
226 ProveNonFlaky(usize),
227 RetryKnownFlaky(usize),
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub enum DetachedPanicPolicy {
232 FailTest,
233 Ignore,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub enum CaptureControl {
238 Default,
239 AlwaysCapture,
240 NeverCapture,
241}
242
243impl CaptureControl {
244 pub fn requires_capturing(&self, default: bool) -> bool {
245 match self {
246 CaptureControl::Default => default,
247 CaptureControl::AlwaysCapture => true,
248 CaptureControl::NeverCapture => false,
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub enum ReportTimeControl {
255 Default,
256 Enabled,
257 Disabled,
258}
259
260#[derive(Clone)]
261pub struct TestProperties {
262 pub should_panic: ShouldPanic,
263 pub test_type: TestType,
264 pub timeout: Option<Duration>,
265 pub flakiness_control: FlakinessControl,
266 pub capture_control: CaptureControl,
267 pub report_time_control: ReportTimeControl,
268 pub ensure_time_control: ReportTimeControl,
269 pub tags: Vec<String>,
270 pub is_ignored: bool,
271 pub detached_panic_policy: DetachedPanicPolicy,
272}
273
274impl TestProperties {
275 pub fn unit_test() -> Self {
276 TestProperties {
277 test_type: TestType::UnitTest,
278 ..Default::default()
279 }
280 }
281
282 pub fn integration_test() -> Self {
283 TestProperties {
284 test_type: TestType::IntegrationTest,
285 ..Default::default()
286 }
287 }
288}
289
290impl Default for TestProperties {
291 fn default() -> Self {
292 Self {
293 should_panic: ShouldPanic::No,
294 test_type: TestType::UnitTest,
295 timeout: None,
296 flakiness_control: FlakinessControl::None,
297 capture_control: CaptureControl::Default,
298 report_time_control: ReportTimeControl::Default,
299 ensure_time_control: ReportTimeControl::Default,
300 tags: Vec::new(),
301 is_ignored: false,
302 detached_panic_policy: DetachedPanicPolicy::FailTest,
303 }
304 }
305}
306
307#[derive(Clone)]
308pub struct RegisteredTest {
309 pub name: String,
310 pub crate_name: String,
311 pub module_path: String,
312 pub run: TestFunction,
313 pub props: TestProperties,
314 pub dependencies: Option<Vec<String>>,
315}
316
317impl RegisteredTest {
318 pub fn filterable_name(&self) -> String {
319 if !self.module_path.is_empty() {
320 format!("{}::{}", self.module_path, self.name)
321 } else {
322 self.name.clone()
323 }
324 }
325
326 pub fn fully_qualified_name(&self) -> String {
327 [&self.crate_name, &self.module_path, &self.name]
328 .into_iter()
329 .filter(|s| !s.is_empty())
330 .cloned()
331 .collect::<Vec<String>>()
332 .join("::")
333 }
334
335 pub fn crate_and_module(&self) -> String {
336 [&self.crate_name, &self.module_path]
337 .into_iter()
338 .filter(|s| !s.is_empty())
339 .cloned()
340 .collect::<Vec<String>>()
341 .join("::")
342 }
343}
344
345impl Debug for RegisteredTest {
346 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
347 f.debug_struct("RegisteredTest")
348 .field("name", &self.name)
349 .field("crate_name", &self.crate_name)
350 .field("module_path", &self.module_path)
351 .finish()
352 }
353}
354
355pub static REGISTERED_TESTS: Mutex<Vec<RegisteredTest>> = Mutex::new(Vec::new());
356
357#[derive(Clone)]
358#[allow(clippy::type_complexity)]
359pub enum DependencyConstructor {
360 Sync(
361 Arc<
362 dyn (Fn(Arc<dyn DependencyView + Send + Sync>) -> Arc<dyn Any + Send + Sync + 'static>)
363 + Send
364 + Sync
365 + 'static,
366 >,
367 ),
368 Async(
369 Arc<
370 dyn (Fn(
371 Arc<dyn DependencyView + Send + Sync>,
372 ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
373 + Send
374 + Sync
375 + 'static,
376 >,
377 ),
378}
379
380pub trait CloneableDep: Sized + Send + Sync + 'static {
394 fn to_wire(&self) -> Vec<u8>;
396
397 fn from_wire(bytes: &[u8]) -> Self;
399}
400
401pub trait HostedDep: Sized + Send + Sync + 'static {
421 fn descriptor(&self) -> Vec<u8>;
424
425 fn from_descriptor(bytes: &[u8]) -> Self;
428}
429
430pub trait AsyncHostedDep: Sized + Send + Sync + 'static {
470 fn descriptor(&self) -> Vec<u8>;
475
476 fn from_descriptor(bytes: &[u8]) -> impl std::future::Future<Output = Self> + Send;
479}
480
481impl<T: HostedDep> AsyncHostedDep for T {
514 fn descriptor(&self) -> Vec<u8> {
515 <T as HostedDep>::descriptor(self)
516 }
517
518 fn from_descriptor(bytes: &[u8]) -> impl std::future::Future<Output = Self> + Send {
519 std::future::ready(<T as HostedDep>::from_descriptor(bytes))
520 }
521}
522
523#[cfg(test)]
524mod hosted_dep_blanket_bridge_tests {
525 use super::{AsyncHostedDep, HostedDep};
526 use std::future::Future;
529
530 #[derive(Debug, PartialEq, Eq)]
533 struct SyncOnlyDep {
534 bytes: Vec<u8>,
535 }
536
537 impl HostedDep for SyncOnlyDep {
538 fn descriptor(&self) -> Vec<u8> {
539 self.bytes.clone()
540 }
541
542 fn from_descriptor(bytes: &[u8]) -> Self {
543 Self {
544 bytes: bytes.to_vec(),
545 }
546 }
547 }
548
549 fn requires_async_hosted_dep<T: AsyncHostedDep>(_t: &T) {}
555
556 #[test]
557 fn blanket_impl_exposes_sync_hosted_dep_via_async_api() {
558 let dep = SyncOnlyDep {
559 bytes: vec![1, 2, 3, 4],
560 };
561
562 requires_async_hosted_dep(&dep);
565
566 assert_eq!(
569 <SyncOnlyDep as HostedDep>::descriptor(&dep),
570 vec![1, 2, 3, 4]
571 );
572 assert_eq!(
573 <SyncOnlyDep as AsyncHostedDep>::descriptor(&dep),
574 vec![1, 2, 3, 4]
575 );
576
577 let fut = <SyncOnlyDep as AsyncHostedDep>::from_descriptor(&[7, 8, 9]);
583 let mut fut = Box::pin(fut);
584 let waker = futures_test_helpers::noop_waker();
585 let mut cx = std::task::Context::from_waker(&waker);
586 match fut.as_mut().poll(&mut cx) {
587 std::task::Poll::Ready(value) => {
588 assert_eq!(
589 value,
590 SyncOnlyDep {
591 bytes: vec![7, 8, 9]
592 },
593 "blanket-bridged from_descriptor must yield the same value the sync impl produces"
594 );
595 }
596 std::task::Poll::Pending => panic!(
597 "blanket-bridged from_descriptor must be immediately ready (std::future::ready)"
598 ),
599 }
600 }
601
602 mod futures_test_helpers {
605 use std::task::{RawWaker, RawWakerVTable, Waker};
606
607 unsafe fn clone(p: *const ()) -> RawWaker {
608 RawWaker::new(p, &VTABLE)
609 }
610 unsafe fn wake(_: *const ()) {}
611 unsafe fn wake_by_ref(_: *const ()) {}
612 unsafe fn drop(_: *const ()) {}
613
614 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
615
616 pub fn noop_waker() -> Waker {
617 unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
620 }
621 }
622}
623
624pub trait HostedRpcDep: Send + Sync + 'static {
645 type Stub: Send + Sync + 'static;
649
650 fn dispatch(&mut self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String>;
657
658 fn build_stub(channel: HostedRpcChannel) -> Self::Stub;
677}
678
679pub trait HostedRpcDispatcher: Send + Sync {
683 fn dispatch(&mut self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String>;
684}
685
686impl<T: HostedRpcDep> HostedRpcDispatcher for T {
687 fn dispatch(&mut self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
688 <T as HostedRpcDep>::dispatch(self, method_idx, args)
689 }
690}
691
692pub trait AsyncHostedRpcDep: Send + Sync + 'static {
708 type Stub: Send + Sync + 'static;
711
712 fn dispatch<'a>(
716 &'a mut self,
717 method_idx: u32,
718 args: &'a [u8],
719 ) -> impl Future<Output = Result<Vec<u8>, String>> + Send + 'a;
720
721 fn build_stub(channel: HostedRpcChannel) -> Self::Stub;
725}
726
727impl<T: HostedRpcDep> AsyncHostedRpcDep for T {
753 type Stub = <T as HostedRpcDep>::Stub;
754
755 fn dispatch<'a>(
756 &'a mut self,
757 method_idx: u32,
758 args: &'a [u8],
759 ) -> impl Future<Output = Result<Vec<u8>, String>> + Send + 'a {
760 std::future::ready(<T as HostedRpcDep>::dispatch(self, method_idx, args))
761 }
762
763 fn build_stub(channel: HostedRpcChannel) -> Self::Stub {
764 <T as HostedRpcDep>::build_stub(channel)
765 }
766}
767
768pub trait AsyncHostedRpcDispatcher: Send + Sync {
771 fn dispatch<'a>(
772 &'a mut self,
773 method_idx: u32,
774 args: &'a [u8],
775 ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>>;
776}
777
778impl<T: AsyncHostedRpcDep> AsyncHostedRpcDispatcher for T {
779 fn dispatch<'a>(
780 &'a mut self,
781 method_idx: u32,
782 args: &'a [u8],
783 ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>> {
784 Box::pin(<T as AsyncHostedRpcDep>::dispatch(self, method_idx, args))
785 }
786}
787
788#[cfg(test)]
789mod hosted_rpc_blanket_bridge_tests {
790 use super::{AsyncHostedRpcDep, HostedRpcChannel, HostedRpcDep};
791
792 struct SyncOnlyOwner {
795 next: u64,
796 }
797
798 pub struct SyncOnlyStub {
801 _channel: HostedRpcChannel,
802 }
803
804 impl HostedRpcDep for SyncOnlyOwner {
805 type Stub = SyncOnlyStub;
806
807 fn dispatch(&mut self, method_idx: u32, _args: &[u8]) -> Result<Vec<u8>, String> {
808 if method_idx == 0 {
809 self.next += 1;
810 Ok(self.next.to_be_bytes().to_vec())
811 } else {
812 Err(format!("SyncOnlyOwner: unknown method_idx {method_idx}"))
813 }
814 }
815
816 fn build_stub(channel: HostedRpcChannel) -> Self::Stub {
817 SyncOnlyStub { _channel: channel }
818 }
819 }
820
821 fn requires_async_hosted_rpc_dep<T: AsyncHostedRpcDep>(_t: &T) {}
827
828 #[test]
829 fn blanket_impl_exposes_sync_hosted_rpc_dep_via_async_api() {
830 let owner = SyncOnlyOwner { next: 0 };
831 requires_async_hosted_rpc_dep(&owner);
834 }
835
836 #[cfg(feature = "tokio")]
842 #[test]
843 fn bridged_async_dispatch_round_trips_sync_owner_bytes() {
844 let mut owner = SyncOnlyOwner { next: 0 };
845 let rt = ::tokio::runtime::Builder::new_multi_thread()
846 .enable_all()
847 .build()
848 .expect("build tokio runtime");
849 let bytes = rt
850 .block_on(<SyncOnlyOwner as AsyncHostedRpcDep>::dispatch(
851 &mut owner,
852 0,
853 &[],
854 ))
855 .expect("bridged dispatch must succeed");
856 assert_eq!(
857 bytes,
858 1u64.to_be_bytes().to_vec(),
859 "bridged async dispatch must yield the same bytes the sync impl produces"
860 );
861 }
862}
863
864pub struct HostedRpcOwnerCell {
878 inner: HostedRpcOwnerCellInner,
879}
880
881enum HostedRpcOwnerCellInner {
882 Sync(Mutex<Box<dyn HostedRpcDispatcher>>),
883 #[cfg(feature = "tokio")]
884 Async(AsyncOwnerCell),
885}
886
887#[cfg(feature = "tokio")]
888struct AsyncOwnerCell {
889 poisoned: std::sync::atomic::AtomicBool,
894 inner: tokio::sync::Mutex<Box<dyn AsyncHostedRpcDispatcher>>,
895}
896
897impl HostedRpcOwnerCell {
898 pub fn from_owner<T: HostedRpcDep>(owner: T) -> Self {
904 Self {
905 inner: HostedRpcOwnerCellInner::Sync(Mutex::new(
906 Box::new(owner) as Box<dyn HostedRpcDispatcher>
907 )),
908 }
909 }
910
911 #[cfg(feature = "tokio")]
916 pub fn from_async_owner<T: AsyncHostedRpcDep>(owner: T) -> Self {
917 Self {
918 inner: HostedRpcOwnerCellInner::Async(AsyncOwnerCell {
919 poisoned: std::sync::atomic::AtomicBool::new(false),
920 inner: tokio::sync::Mutex::new(Box::new(owner) as Box<dyn AsyncHostedRpcDispatcher>),
921 }),
922 }
923 }
924
925 pub fn from_shared_owner_sync<T, F>(owner: Arc<T>, dispatch: F) -> Self
940 where
941 T: Send + Sync + 'static,
942 F: Fn(&T, u32, &[u8]) -> Result<Vec<u8>, String> + Send + Sync + 'static,
943 {
944 struct SharedDispatcher<T, F>
945 where
946 T: Send + Sync + 'static,
947 F: Fn(&T, u32, &[u8]) -> Result<Vec<u8>, String> + Send + Sync + 'static,
948 {
949 owner: Arc<T>,
950 dispatch: F,
951 }
952
953 impl<T, F> HostedRpcDispatcher for SharedDispatcher<T, F>
954 where
955 T: Send + Sync + 'static,
956 F: Fn(&T, u32, &[u8]) -> Result<Vec<u8>, String> + Send + Sync + 'static,
957 {
958 fn dispatch(&mut self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
959 (self.dispatch)(&self.owner, method_idx, args)
960 }
961 }
962
963 let dispatcher: Box<dyn HostedRpcDispatcher> =
964 Box::new(SharedDispatcher { owner, dispatch });
965 Self {
966 inner: HostedRpcOwnerCellInner::Sync(Mutex::new(dispatcher)),
967 }
968 }
969
970 #[cfg(feature = "tokio")]
979 pub fn from_shared_owner_async<T, F>(owner: Arc<T>, dispatch: F) -> Self
980 where
981 T: Send + Sync + 'static,
982 F: for<'a> Fn(
983 &'a T,
984 u32,
985 &'a [u8],
986 )
987 -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>>
988 + Send
989 + Sync
990 + 'static,
991 {
992 struct SharedAsyncDispatcher<T, F>
993 where
994 T: Send + Sync + 'static,
995 F: for<'a> Fn(
996 &'a T,
997 u32,
998 &'a [u8],
999 )
1000 -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>>
1001 + Send
1002 + Sync
1003 + 'static,
1004 {
1005 owner: Arc<T>,
1006 dispatch: F,
1007 }
1008
1009 impl<T, F> AsyncHostedRpcDispatcher for SharedAsyncDispatcher<T, F>
1010 where
1011 T: Send + Sync + 'static,
1012 F: for<'a> Fn(
1013 &'a T,
1014 u32,
1015 &'a [u8],
1016 )
1017 -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>>
1018 + Send
1019 + Sync
1020 + 'static,
1021 {
1022 fn dispatch<'a>(
1023 &'a mut self,
1024 method_idx: u32,
1025 args: &'a [u8],
1026 ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + Send + 'a>> {
1027 (self.dispatch)(&self.owner, method_idx, args)
1028 }
1029 }
1030
1031 let dispatcher: Box<dyn AsyncHostedRpcDispatcher> =
1032 Box::new(SharedAsyncDispatcher { owner, dispatch });
1033 Self {
1034 inner: HostedRpcOwnerCellInner::Async(AsyncOwnerCell {
1035 poisoned: std::sync::atomic::AtomicBool::new(false),
1036 inner: tokio::sync::Mutex::new(dispatcher),
1037 }),
1038 }
1039 }
1040
1041 pub fn dispatch(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
1059 match &self.inner {
1060 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
1061 #[cfg(feature = "tokio")]
1062 HostedRpcOwnerCellInner::Async(_) => Err(
1063 "hosted rpc owner cell uses the async dispatch path; use dispatch_async or dispatch_blocking"
1064 .to_string(),
1065 ),
1066 }
1067 }
1068
1069 #[cfg(feature = "tokio")]
1078 pub async fn dispatch_async(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
1079 match &self.inner {
1080 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
1081 HostedRpcOwnerCellInner::Async(cell) => {
1082 async_dispatch_inner(cell, method_idx, args).await
1083 }
1084 }
1085 }
1086
1087 #[cfg(feature = "tokio")]
1098 pub fn dispatch_blocking(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
1099 match &self.inner {
1100 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
1101 HostedRpcOwnerCellInner::Async(cell) => {
1102 let handle = tokio::runtime::Handle::try_current().map_err(|_| {
1103 "hosted rpc owner is async-only and no Tokio runtime is active at the dispatch site"
1104 .to_string()
1105 })?;
1106 if !matches!(
1112 handle.runtime_flavor(),
1113 tokio::runtime::RuntimeFlavor::MultiThread
1114 ) {
1115 return Err(
1116 "hosted rpc owner is async-only and the current Tokio runtime is not multi-threaded"
1117 .to_string(),
1118 );
1119 }
1120 tokio::task::block_in_place(|| {
1121 handle.block_on(async_dispatch_inner(cell, method_idx, args))
1122 })
1123 }
1124 }
1125 }
1126}
1127
1128fn sync_dispatch_inner(
1129 mtx: &Mutex<Box<dyn HostedRpcDispatcher>>,
1130 method_idx: u32,
1131 args: &[u8],
1132) -> Result<Vec<u8>, String> {
1133 let dispatch_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1141 let mut guard = match mtx.lock() {
1142 Ok(g) => g,
1143 Err(_) => return Err("hosted rpc owner poisoned".to_string()),
1144 };
1145 guard.dispatch(method_idx, args)
1146 }));
1147 panic_payload_to_err(dispatch_result)
1148}
1149
1150#[cfg(feature = "tokio")]
1151async fn async_dispatch_inner(
1152 cell: &AsyncOwnerCell,
1153 method_idx: u32,
1154 args: &[u8],
1155) -> Result<Vec<u8>, String> {
1156 use futures::FutureExt;
1157 use std::sync::atomic::Ordering;
1158
1159 if cell.poisoned.load(Ordering::SeqCst) {
1162 return Err("hosted rpc owner poisoned".to_string());
1163 }
1164 let mut guard = cell.inner.lock().await;
1165 if cell.poisoned.load(Ordering::SeqCst) {
1173 return Err("hosted rpc owner poisoned".to_string());
1174 }
1175 let fut = std::panic::AssertUnwindSafe(async {
1176 AsyncHostedRpcDispatcher::dispatch(&mut **guard, method_idx, args).await
1177 });
1178 let outcome = fut.catch_unwind().await;
1179 match outcome {
1180 Ok(r) => {
1181 drop(guard);
1182 r
1183 }
1184 Err(payload) => {
1185 cell.poisoned.store(true, Ordering::SeqCst);
1190 drop(guard);
1191 let msg = panic_payload_to_string(&payload);
1192 Err(format!("hosted rpc owner panicked: {msg}"))
1193 }
1194 }
1195}
1196
1197fn panic_payload_to_err(
1198 dispatch_result: Result<Result<Vec<u8>, String>, Box<dyn Any + Send>>,
1199) -> Result<Vec<u8>, String> {
1200 match dispatch_result {
1201 Ok(r) => r,
1202 Err(payload) => {
1203 let msg = panic_payload_to_string(&payload);
1204 Err(format!("hosted rpc owner panicked: {msg}"))
1205 }
1206 }
1207}
1208
1209fn panic_payload_to_string(payload: &Box<dyn Any + Send>) -> String {
1210 if let Some(s) = payload.downcast_ref::<&str>() {
1211 (*s).to_string()
1212 } else if let Some(s) = payload.downcast_ref::<String>() {
1213 s.clone()
1214 } else {
1215 "<non-string panic payload>".to_string()
1216 }
1217}
1218
1219pub struct HostedBothShared {
1243 descriptor_bytes: Vec<u8>,
1244 owner: Arc<dyn Any + Send + Sync>,
1250 rpc_cell: Arc<HostedRpcOwnerCell>,
1251}
1252
1253impl HostedBothShared {
1254 pub fn new(
1258 descriptor_bytes: Vec<u8>,
1259 owner: Arc<dyn Any + Send + Sync>,
1260 rpc_cell: Arc<HostedRpcOwnerCell>,
1261 ) -> Self {
1262 Self {
1263 descriptor_bytes,
1264 owner,
1265 rpc_cell,
1266 }
1267 }
1268
1269 pub fn descriptor_bytes(&self) -> &[u8] {
1272 &self.descriptor_bytes
1273 }
1274
1275 pub fn rpc_cell(&self) -> Arc<HostedRpcOwnerCell> {
1279 self.rpc_cell.clone()
1280 }
1281
1282 pub fn owner_arc<T>(&self) -> Arc<T>
1287 where
1288 T: Send + Sync + 'static,
1289 {
1290 Arc::clone(&self.owner)
1291 .downcast::<T>()
1292 .expect("HostedBothShared owner type mismatch")
1293 }
1294}
1295
1296#[derive(Debug, Clone)]
1298pub enum HostedRpcError {
1299 Dispatch(String),
1302 Transport(String),
1305}
1306
1307impl std::fmt::Display for HostedRpcError {
1308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1309 match self {
1310 HostedRpcError::Dispatch(s) => write!(f, "hosted rpc dispatch error: {s}"),
1311 HostedRpcError::Transport(s) => write!(f, "hosted rpc transport error: {s}"),
1312 }
1313 }
1314}
1315
1316impl std::error::Error for HostedRpcError {}
1317
1318pub trait HostedRpcTransport: Send + Sync {
1323 fn call(&self, dep_id: &str, method_idx: u32, args: Vec<u8>)
1327 -> Result<Vec<u8>, HostedRpcError>;
1328}
1329
1330pub struct HostedRpcChannel {
1336 dep_id: String,
1337 transport: Arc<dyn HostedRpcTransport>,
1338}
1339
1340impl HostedRpcChannel {
1341 pub fn new(dep_id: String, transport: Arc<dyn HostedRpcTransport>) -> Self {
1344 Self { dep_id, transport }
1345 }
1346
1347 pub fn dep_id(&self) -> &str {
1350 &self.dep_id
1351 }
1352
1353 pub fn call(&self, method_idx: u32, args: Vec<u8>) -> Result<Vec<u8>, HostedRpcError> {
1378 self.transport.call(&self.dep_id, method_idx, args)
1379 }
1380}
1381
1382impl Clone for HostedRpcChannel {
1383 fn clone(&self) -> Self {
1384 Self {
1385 dep_id: self.dep_id.clone(),
1386 transport: self.transport.clone(),
1387 }
1388 }
1389}
1390
1391pub struct InProcessHostedRpcTransport {
1395 cells: HashMap<String, Arc<HostedRpcOwnerCell>>,
1396}
1397
1398impl InProcessHostedRpcTransport {
1399 pub fn new(cells: HashMap<String, Arc<HostedRpcOwnerCell>>) -> Self {
1400 Self { cells }
1401 }
1402}
1403
1404impl HostedRpcTransport for InProcessHostedRpcTransport {
1405 fn call(
1406 &self,
1407 dep_id: &str,
1408 method_idx: u32,
1409 args: Vec<u8>,
1410 ) -> Result<Vec<u8>, HostedRpcError> {
1411 let cell = self.cells.get(dep_id).ok_or_else(|| {
1412 HostedRpcError::Transport(format!("in-process HostedRpc: unknown dep id '{dep_id}'"))
1413 })?;
1414 #[cfg(feature = "tokio")]
1419 let result = cell.dispatch_blocking(method_idx, &args);
1420 #[cfg(not(feature = "tokio"))]
1421 let result = cell.dispatch(method_idx, &args);
1422 result.map_err(HostedRpcError::Dispatch)
1423 }
1424}
1425
1426#[derive(Clone)]
1431#[allow(clippy::type_complexity)]
1432pub struct RpcFactory {
1433 pub owner_into_cell: Arc<
1436 dyn (Fn(Arc<dyn Any + Send + Sync>) -> Arc<HostedRpcOwnerCell>) + Send + Sync + 'static,
1437 >,
1438 pub build_stub:
1441 Arc<dyn (Fn(HostedRpcChannel) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
1442}
1443
1444#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
1449pub enum DepScope {
1450 #[default]
1454 Shared,
1455 PerWorker,
1458 Cloneable,
1462 Hosted,
1471 HostedRpc,
1478}
1479
1480impl DepScope {
1481 pub fn requires_single_thread_when_capturing(&self) -> bool {
1484 matches!(self, DepScope::Shared)
1485 }
1486
1487 pub fn parent_must_materialize_under_spawn_workers(&self) -> bool {
1493 matches!(
1494 self,
1495 DepScope::Cloneable | DepScope::Hosted | DepScope::HostedRpc
1496 )
1497 }
1498}
1499
1500#[derive(Clone)]
1505#[allow(clippy::type_complexity)]
1506pub enum WorkerReconstructor {
1507 Sync(
1508 Arc<
1509 dyn (Fn(
1510 Arc<dyn Any + Send + Sync>,
1511 Arc<dyn DependencyView + Send + Sync>,
1512 ) -> Arc<dyn Any + Send + Sync + 'static>)
1513 + Send
1514 + Sync
1515 + 'static,
1516 >,
1517 ),
1518 Async(
1519 Arc<
1520 dyn (Fn(
1521 Arc<dyn Any + Send + Sync>,
1522 Arc<dyn DependencyView + Send + Sync>,
1523 ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
1524 + Send
1525 + Sync
1526 + 'static,
1527 >,
1528 ),
1529}
1530
1531#[derive(Clone)]
1535#[allow(clippy::type_complexity)]
1536pub struct CloneableCodec {
1537 pub to_wire: Arc<dyn (Fn(Arc<dyn Any + Send + Sync>) -> Vec<u8>) + Send + Sync + 'static>,
1540 pub from_wire_bytes: Arc<dyn (Fn(&[u8]) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
1543}
1544
1545#[derive(Clone)]
1546pub struct RegisteredDependency {
1547 pub name: String, pub crate_name: String,
1549 pub module_path: String,
1550 pub constructor: DependencyConstructor,
1551 pub dependencies: Vec<String>,
1552 pub scope: DepScope,
1555 pub worker_fn: Option<WorkerReconstructor>,
1560 pub cloneable_codec: Option<CloneableCodec>,
1564 pub hosted_codec: Option<CloneableCodec>,
1571 pub rpc_factory: Option<RpcFactory>,
1576 pub companions: Vec<String>,
1595}
1596
1597impl RegisteredDependency {
1598 pub fn new_shared(
1602 name: String,
1603 crate_name: String,
1604 module_path: String,
1605 constructor: DependencyConstructor,
1606 dependencies: Vec<String>,
1607 ) -> Self {
1608 Self {
1609 name,
1610 crate_name,
1611 module_path,
1612 constructor,
1613 dependencies,
1614 scope: DepScope::Shared,
1615 worker_fn: None,
1616 cloneable_codec: None,
1617 hosted_codec: None,
1618 rpc_factory: None,
1619 companions: Vec::new(),
1620 }
1621 }
1622}
1623
1624impl Debug for RegisteredDependency {
1625 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1626 f.debug_struct("RegisteredDependency")
1627 .field("name", &self.name)
1628 .field("crate_name", &self.crate_name)
1629 .field("module_path", &self.module_path)
1630 .finish()
1631 }
1632}
1633
1634impl PartialEq for RegisteredDependency {
1635 fn eq(&self, other: &Self) -> bool {
1636 self.name == other.name
1637 }
1638}
1639
1640impl Eq for RegisteredDependency {}
1641
1642impl Hash for RegisteredDependency {
1643 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1644 self.name.hash(state);
1645 }
1646}
1647
1648impl RegisteredDependency {
1649 pub fn crate_and_module(&self) -> String {
1650 [&self.crate_name, &self.module_path]
1651 .into_iter()
1652 .filter(|s| !s.is_empty())
1653 .cloned()
1654 .collect::<Vec<String>>()
1655 .join("::")
1656 }
1657
1658 pub fn qualified_id(&self) -> String {
1663 [&self.crate_name, &self.module_path, &self.name]
1664 .into_iter()
1665 .filter(|s| !s.is_empty())
1666 .cloned()
1667 .collect::<Vec<String>>()
1668 .join("::")
1669 }
1670}
1671
1672pub static REGISTERED_DEPENDENCY_CONSTRUCTORS: Mutex<Vec<RegisteredDependency>> =
1673 Mutex::new(Vec::new());
1674
1675#[derive(Debug, Clone)]
1676pub enum RegisteredTestSuiteProperty {
1677 Sequential {
1678 name: String,
1679 crate_name: String,
1680 module_path: String,
1681 },
1682 Tag {
1683 name: String,
1684 crate_name: String,
1685 module_path: String,
1686 tag: String,
1687 },
1688 Timeout {
1689 name: String,
1690 crate_name: String,
1691 module_path: String,
1692 timeout: Duration,
1693 },
1694}
1695
1696impl RegisteredTestSuiteProperty {
1697 pub fn crate_name(&self) -> &String {
1698 match self {
1699 RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
1700 RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
1701 RegisteredTestSuiteProperty::Timeout { crate_name, .. } => crate_name,
1702 }
1703 }
1704
1705 pub fn module_path(&self) -> &String {
1706 match self {
1707 RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
1708 RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
1709 RegisteredTestSuiteProperty::Timeout { module_path, .. } => module_path,
1710 }
1711 }
1712
1713 pub fn name(&self) -> &String {
1714 match self {
1715 RegisteredTestSuiteProperty::Sequential { name, .. } => name,
1716 RegisteredTestSuiteProperty::Tag { name, .. } => name,
1717 RegisteredTestSuiteProperty::Timeout { name, .. } => name,
1718 }
1719 }
1720
1721 pub fn crate_and_module(&self) -> String {
1722 [self.crate_name(), self.module_path(), self.name()]
1723 .into_iter()
1724 .filter(|s| !s.is_empty())
1725 .cloned()
1726 .collect::<Vec<String>>()
1727 .join("::")
1728 }
1729}
1730
1731pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
1732 Mutex::new(Vec::new());
1733
1734#[derive(Clone)]
1735#[allow(clippy::type_complexity)]
1736pub enum TestGeneratorFunction {
1737 Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
1738 Async(
1739 Arc<
1740 dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
1741 + Send
1742 + Sync
1743 + 'static,
1744 >,
1745 ),
1746}
1747
1748pub struct DynamicTestRegistration {
1749 tests: Vec<GeneratedTest>,
1750}
1751
1752impl Default for DynamicTestRegistration {
1753 fn default() -> Self {
1754 Self::new()
1755 }
1756}
1757
1758impl DynamicTestRegistration {
1759 pub fn new() -> Self {
1760 Self { tests: Vec::new() }
1761 }
1762
1763 pub fn to_vec(self) -> Vec<GeneratedTest> {
1764 self.tests
1765 }
1766
1767 pub fn add_sync_test<R: TestReturnValue + 'static>(
1768 &mut self,
1769 name: impl AsRef<str>,
1770 props: TestProperties,
1771 dependencies: Option<Vec<String>>,
1772 run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
1773 ) {
1774 self.tests.push(GeneratedTest {
1775 name: name.as_ref().to_string(),
1776 run: TestFunction::Sync(Arc::new(move |deps| {
1777 Box::new(run(deps)) as Box<dyn TestReturnValue>
1778 })),
1779 props,
1780 dependencies,
1781 });
1782 }
1783
1784 #[cfg(feature = "tokio")]
1785 pub fn add_async_test<R: TestReturnValue + 'static>(
1786 &mut self,
1787 name: impl AsRef<str>,
1788 props: TestProperties,
1789 dependencies: Option<Vec<String>>,
1790 run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
1791 + Send
1792 + Sync
1793 + Clone
1794 + 'static,
1795 ) {
1796 self.tests.push(GeneratedTest {
1797 name: name.as_ref().to_string(),
1798 run: TestFunction::Async(Arc::new(move |deps| {
1799 let run = run.clone();
1800 Box::pin(async move {
1801 let r = run(deps).await;
1802 Box::new(r) as Box<dyn TestReturnValue>
1803 })
1804 })),
1805 props,
1806 dependencies,
1807 });
1808 }
1809}
1810
1811#[derive(Clone)]
1812pub struct GeneratedTest {
1813 pub name: String,
1814 pub run: TestFunction,
1815 pub props: TestProperties,
1816 pub dependencies: Option<Vec<String>>,
1817}
1818
1819#[derive(Clone)]
1820pub struct RegisteredTestGenerator {
1821 pub name: String,
1822 pub crate_name: String,
1823 pub module_path: String,
1824 pub run: TestGeneratorFunction,
1825 pub is_ignored: bool,
1826}
1827
1828impl RegisteredTestGenerator {
1829 pub fn crate_and_module(&self) -> String {
1830 [&self.crate_name, &self.module_path]
1831 .into_iter()
1832 .filter(|s| !s.is_empty())
1833 .cloned()
1834 .collect::<Vec<String>>()
1835 .join("::")
1836 }
1837}
1838
1839pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
1840
1841pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
1842 if let Some(tag_list) = filter.strip_prefix(":tag:") {
1843 if tag_list.is_empty() {
1844 test.props.tags.is_empty()
1846 } else {
1847 let or_tags = tag_list.split('|').collect::<Vec<&str>>();
1848 let mut result = false;
1849 for or_tag in or_tags {
1850 let and_tags = or_tag.split('&').collect::<Vec<&str>>();
1851 let mut and_result = true;
1852 for and_tag in and_tags {
1853 if !test.props.tags.contains(&and_tag.to_string()) {
1854 and_result = false;
1855 break;
1856 }
1857 }
1858 if and_result {
1859 result = true;
1860 break;
1861 }
1862 }
1863 result
1864 }
1865 } else if exact {
1866 test.filterable_name() == filter
1867 } else {
1868 test.filterable_name().contains(filter)
1869 }
1870}
1871
1872pub(crate) fn apply_suite_props_to_tests(
1873 tests: &[RegisteredTest],
1874 props: &[RegisteredTestSuiteProperty],
1875) -> Vec<RegisteredTest> {
1876 let props_with_prefix = props
1877 .iter()
1878 .map(|prop| (prop.crate_and_module(), prop))
1879 .collect::<Vec<_>>();
1880
1881 let mut result = Vec::new();
1882 for test in tests {
1883 let mut test = test.clone();
1884 for (prefix, prop) in &props_with_prefix {
1885 if test.crate_and_module().starts_with(prefix) {
1886 match prop {
1887 RegisteredTestSuiteProperty::Tag { tag, .. } => {
1888 test.props.tags.push(tag.clone());
1889 }
1890 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
1891 if test.props.timeout.is_none() {
1892 test.props.timeout = Some(*timeout);
1893 }
1894 }
1895 RegisteredTestSuiteProperty::Sequential { .. } => {
1896 }
1898 }
1899 }
1900 }
1901 result.push(test);
1902 }
1903 result
1904}
1905
1906pub(crate) fn filter_registered_tests(
1907 args: &Arguments,
1908 registered_tests: &[RegisteredTest],
1909) -> Vec<RegisteredTest> {
1910 registered_tests
1911 .iter()
1912 .filter(|registered_test| {
1913 !args
1914 .skip
1915 .iter()
1916 .any(|skip| filter_test(registered_test, skip, args.exact))
1917 })
1918 .filter(|registered_test| {
1919 args.filter.is_empty()
1920 || args
1921 .filter
1922 .iter()
1923 .any(|filter| filter_test(registered_test, filter, args.exact))
1924 })
1925 .filter(|registered_tests| {
1926 (args.bench && registered_tests.run.is_bench())
1927 || (args.test && !registered_tests.run.is_bench())
1928 || (!args.bench && !args.test)
1929 })
1930 .filter(|registered_test| {
1931 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
1932 })
1933 .cloned()
1934 .collect::<Vec<_>>()
1935}
1936
1937fn add_generated_tests(
1938 target: &mut Vec<RegisteredTest>,
1939 generator: &RegisteredTestGenerator,
1940 generated: Vec<GeneratedTest>,
1941) {
1942 target.extend(generated.into_iter().map(|mut test| {
1943 test.props.is_ignored |= generator.is_ignored;
1944 RegisteredTest {
1945 name: format!("{}::{}", generator.name, test.name),
1946 crate_name: generator.crate_name.clone(),
1947 module_path: generator.module_path.clone(),
1948 run: test.run,
1949 props: test.props,
1950 dependencies: test.dependencies,
1951 }
1952 }));
1953}
1954
1955#[cfg(feature = "tokio")]
1956pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1957 let mut result = Vec::new();
1958 for generator in generators {
1959 match &generator.run {
1960 TestGeneratorFunction::Sync(generator_fn) => {
1961 let tests = generator_fn();
1962 add_generated_tests(&mut result, generator, tests);
1963 }
1964 TestGeneratorFunction::Async(generator_fn) => {
1965 let tests = generator_fn().await;
1966 add_generated_tests(&mut result, generator, tests);
1967 }
1968 }
1969 }
1970 result
1971}
1972
1973pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1974 let mut result = Vec::new();
1975 for generator in generators {
1976 match &generator.run {
1977 TestGeneratorFunction::Sync(generator_fn) => {
1978 let tests = generator_fn();
1979 add_generated_tests(&mut result, generator, tests);
1980 }
1981 TestGeneratorFunction::Async(_) => {
1982 panic!("Async test generators are not supported in sync mode")
1983 }
1984 }
1985 }
1986 result
1987}
1988
1989pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
1990 let should_ensure_time = match test.props.ensure_time_control {
1991 ReportTimeControl::Default => args.ensure_time,
1992 ReportTimeControl::Enabled => true,
1993 ReportTimeControl::Disabled => false,
1994 };
1995 if should_ensure_time {
1996 match test.props.test_type {
1997 TestType::UnitTest => Some(args.unit_test_threshold()),
1998 TestType::IntegrationTest => Some(args.integration_test_threshold()),
1999 }
2000 } else {
2001 None
2002 }
2003}
2004
2005#[derive(Clone)]
2006pub enum TestResult {
2007 Passed {
2008 captured: Vec<CapturedOutput>,
2009 exec_time: Duration,
2010 },
2011 Benchmarked {
2012 captured: Vec<CapturedOutput>,
2013 exec_time: Duration,
2014 ns_iter_summ: Summary,
2015 mb_s: usize,
2016 },
2017 Failed {
2018 cause: FailureCause,
2019 captured: Vec<CapturedOutput>,
2020 exec_time: Duration,
2021 },
2022 Ignored {
2023 captured: Vec<CapturedOutput>,
2024 },
2025}
2026
2027impl TestResult {
2028 pub fn passed(exec_time: Duration) -> Self {
2029 TestResult::Passed {
2030 captured: Vec::new(),
2031 exec_time,
2032 }
2033 }
2034
2035 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
2036 TestResult::Benchmarked {
2037 captured: Vec::new(),
2038 exec_time,
2039 ns_iter_summ,
2040 mb_s,
2041 }
2042 }
2043
2044 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
2045 TestResult::Failed {
2046 cause,
2047 captured: Vec::new(),
2048 exec_time,
2049 }
2050 }
2051
2052 pub fn ignored() -> Self {
2053 TestResult::Ignored {
2054 captured: Vec::new(),
2055 }
2056 }
2057
2058 pub(crate) fn is_passed(&self) -> bool {
2059 matches!(self, TestResult::Passed { .. })
2060 }
2061
2062 pub(crate) fn is_benchmarked(&self) -> bool {
2063 matches!(self, TestResult::Benchmarked { .. })
2064 }
2065
2066 pub(crate) fn is_failed(&self) -> bool {
2067 matches!(self, TestResult::Failed { .. })
2068 }
2069
2070 pub(crate) fn is_ignored(&self) -> bool {
2071 matches!(self, TestResult::Ignored { .. })
2072 }
2073
2074 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
2075 match self {
2076 TestResult::Passed { captured, .. } => captured,
2077 TestResult::Failed { captured, .. } => captured,
2078 TestResult::Ignored { captured, .. } => captured,
2079 TestResult::Benchmarked { captured, .. } => captured,
2080 }
2081 }
2082
2083 pub(crate) fn stats(&self) -> Option<&Summary> {
2084 match self {
2085 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
2086 _ => None,
2087 }
2088 }
2089
2090 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
2091 match self {
2092 TestResult::Passed {
2093 captured: captured_ref,
2094 ..
2095 } => *captured_ref = captured,
2096 TestResult::Failed {
2097 captured: captured_ref,
2098 ..
2099 } => *captured_ref = captured,
2100 TestResult::Ignored {
2101 captured: captured_ref,
2102 } => *captured_ref = captured,
2103 TestResult::Benchmarked {
2104 captured: captured_ref,
2105 ..
2106 } => *captured_ref = captured,
2107 }
2108 }
2109
2110 pub(crate) fn from_result<A>(
2111 should_panic: &ShouldPanic,
2112 elapsed: Duration,
2113 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
2114 ) -> Self {
2115 match result {
2116 Ok(Ok(_)) => {
2117 if should_panic == &ShouldPanic::No {
2118 TestResult::passed(elapsed)
2119 } else {
2120 TestResult::failed(
2121 elapsed,
2122 FailureCause::HarnessError("Test did not panic as expected".to_string()),
2123 )
2124 }
2125 }
2126 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
2127 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
2128 }
2129 }
2130
2131 pub(crate) fn from_summary(
2132 should_panic: &ShouldPanic,
2133 elapsed: Duration,
2134 result: Result<Summary, Box<dyn Any + Send>>,
2135 bytes: u64,
2136 ) -> Self {
2137 match result {
2138 Ok(summary) => {
2139 let ns_iter = max(summary.median as u64, 1);
2140 let mb_s = bytes * 1000 / ns_iter;
2141 TestResult::benchmarked(elapsed, summary, mb_s as usize)
2142 }
2143 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
2144 }
2145 }
2146
2147 fn from_panic(
2148 should_panic: &ShouldPanic,
2149 elapsed: Duration,
2150 panic: Box<dyn Any + Send>,
2151 ) -> Self {
2152 let captured = crate::panic_hook::take_current_panic_capture();
2153
2154 let panic_cause = if let Some(cause) = captured {
2155 cause
2156 } else {
2157 let message = panic
2158 .downcast_ref::<String>()
2159 .cloned()
2160 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
2161 PanicCause {
2162 message,
2163 location: None,
2164 backtrace: None,
2165 }
2166 };
2167
2168 match should_panic {
2169 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
2170 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
2171 _ => TestResult::failed(
2172 elapsed,
2173 FailureCause::Panic(PanicCause {
2174 message: Some(format!(
2175 "Test panicked with unexpected message: {}",
2176 panic_cause.message.as_deref().unwrap_or_default()
2177 )),
2178 location: None,
2179 backtrace: None,
2180 }),
2181 ),
2182 },
2183 ShouldPanic::Yes => TestResult::passed(elapsed),
2184 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
2185 }
2186 }
2187
2188 pub(crate) fn failure_message(&self) -> Option<String> {
2189 self.failure_cause().map(|c| c.render())
2190 }
2191
2192 pub fn failure_cause(&self) -> Option<&FailureCause> {
2193 match self {
2194 TestResult::Failed { cause, .. } => Some(cause),
2195 _ => None,
2196 }
2197 }
2198}
2199
2200pub struct SuiteResult {
2201 pub passed: usize,
2202 pub failed: usize,
2203 pub ignored: usize,
2204 pub measured: usize,
2205 pub filtered_out: usize,
2206 pub exec_time: Duration,
2207}
2208
2209impl SuiteResult {
2210 pub fn from_test_results(
2211 registered_tests: &[RegisteredTest],
2212 results: &[(RegisteredTest, TestResult)],
2213 exec_time: Duration,
2214 ) -> Self {
2215 let passed = results
2216 .iter()
2217 .filter(|(_, result)| result.is_passed())
2218 .count();
2219 let measured = results
2220 .iter()
2221 .filter(|(_, result)| result.is_benchmarked())
2222 .count();
2223 let failed = results
2224 .iter()
2225 .filter(|(_, result)| result.is_failed())
2226 .count();
2227 let ignored = results
2228 .iter()
2229 .filter(|(_, result)| result.is_ignored())
2230 .count();
2231 let filtered_out = registered_tests.len() - results.len();
2232
2233 Self {
2234 passed,
2235 failed,
2236 ignored,
2237 measured,
2238 filtered_out,
2239 exec_time,
2240 }
2241 }
2242
2243 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
2244 if results.iter().any(|(_, result)| result.is_failed()) {
2245 ExitCode::from(101)
2246 } else {
2247 ExitCode::SUCCESS
2248 }
2249 }
2250}
2251
2252pub trait DependencyView: Debug {
2253 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
2254}
2255
2256impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
2257 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
2258 self.as_ref().get(name)
2259 }
2260}
2261
2262#[derive(Debug, Clone, Eq, PartialEq)]
2263pub enum CapturedOutput {
2264 Stdout { timestamp: SystemTime, line: String },
2265 Stderr { timestamp: SystemTime, line: String },
2266}
2267
2268impl CapturedOutput {
2269 pub fn stdout(line: String) -> Self {
2270 CapturedOutput::Stdout {
2271 timestamp: SystemTime::now(),
2272 line,
2273 }
2274 }
2275
2276 pub fn stderr(line: String) -> Self {
2277 CapturedOutput::Stderr {
2278 timestamp: SystemTime::now(),
2279 line,
2280 }
2281 }
2282
2283 pub fn timestamp(&self) -> SystemTime {
2284 match self {
2285 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
2286 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
2287 }
2288 }
2289
2290 pub fn line(&self) -> &str {
2291 match self {
2292 CapturedOutput::Stdout { line, .. } => line,
2293 CapturedOutput::Stderr { line, .. } => line,
2294 }
2295 }
2296}
2297
2298impl PartialOrd for CapturedOutput {
2299 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2300 Some(self.cmp(other))
2301 }
2302}
2303
2304impl Ord for CapturedOutput {
2305 fn cmp(&self, other: &Self) -> Ordering {
2306 self.timestamp().cmp(&other.timestamp())
2307 }
2308}
2309
2310#[cfg(test)]
2311mod error_reporting_tests {
2312 use super::*;
2313 use std::panic::{catch_unwind, AssertUnwindSafe};
2314 use std::time::Duration;
2315
2316 fn simulate_runner(
2317 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
2318 ) -> TestResult {
2319 crate::panic_hook::install_panic_hook();
2320 let test_id = crate::panic_hook::next_test_id();
2321 crate::panic_hook::set_current_test_id(test_id);
2322 let result = catch_unwind(AssertUnwindSafe(move || {
2323 let ret = test_fn();
2324 ret.into_result()?;
2325 Ok(())
2326 }));
2327 let test_result =
2328 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
2329 crate::panic_hook::clear_current_test_id();
2330 test_result
2331 }
2332
2333 #[test]
2334 fn panic_with_assert_eq() {
2335 let result = simulate_runner(|| {
2336 assert_eq!(1, 2);
2337 Box::new(())
2338 });
2339 assert!(result.is_failed());
2340 let msg = result.failure_message().unwrap();
2341 println!("=== panic assert_eq failure message ===\n{msg}\n===");
2342 assert!(
2343 msg.contains("assertion `left == right` failed"),
2344 "Expected assertion message, got: {msg}"
2345 );
2346 assert!(
2347 msg.contains("at "),
2348 "Expected location info in message, got: {msg}"
2349 );
2350 }
2351
2352 #[test]
2353 fn string_error() {
2354 let result = simulate_runner(|| {
2355 let r: Result<(), String> = Err("something went wrong".to_string());
2356 Box::new(r)
2357 });
2358 assert!(result.is_failed());
2359 let msg = result.failure_message().unwrap();
2360 println!("=== string error failure message ===\n{msg}\n===");
2361 assert_eq!(msg, "something went wrong");
2362 }
2363
2364 #[test]
2365 fn anyhow_error() {
2366 let result = simulate_runner(|| {
2367 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
2368 let err = anyhow::anyhow!(inner).context("operation failed");
2369 let r: Result<(), anyhow::Error> = Err(err);
2370 Box::new(r)
2371 });
2372 assert!(result.is_failed());
2373 let msg = result.failure_message().unwrap();
2374 println!("=== anyhow error failure message ===\n{msg}\n===");
2375 assert!(
2376 msg.contains("operation failed"),
2377 "Expected 'operation failed', got: {msg}"
2378 );
2379 assert!(
2380 msg.contains("file not found"),
2381 "Expected 'file not found', got: {msg}"
2382 );
2383 }
2384
2385 #[test]
2386 fn std_io_error() {
2387 let result = simulate_runner(|| {
2388 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
2389 std::io::ErrorKind::NotFound,
2390 "file not found",
2391 ));
2392 Box::new(r)
2393 });
2394 assert!(result.is_failed());
2395 let msg = result.failure_message().unwrap();
2396 println!("=== std io error failure message ===\n{msg}\n===");
2397 assert_eq!(msg, "file not found");
2399 }
2400
2401 #[test]
2402 fn panic_with_location_info() {
2403 let result = simulate_runner(|| {
2404 panic!("test panic with location");
2405 #[allow(unreachable_code)]
2406 Box::new(())
2407 });
2408 assert!(result.is_failed());
2409 let cause = result.failure_cause().unwrap();
2410 match cause {
2411 FailureCause::Panic(p) => {
2412 assert!(p.location.is_some(), "Expected location info");
2413 let loc = p.location.as_ref().unwrap();
2414 assert!(
2415 loc.file.contains("internal.rs"),
2416 "Expected file to contain internal.rs, got: {}",
2417 loc.file
2418 );
2419 assert!(loc.line > 0, "Expected non-zero line number");
2420 }
2421 other => panic!("Expected Panic cause, got: {other:?}"),
2422 }
2423 }
2424
2425 #[test]
2426 fn panic_render_includes_location() {
2427 let result = simulate_runner(|| {
2428 panic!("location test");
2429 #[allow(unreachable_code)]
2430 Box::new(())
2431 });
2432 let msg = result.failure_message().unwrap();
2433 assert!(
2434 msg.contains("location test"),
2435 "Expected panic message, got: {msg}"
2436 );
2437 assert!(
2438 msg.contains("\n at "),
2439 "Expected location line in render, got: {msg}"
2440 );
2441 }
2442
2443 #[test]
2444 fn should_panic_with_message_matching() {
2445 crate::panic_hook::install_panic_hook();
2446 let test_id = crate::panic_hook::next_test_id();
2447 crate::panic_hook::set_current_test_id(test_id);
2448 let result = catch_unwind(AssertUnwindSafe(|| {
2449 panic!("expected panic message");
2450 }));
2451 let test_result = TestResult::from_result(
2452 &ShouldPanic::WithMessage("expected panic".to_string()),
2453 Duration::from_millis(1),
2454 result.map(|_| Ok(())),
2455 );
2456 crate::panic_hook::clear_current_test_id();
2457 assert!(
2458 test_result.is_passed(),
2459 "Expected test to pass with matching panic message"
2460 );
2461 }
2462
2463 #[test]
2464 fn should_panic_with_wrong_message() {
2465 crate::panic_hook::install_panic_hook();
2466 let test_id = crate::panic_hook::next_test_id();
2467 crate::panic_hook::set_current_test_id(test_id);
2468 let result = catch_unwind(AssertUnwindSafe(|| {
2469 panic!("actual panic message");
2470 }));
2471 let test_result = TestResult::from_result(
2472 &ShouldPanic::WithMessage("completely different".to_string()),
2473 Duration::from_millis(1),
2474 result.map(|_| Ok(())),
2475 );
2476 crate::panic_hook::clear_current_test_id();
2477 assert!(
2478 test_result.is_failed(),
2479 "Expected test to fail with wrong panic message"
2480 );
2481 let msg = test_result.failure_message().unwrap();
2482 assert!(
2483 msg.contains("unexpected message"),
2484 "Expected 'unexpected message' in: {msg}"
2485 );
2486 }
2487
2488 #[test]
2489 fn pretty_assertions_diff() {
2490 let result = simulate_runner(|| {
2491 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
2492 Box::new(())
2493 });
2494 assert!(result.is_failed());
2495 let cause = result.failure_cause().unwrap();
2496
2497 let panic_cause = match cause {
2499 FailureCause::Panic(p) => p,
2500 other => panic!("Expected Panic cause, got: {other:?}"),
2501 };
2502
2503 let message = panic_cause.message.as_deref().unwrap();
2505 println!("=== pretty_assertions failure message ===\n{message}\n===");
2506 assert!(
2507 message.contains("foo") && message.contains("baz"),
2508 "Expected diff with 'foo' and 'baz', got: {message}"
2509 );
2510
2511 assert!(panic_cause.location.is_some(), "Expected location info");
2513
2514 let rendered = cause.render();
2516 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
2517 assert!(
2518 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
2519 "Expected no backtrace noise in rendered output, got: {rendered}"
2520 );
2521 assert!(
2523 rendered.contains("\n at "),
2524 "Expected location in rendered output, got: {rendered}"
2525 );
2526 }
2527
2528 #[test]
2529 fn detached_thread_panic_detected() {
2530 crate::panic_hook::install_panic_hook();
2531 let test_id = crate::panic_hook::next_test_id();
2532 crate::panic_hook::set_current_test_id(test_id);
2533 crate::panic_hook::create_detached_collector(test_id);
2534
2535 let result = catch_unwind(AssertUnwindSafe(|| {
2536 let handle = crate::spawn::spawn_thread(|| {
2537 panic!("background thread panic");
2538 });
2539 let _ = handle.join();
2540 }));
2541
2542 let mut test_result = TestResult::from_result(
2543 &ShouldPanic::No,
2544 Duration::from_millis(1),
2545 result.map(|_| Ok(())),
2546 );
2547
2548 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2549 let panics = match collector.lock() {
2550 Ok(p) => p,
2551 Err(poisoned) => poisoned.into_inner(),
2552 };
2553 if !panics.is_empty() && test_result.is_passed() {
2554 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
2555 test_result = TestResult::failed(
2556 Duration::from_millis(1),
2557 FailureCause::Panic(PanicCause {
2558 message: Some(format!(
2559 "Detached task(s) panicked:\n{}",
2560 messages.join("\n---\n")
2561 )),
2562 location: panics.first().and_then(|p| p.location.clone()),
2563 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
2564 }),
2565 );
2566 }
2567 }
2568
2569 crate::panic_hook::clear_current_test_id();
2570
2571 assert!(
2572 test_result.is_failed(),
2573 "Expected test to fail due to detached panic"
2574 );
2575 let msg = test_result.failure_message().unwrap();
2576 assert!(
2577 msg.contains("Detached task(s) panicked"),
2578 "Expected detached panic message, got: {msg}"
2579 );
2580 assert!(
2581 msg.contains("background thread panic"),
2582 "Expected original panic message, got: {msg}"
2583 );
2584 }
2585
2586 #[test]
2587 fn detached_thread_panic_ignored_with_policy() {
2588 crate::panic_hook::install_panic_hook();
2589 let test_id = crate::panic_hook::next_test_id();
2590 crate::panic_hook::set_current_test_id(test_id);
2591 crate::panic_hook::create_detached_collector(test_id);
2592
2593 let result = catch_unwind(AssertUnwindSafe(|| {
2594 let handle = crate::spawn::spawn_thread(|| {
2595 panic!("ignored thread panic");
2596 });
2597 let _ = handle.join();
2598 }));
2599
2600 let test_result = TestResult::from_result(
2601 &ShouldPanic::No,
2602 Duration::from_millis(1),
2603 result.map(|_| Ok(())),
2604 );
2605
2606 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2607 let panics = match collector.lock() {
2608 Ok(p) => p,
2609 Err(poisoned) => poisoned.into_inner(),
2610 };
2611 assert!(
2613 !panics.is_empty(),
2614 "Expected panics in collector even with Ignore policy"
2615 );
2616 }
2617
2618 crate::panic_hook::clear_current_test_id();
2619
2620 assert!(
2621 test_result.is_passed(),
2622 "Expected test to pass with Ignore policy"
2623 );
2624 }
2625
2626 #[cfg(feature = "tokio")]
2627 #[test]
2628 fn detached_task_panic_detected() {
2629 let rt = tokio::runtime::Runtime::new().unwrap();
2630 rt.block_on(async {
2631 crate::panic_hook::install_panic_hook();
2632 let test_id = crate::panic_hook::next_test_id();
2633 crate::panic_hook::set_current_test_id(test_id);
2634 crate::panic_hook::create_detached_collector(test_id);
2635
2636 let handle = crate::spawn::spawn(async {
2637 panic!("detached task panic");
2638 });
2639 let _ = handle.await;
2640
2641 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
2642 let panics = collector.lock().unwrap();
2643
2644 assert_eq!(panics.len(), 1);
2645 assert!(
2646 panics[0]
2647 .message
2648 .as_ref()
2649 .unwrap()
2650 .contains("detached task panic"),
2651 "Expected panic message, got: {:?}",
2652 panics[0].message
2653 );
2654
2655 crate::panic_hook::clear_current_test_id();
2656 });
2657 }
2658
2659 #[test]
2660 fn failure_cause_variants() {
2661 let cause = FailureCause::ReturnedMessage("simple message".to_string());
2663 assert_eq!(cause.render(), "simple message");
2664 assert!(cause.panic_message().is_none());
2665
2666 let cause = FailureCause::ReturnedError {
2668 display: "display text".to_string(),
2669 debug: "debug text".to_string(),
2670 prefer_debug: false,
2671 error: Arc::new("display text".to_string()),
2672 };
2673 assert_eq!(cause.render(), "display text");
2674
2675 let cause = FailureCause::ReturnedError {
2677 display: "display text".to_string(),
2678 debug: "debug text".to_string(),
2679 prefer_debug: true,
2680 error: Arc::new("debug text".to_string()),
2681 };
2682 assert_eq!(cause.render(), "debug text");
2683
2684 let cause = FailureCause::HarnessError("harness error".to_string());
2686 assert_eq!(cause.render(), "harness error");
2687
2688 let cause = FailureCause::Panic(PanicCause {
2690 message: Some("panic msg".to_string()),
2691 location: None,
2692 backtrace: None,
2693 });
2694 assert_eq!(cause.render(), "panic msg");
2695 assert_eq!(cause.panic_message(), Some("panic msg"));
2696 }
2697}
2698
2699#[cfg(test)]
2700mod filter_tests {
2701 use super::*;
2702
2703 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
2704 RegisteredTest {
2705 name: name.to_string(),
2706 crate_name: "mycrate".to_string(),
2707 module_path: module_path.to_string(),
2708 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
2709 props: TestProperties::default(),
2710 dependencies: None,
2711 }
2712 }
2713
2714 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
2715 let mut test = make_test(name, module_path);
2716 test.props.tags = tags.into_iter().map(String::from).collect();
2717 test
2718 }
2719
2720 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
2721 Arguments {
2722 filter: filters.into_iter().map(String::from).collect(),
2723 skip: skip.into_iter().map(String::from).collect(),
2724 exact,
2725 ..Default::default()
2726 }
2727 }
2728
2729 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
2730 filter_registered_tests(args, tests)
2731 .into_iter()
2732 .map(|t| t.filterable_name())
2733 .collect()
2734 }
2735
2736 #[test]
2739 fn filter_test_substring_match() {
2740 let test = make_test("hello_world", "mod1");
2741 assert!(filter_test(&test, "hello", false));
2742 assert!(filter_test(&test, "world", false));
2743 assert!(filter_test(&test, "mod1::hello", false));
2744 assert!(!filter_test(&test, "nonexistent", false));
2745 }
2746
2747 #[test]
2748 fn filter_test_exact_match() {
2749 let test = make_test("hello_world", "mod1");
2750 assert!(filter_test(&test, "mod1::hello_world", true));
2751 assert!(!filter_test(&test, "hello_world", true));
2752 assert!(!filter_test(&test, "hello", true));
2753 }
2754
2755 #[test]
2756 fn filter_test_tag_match() {
2757 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
2758 assert!(filter_test(&test, ":tag:fast", false));
2759 assert!(filter_test(&test, ":tag:unit", false));
2760 assert!(!filter_test(&test, ":tag:slow", false));
2761 }
2762
2763 #[test]
2764 fn filter_test_tag_empty_matches_untagged() {
2765 let untagged = make_test("t1", "mod1");
2766 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
2767 assert!(filter_test(&untagged, ":tag:", false));
2768 assert!(!filter_test(&tagged, ":tag:", false));
2769 }
2770
2771 #[test]
2774 fn no_filters_includes_all() {
2775 let tests = vec![make_test("a", "m"), make_test("b", "m")];
2776 let args = make_args(vec![], vec![], false);
2777 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
2778 }
2779
2780 #[test]
2781 fn single_filter_substring() {
2782 let tests = vec![
2783 make_test("alpha", "m"),
2784 make_test("beta", "m"),
2785 make_test("alphabet", "m"),
2786 ];
2787 let args = make_args(vec!["alpha"], vec![], false);
2788 assert_eq!(
2789 filtered_names(&args, &tests),
2790 vec!["m::alpha", "m::alphabet"]
2791 );
2792 }
2793
2794 #[test]
2795 fn multiple_filters_or_semantics() {
2796 let tests = vec![
2797 make_test("alpha", "m"),
2798 make_test("beta", "m"),
2799 make_test("gamma", "m"),
2800 ];
2801 let args = make_args(vec!["alpha", "gamma"], vec![], false);
2802 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
2803 }
2804
2805 #[test]
2806 fn multiple_filters_exact() {
2807 let tests = vec![
2808 make_test("alpha", "m"),
2809 make_test("alphabet", "m"),
2810 make_test("beta", "m"),
2811 ];
2812 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
2813 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
2814 }
2815
2816 #[test]
2819 fn skip_substring_match() {
2820 let tests = vec![
2821 make_test("fast_test", "m"),
2822 make_test("slow_test", "m"),
2823 make_test("slower_test", "m"),
2824 ];
2825 let args = make_args(vec![], vec!["slow"], false);
2826 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
2827 }
2828
2829 #[test]
2830 fn skip_exact_match() {
2831 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
2832 let args = make_args(vec![], vec!["m::slow_test"], true);
2833 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
2834 }
2835
2836 #[test]
2837 fn skip_with_tag() {
2838 let tests = vec![
2839 make_tagged_test("t1", "m", vec!["slow"]),
2840 make_tagged_test("t2", "m", vec!["fast"]),
2841 make_test("t3", "m"),
2842 ];
2843 let args = make_args(vec![], vec![":tag:slow"], false);
2844 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
2845 }
2846
2847 #[test]
2850 fn include_and_skip_combined() {
2851 let tests = vec![
2852 make_test("alpha_fast", "m"),
2853 make_test("alpha_slow", "m"),
2854 make_test("beta_fast", "m"),
2855 ];
2856 let args = make_args(vec!["alpha"], vec!["slow"], false);
2858 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
2859 }
2860
2861 #[test]
2862 fn skip_wins_over_include() {
2863 let tests = vec![make_test("target", "m")];
2864 let args = make_args(vec!["target"], vec!["target"], false);
2866 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
2867 }
2868
2869 #[test]
2872 fn filter_test_tag_or_expression() {
2873 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2875 let test_b = make_tagged_test("t2", "m", vec!["b"]);
2876 let test_c = make_tagged_test("t3", "m", vec!["c"]);
2877 assert!(filter_test(&test_a, ":tag:a|b", false));
2878 assert!(filter_test(&test_b, ":tag:a|b", false));
2879 assert!(!filter_test(&test_c, ":tag:a|b", false));
2880 }
2881
2882 #[test]
2883 fn filter_test_tag_and_expression() {
2884 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
2886 let test_a = make_tagged_test("t2", "m", vec!["a"]);
2887 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2888 assert!(filter_test(&test_ab, ":tag:a&b", false));
2889 assert!(!filter_test(&test_a, ":tag:a&b", false));
2890 assert!(!filter_test(&test_b, ":tag:a&b", false));
2891 }
2892
2893 #[test]
2894 fn filter_test_tag_mixed_and_or() {
2895 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2897 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
2898 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2899 let test_c = make_tagged_test("t4", "m", vec!["c"]);
2900 let test_none = make_test("t5", "m");
2901 assert!(filter_test(&test_a, ":tag:a|b&c", false));
2902 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
2903 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
2904 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
2905 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
2906 }
2907
2908 #[test]
2909 fn filter_test_tag_exact_flag_does_not_affect_tags() {
2910 let test = make_tagged_test("t1", "m", vec!["fast"]);
2912 assert!(filter_test(&test, ":tag:fast", true));
2913 assert!(!filter_test(&test, ":tag:slow", true));
2914 }
2915
2916 #[test]
2917 fn include_by_tag_or_expression() {
2918 let tests = vec![
2919 make_tagged_test("t1", "m", vec!["unit"]),
2920 make_tagged_test("t2", "m", vec!["integration"]),
2921 make_tagged_test("t3", "m", vec!["e2e"]),
2922 ];
2923 let args = make_args(vec![":tag:unit|integration"], vec![], false);
2924 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
2925 }
2926
2927 #[test]
2928 fn skip_by_tag_and_expression() {
2929 let tests = vec![
2930 make_tagged_test("t1", "m", vec!["slow", "network"]),
2931 make_tagged_test("t2", "m", vec!["slow"]),
2932 make_tagged_test("t3", "m", vec!["network"]),
2933 make_test("t4", "m"),
2934 ];
2935 let args = make_args(vec![], vec![":tag:slow&network"], false);
2937 assert_eq!(
2938 filtered_names(&args, &tests),
2939 vec!["m::t2", "m::t3", "m::t4"]
2940 );
2941 }
2942}