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)]
1686pub struct MatrixCase {
1687 pub case_label: String,
1688 pub dep_name: String,
1689 pub auto_tag: String,
1690}
1691
1692#[derive(Debug, Clone)]
1693pub enum RegisteredTestSuiteProperty {
1694 Sequential {
1695 name: String,
1696 crate_name: String,
1697 module_path: String,
1698 },
1699 Tag {
1700 name: String,
1701 crate_name: String,
1702 module_path: String,
1703 tag: String,
1704 },
1705 Timeout {
1706 name: String,
1707 crate_name: String,
1708 module_path: String,
1709 timeout: Duration,
1710 },
1711 Matrix {
1718 name: String,
1719 crate_name: String,
1720 module_path: String,
1721 dep_name: String,
1722 cases: Vec<MatrixCase>,
1723 },
1724}
1725
1726impl RegisteredTestSuiteProperty {
1727 pub fn crate_name(&self) -> &String {
1728 match self {
1729 RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
1730 RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
1731 RegisteredTestSuiteProperty::Timeout { crate_name, .. } => crate_name,
1732 RegisteredTestSuiteProperty::Matrix { crate_name, .. } => crate_name,
1733 }
1734 }
1735
1736 pub fn module_path(&self) -> &String {
1737 match self {
1738 RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
1739 RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
1740 RegisteredTestSuiteProperty::Timeout { module_path, .. } => module_path,
1741 RegisteredTestSuiteProperty::Matrix { module_path, .. } => module_path,
1742 }
1743 }
1744
1745 pub fn name(&self) -> &String {
1746 match self {
1747 RegisteredTestSuiteProperty::Sequential { name, .. } => name,
1748 RegisteredTestSuiteProperty::Tag { name, .. } => name,
1749 RegisteredTestSuiteProperty::Timeout { name, .. } => name,
1750 RegisteredTestSuiteProperty::Matrix { name, .. } => name,
1751 }
1752 }
1753
1754 pub fn crate_and_module(&self) -> String {
1755 [self.crate_name(), self.module_path(), self.name()]
1756 .into_iter()
1757 .filter(|s| !s.is_empty())
1758 .cloned()
1759 .collect::<Vec<String>>()
1760 .join("::")
1761 }
1762}
1763
1764pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
1765 Mutex::new(Vec::new());
1766
1767#[derive(Clone)]
1768#[allow(clippy::type_complexity)]
1769pub enum TestGeneratorFunction {
1770 Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
1771 Async(
1772 Arc<
1773 dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
1774 + Send
1775 + Sync
1776 + 'static,
1777 >,
1778 ),
1779}
1780
1781pub struct DynamicTestRegistration {
1782 tests: Vec<GeneratedTest>,
1783}
1784
1785impl Default for DynamicTestRegistration {
1786 fn default() -> Self {
1787 Self::new()
1788 }
1789}
1790
1791impl DynamicTestRegistration {
1792 pub fn new() -> Self {
1793 Self { tests: Vec::new() }
1794 }
1795
1796 pub fn to_vec(self) -> Vec<GeneratedTest> {
1797 self.tests
1798 }
1799
1800 pub fn add_sync_test<R: TestReturnValue + 'static>(
1801 &mut self,
1802 name: impl AsRef<str>,
1803 props: TestProperties,
1804 dependencies: Option<Vec<String>>,
1805 run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
1806 ) {
1807 self.tests.push(GeneratedTest {
1808 name: name.as_ref().to_string(),
1809 run: TestFunction::Sync(Arc::new(move |deps| {
1810 Box::new(run(deps)) as Box<dyn TestReturnValue>
1811 })),
1812 props,
1813 dependencies,
1814 });
1815 }
1816
1817 #[cfg(feature = "tokio")]
1818 pub fn add_async_test<R: TestReturnValue + 'static>(
1819 &mut self,
1820 name: impl AsRef<str>,
1821 props: TestProperties,
1822 dependencies: Option<Vec<String>>,
1823 run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
1824 + Send
1825 + Sync
1826 + Clone
1827 + 'static,
1828 ) {
1829 self.tests.push(GeneratedTest {
1830 name: name.as_ref().to_string(),
1831 run: TestFunction::Async(Arc::new(move |deps| {
1832 let run = run.clone();
1833 Box::pin(async move {
1834 let r = run(deps).await;
1835 Box::new(r) as Box<dyn TestReturnValue>
1836 })
1837 })),
1838 props,
1839 dependencies,
1840 });
1841 }
1842}
1843
1844#[derive(Clone)]
1845pub struct GeneratedTest {
1846 pub name: String,
1847 pub run: TestFunction,
1848 pub props: TestProperties,
1849 pub dependencies: Option<Vec<String>>,
1850}
1851
1852#[derive(Clone)]
1853pub struct RegisteredTestGenerator {
1854 pub name: String,
1855 pub crate_name: String,
1856 pub module_path: String,
1857 pub run: TestGeneratorFunction,
1858 pub is_ignored: bool,
1859}
1860
1861impl RegisteredTestGenerator {
1862 pub fn crate_and_module(&self) -> String {
1863 [&self.crate_name, &self.module_path]
1864 .into_iter()
1865 .filter(|s| !s.is_empty())
1866 .cloned()
1867 .collect::<Vec<String>>()
1868 .join("::")
1869 }
1870}
1871
1872pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
1873
1874pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
1875 if let Some(tag_list) = filter.strip_prefix(":tag:") {
1876 if tag_list.is_empty() {
1877 test.props.tags.is_empty()
1879 } else {
1880 let or_tags = tag_list.split('|').collect::<Vec<&str>>();
1881 let mut result = false;
1882 for or_tag in or_tags {
1883 let and_tags = or_tag.split('&').collect::<Vec<&str>>();
1884 let mut and_result = true;
1885 for and_tag in and_tags {
1886 if !test.props.tags.contains(&and_tag.to_string()) {
1887 and_result = false;
1888 break;
1889 }
1890 }
1891 if and_result {
1892 result = true;
1893 break;
1894 }
1895 }
1896 result
1897 }
1898 } else if exact {
1899 test.filterable_name() == filter
1900 } else {
1901 test.filterable_name().contains(filter)
1902 }
1903}
1904
1905pub(crate) fn apply_suite_props_to_tests(
1906 tests: &[RegisteredTest],
1907 props: &[RegisteredTestSuiteProperty],
1908) -> Vec<RegisteredTest> {
1909 let props_with_prefix = props
1910 .iter()
1911 .map(|prop| (prop.crate_and_module(), prop))
1912 .collect::<Vec<_>>();
1913
1914 let mut result = Vec::new();
1915 for test in tests {
1916 let mut matrix_dims: Vec<&RegisteredTestSuiteProperty> = Vec::new();
1922 let mut tag_timeout_sequential: Vec<&RegisteredTestSuiteProperty> = Vec::new();
1923 for (prefix, prop) in &props_with_prefix {
1924 if test.crate_and_module().starts_with(prefix) {
1925 match prop {
1926 RegisteredTestSuiteProperty::Matrix { .. } => matrix_dims.push(prop),
1927 _ => tag_timeout_sequential.push(prop),
1928 }
1929 }
1930 }
1931
1932 let mut expanded: Vec<RegisteredTest> = vec![test.clone()];
1939 for dim_prop in &matrix_dims {
1940 let RegisteredTestSuiteProperty::Matrix {
1941 dep_name, cases, ..
1942 } = dim_prop
1943 else {
1944 unreachable!()
1945 };
1946 let mut next: Vec<RegisteredTest> = Vec::new();
1947 for t in expanded.iter().cloned() {
1948 let depends_on_dim = t.dependencies.iter().flatten().any(|d| d == dep_name);
1949 if depends_on_dim && !cases.is_empty() {
1950 for case in cases {
1951 next.push(multiply_test_for_case(&t, dep_name, case));
1952 }
1953 } else {
1954 next.push(t);
1955 }
1956 }
1957 expanded = next;
1958 }
1959
1960 for mut t in expanded {
1964 for prop in &tag_timeout_sequential {
1965 match prop {
1966 RegisteredTestSuiteProperty::Tag { tag, .. } => {
1967 t.props.tags.push(tag.clone());
1968 }
1969 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
1970 if t.props.timeout.is_none() {
1971 t.props.timeout = Some(*timeout);
1972 }
1973 }
1974 RegisteredTestSuiteProperty::Sequential { .. } => {}
1975 RegisteredTestSuiteProperty::Matrix { .. } => unreachable!(),
1976 }
1977 }
1978 result.push(t);
1979 }
1980 }
1981 result
1982}
1983
1984fn multiply_test_for_case(
1997 test: &RegisteredTest,
1998 dep_name: &str,
1999 case: &MatrixCase,
2000) -> RegisteredTest {
2001 let mut clone = test.clone();
2002 clone.name = format!("{}_{}", test.name, case.case_label);
2003 clone.props.tags.push(case.auto_tag.clone());
2004 if let Some(deps) = clone.dependencies.as_mut() {
2005 for d in deps.iter_mut() {
2006 if d == dep_name {
2007 *d = case.dep_name.clone();
2008 }
2009 }
2010 }
2011 clone.run = wrap_with_aliasing_view(
2012 test.run.clone(),
2013 dep_name.to_string(),
2014 case.dep_name.clone(),
2015 );
2016 clone
2017}
2018
2019fn wrap_with_aliasing_view(
2026 run: TestFunction,
2027 alias_from: String,
2028 alias_to: String,
2029) -> TestFunction {
2030 let make_view = move |deps: Arc<dyn DependencyView + Send + Sync>| {
2031 Arc::new(AliasedDependencyView {
2032 inner: deps,
2033 alias_from: alias_from.clone(),
2034 alias_to: alias_to.clone(),
2035 }) as Arc<dyn DependencyView + Send + Sync>
2036 };
2037 match run {
2038 TestFunction::Sync(f) => TestFunction::Sync(Arc::new(move |deps| {
2039 let aliased = make_view(deps);
2040 f(aliased)
2041 })),
2042 TestFunction::SyncBench(f) => TestFunction::SyncBench(Arc::new(move |b, deps| {
2043 let aliased = make_view(deps);
2044 f(b, aliased)
2045 })),
2046 #[cfg(feature = "tokio")]
2047 TestFunction::Async(f) => TestFunction::Async(Arc::new(move |deps| {
2048 let aliased = make_view(deps);
2049 f(aliased)
2050 })),
2051 #[cfg(feature = "tokio")]
2052 TestFunction::AsyncBench(f) => TestFunction::AsyncBench(Arc::new(move |b, deps| {
2053 let aliased = make_view(deps);
2054 f(b, aliased)
2055 })),
2056 }
2057}
2058
2059#[derive(Debug)]
2065struct AliasedDependencyView {
2066 inner: Arc<dyn DependencyView + Send + Sync>,
2067 alias_from: String,
2068 alias_to: String,
2069}
2070
2071impl DependencyView for AliasedDependencyView {
2072 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
2073 if name == self.alias_from {
2074 self.inner.get(&self.alias_to)
2075 } else {
2076 self.inner.get(name)
2077 }
2078 }
2079}
2080
2081pub(crate) fn filter_registered_tests(
2082 args: &Arguments,
2083 registered_tests: &[RegisteredTest],
2084) -> Vec<RegisteredTest> {
2085 registered_tests
2086 .iter()
2087 .filter(|registered_test| {
2088 !args
2089 .skip
2090 .iter()
2091 .any(|skip| filter_test(registered_test, skip, args.exact))
2092 })
2093 .filter(|registered_test| {
2094 args.filter.is_empty()
2095 || args
2096 .filter
2097 .iter()
2098 .any(|filter| filter_test(registered_test, filter, args.exact))
2099 })
2100 .filter(|registered_tests| {
2101 (args.bench && registered_tests.run.is_bench())
2102 || (args.test && !registered_tests.run.is_bench())
2103 || (!args.bench && !args.test)
2104 })
2105 .filter(|registered_test| {
2106 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
2107 })
2108 .cloned()
2109 .collect::<Vec<_>>()
2110}
2111
2112fn add_generated_tests(
2113 target: &mut Vec<RegisteredTest>,
2114 generator: &RegisteredTestGenerator,
2115 generated: Vec<GeneratedTest>,
2116) {
2117 target.extend(generated.into_iter().map(|mut test| {
2118 test.props.is_ignored |= generator.is_ignored;
2119 RegisteredTest {
2120 name: format!("{}::{}", generator.name, test.name),
2121 crate_name: generator.crate_name.clone(),
2122 module_path: generator.module_path.clone(),
2123 run: test.run,
2124 props: test.props,
2125 dependencies: test.dependencies,
2126 }
2127 }));
2128}
2129
2130#[cfg(feature = "tokio")]
2131pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
2132 let mut result = Vec::new();
2133 for generator in generators {
2134 match &generator.run {
2135 TestGeneratorFunction::Sync(generator_fn) => {
2136 let tests = generator_fn();
2137 add_generated_tests(&mut result, generator, tests);
2138 }
2139 TestGeneratorFunction::Async(generator_fn) => {
2140 let tests = generator_fn().await;
2141 add_generated_tests(&mut result, generator, tests);
2142 }
2143 }
2144 }
2145 result
2146}
2147
2148pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
2149 let mut result = Vec::new();
2150 for generator in generators {
2151 match &generator.run {
2152 TestGeneratorFunction::Sync(generator_fn) => {
2153 let tests = generator_fn();
2154 add_generated_tests(&mut result, generator, tests);
2155 }
2156 TestGeneratorFunction::Async(_) => {
2157 panic!("Async test generators are not supported in sync mode")
2158 }
2159 }
2160 }
2161 result
2162}
2163
2164pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
2165 let should_ensure_time = match test.props.ensure_time_control {
2166 ReportTimeControl::Default => args.ensure_time,
2167 ReportTimeControl::Enabled => true,
2168 ReportTimeControl::Disabled => false,
2169 };
2170 if should_ensure_time {
2171 match test.props.test_type {
2172 TestType::UnitTest => Some(args.unit_test_threshold()),
2173 TestType::IntegrationTest => Some(args.integration_test_threshold()),
2174 }
2175 } else {
2176 None
2177 }
2178}
2179
2180#[derive(Clone)]
2181pub enum TestResult {
2182 Passed {
2183 captured: Vec<CapturedOutput>,
2184 exec_time: Duration,
2185 },
2186 Benchmarked {
2187 captured: Vec<CapturedOutput>,
2188 exec_time: Duration,
2189 ns_iter_summ: Summary,
2190 mb_s: usize,
2191 },
2192 Failed {
2193 cause: FailureCause,
2194 captured: Vec<CapturedOutput>,
2195 exec_time: Duration,
2196 },
2197 Ignored {
2198 captured: Vec<CapturedOutput>,
2199 },
2200}
2201
2202impl TestResult {
2203 pub fn passed(exec_time: Duration) -> Self {
2204 TestResult::Passed {
2205 captured: Vec::new(),
2206 exec_time,
2207 }
2208 }
2209
2210 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
2211 TestResult::Benchmarked {
2212 captured: Vec::new(),
2213 exec_time,
2214 ns_iter_summ,
2215 mb_s,
2216 }
2217 }
2218
2219 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
2220 TestResult::Failed {
2221 cause,
2222 captured: Vec::new(),
2223 exec_time,
2224 }
2225 }
2226
2227 pub fn ignored() -> Self {
2228 TestResult::Ignored {
2229 captured: Vec::new(),
2230 }
2231 }
2232
2233 pub(crate) fn is_passed(&self) -> bool {
2234 matches!(self, TestResult::Passed { .. })
2235 }
2236
2237 pub(crate) fn is_benchmarked(&self) -> bool {
2238 matches!(self, TestResult::Benchmarked { .. })
2239 }
2240
2241 pub(crate) fn is_failed(&self) -> bool {
2242 matches!(self, TestResult::Failed { .. })
2243 }
2244
2245 pub(crate) fn is_ignored(&self) -> bool {
2246 matches!(self, TestResult::Ignored { .. })
2247 }
2248
2249 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
2250 match self {
2251 TestResult::Passed { captured, .. } => captured,
2252 TestResult::Failed { captured, .. } => captured,
2253 TestResult::Ignored { captured, .. } => captured,
2254 TestResult::Benchmarked { captured, .. } => captured,
2255 }
2256 }
2257
2258 pub(crate) fn stats(&self) -> Option<&Summary> {
2259 match self {
2260 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
2261 _ => None,
2262 }
2263 }
2264
2265 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
2266 match self {
2267 TestResult::Passed {
2268 captured: captured_ref,
2269 ..
2270 } => *captured_ref = captured,
2271 TestResult::Failed {
2272 captured: captured_ref,
2273 ..
2274 } => *captured_ref = captured,
2275 TestResult::Ignored {
2276 captured: captured_ref,
2277 } => *captured_ref = captured,
2278 TestResult::Benchmarked {
2279 captured: captured_ref,
2280 ..
2281 } => *captured_ref = captured,
2282 }
2283 }
2284
2285 pub(crate) fn from_result<A>(
2286 should_panic: &ShouldPanic,
2287 elapsed: Duration,
2288 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
2289 ) -> Self {
2290 match result {
2291 Ok(Ok(_)) => {
2292 if should_panic == &ShouldPanic::No {
2293 TestResult::passed(elapsed)
2294 } else {
2295 TestResult::failed(
2296 elapsed,
2297 FailureCause::HarnessError("Test did not panic as expected".to_string()),
2298 )
2299 }
2300 }
2301 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
2302 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
2303 }
2304 }
2305
2306 pub(crate) fn from_summary(
2307 should_panic: &ShouldPanic,
2308 elapsed: Duration,
2309 result: Result<Summary, Box<dyn Any + Send>>,
2310 bytes: u64,
2311 ) -> Self {
2312 match result {
2313 Ok(summary) => {
2314 let ns_iter = max(summary.median as u64, 1);
2315 let mb_s = bytes * 1000 / ns_iter;
2316 TestResult::benchmarked(elapsed, summary, mb_s as usize)
2317 }
2318 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
2319 }
2320 }
2321
2322 fn from_panic(
2323 should_panic: &ShouldPanic,
2324 elapsed: Duration,
2325 panic: Box<dyn Any + Send>,
2326 ) -> Self {
2327 let captured = crate::panic_hook::take_current_panic_capture();
2328
2329 let panic_cause = if let Some(cause) = captured {
2330 cause
2331 } else {
2332 let message = panic
2333 .downcast_ref::<String>()
2334 .cloned()
2335 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
2336 PanicCause {
2337 message,
2338 location: None,
2339 backtrace: None,
2340 }
2341 };
2342
2343 match should_panic {
2344 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
2345 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
2346 _ => TestResult::failed(
2347 elapsed,
2348 FailureCause::Panic(PanicCause {
2349 message: Some(format!(
2350 "Test panicked with unexpected message: {}",
2351 panic_cause.message.as_deref().unwrap_or_default()
2352 )),
2353 location: None,
2354 backtrace: None,
2355 }),
2356 ),
2357 },
2358 ShouldPanic::Yes => TestResult::passed(elapsed),
2359 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
2360 }
2361 }
2362
2363 pub(crate) fn failure_message(&self) -> Option<String> {
2364 self.failure_cause().map(|c| c.render())
2365 }
2366
2367 pub fn failure_cause(&self) -> Option<&FailureCause> {
2368 match self {
2369 TestResult::Failed { cause, .. } => Some(cause),
2370 _ => None,
2371 }
2372 }
2373}
2374
2375pub struct SuiteResult {
2376 pub passed: usize,
2377 pub failed: usize,
2378 pub ignored: usize,
2379 pub measured: usize,
2380 pub filtered_out: usize,
2381 pub exec_time: Duration,
2382}
2383
2384impl SuiteResult {
2385 pub fn from_test_results(
2386 registered_tests: &[RegisteredTest],
2387 results: &[(RegisteredTest, TestResult)],
2388 exec_time: Duration,
2389 ) -> Self {
2390 let passed = results
2391 .iter()
2392 .filter(|(_, result)| result.is_passed())
2393 .count();
2394 let measured = results
2395 .iter()
2396 .filter(|(_, result)| result.is_benchmarked())
2397 .count();
2398 let failed = results
2399 .iter()
2400 .filter(|(_, result)| result.is_failed())
2401 .count();
2402 let ignored = results
2403 .iter()
2404 .filter(|(_, result)| result.is_ignored())
2405 .count();
2406 let filtered_out = registered_tests.len() - results.len();
2407
2408 Self {
2409 passed,
2410 failed,
2411 ignored,
2412 measured,
2413 filtered_out,
2414 exec_time,
2415 }
2416 }
2417
2418 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
2419 if results.iter().any(|(_, result)| result.is_failed()) {
2420 ExitCode::from(101)
2421 } else {
2422 ExitCode::SUCCESS
2423 }
2424 }
2425}
2426
2427pub trait DependencyView: Debug {
2428 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
2429}
2430
2431impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
2432 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
2433 self.as_ref().get(name)
2434 }
2435}
2436
2437#[derive(Debug, Clone, Eq, PartialEq)]
2438pub enum CapturedOutput {
2439 Stdout {
2440 timestamp: SystemTime,
2441 line: String,
2442 },
2443 Stderr {
2444 timestamp: SystemTime,
2445 line: String,
2446 },
2447 Host {
2459 timestamp: SystemTime,
2460 line: String,
2461 },
2462}
2463
2464impl CapturedOutput {
2465 pub fn stdout(line: String) -> Self {
2466 CapturedOutput::Stdout {
2467 timestamp: SystemTime::now(),
2468 line,
2469 }
2470 }
2471
2472 pub fn stderr(line: String) -> Self {
2473 CapturedOutput::Stderr {
2474 timestamp: SystemTime::now(),
2475 line,
2476 }
2477 }
2478
2479 pub fn host(timestamp: SystemTime, line: String) -> Self {
2484 CapturedOutput::Host { timestamp, line }
2485 }
2486
2487 pub fn timestamp(&self) -> SystemTime {
2488 match self {
2489 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
2490 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
2491 CapturedOutput::Host { timestamp, .. } => *timestamp,
2492 }
2493 }
2494
2495 pub fn line(&self) -> &str {
2496 match self {
2497 CapturedOutput::Stdout { line, .. } => line,
2498 CapturedOutput::Stderr { line, .. } => line,
2499 CapturedOutput::Host { line, .. } => line,
2500 }
2501 }
2502}
2503
2504impl PartialOrd for CapturedOutput {
2505 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2506 Some(self.cmp(other))
2507 }
2508}
2509
2510impl Ord for CapturedOutput {
2511 fn cmp(&self, other: &Self) -> Ordering {
2512 self.timestamp().cmp(&other.timestamp())
2513 }
2514}
2515
2516#[cfg(test)]
2517mod error_reporting_tests {
2518 use super::*;
2519 use std::panic::{catch_unwind, AssertUnwindSafe};
2520 use std::time::Duration;
2521
2522 fn simulate_runner(
2523 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
2524 ) -> TestResult {
2525 crate::panic_hook::install_panic_hook();
2526 let test_id = crate::panic_hook::next_test_id();
2527 crate::panic_hook::set_current_test_id(test_id);
2528 let result = catch_unwind(AssertUnwindSafe(move || {
2529 let ret = test_fn();
2530 ret.into_result()?;
2531 Ok(())
2532 }));
2533 let test_result =
2534 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
2535 crate::panic_hook::clear_current_test_id();
2536 test_result
2537 }
2538
2539 #[test]
2540 fn panic_with_assert_eq() {
2541 let result = simulate_runner(|| {
2542 assert_eq!(1, 2);
2543 Box::new(())
2544 });
2545 assert!(result.is_failed());
2546 let msg = result.failure_message().unwrap();
2547 println!("=== panic assert_eq failure message ===\n{msg}\n===");
2548 assert!(
2549 msg.contains("assertion `left == right` failed"),
2550 "Expected assertion message, got: {msg}"
2551 );
2552 assert!(
2553 msg.contains("at "),
2554 "Expected location info in message, got: {msg}"
2555 );
2556 }
2557
2558 #[test]
2559 fn string_error() {
2560 let result = simulate_runner(|| {
2561 let r: Result<(), String> = Err("something went wrong".to_string());
2562 Box::new(r)
2563 });
2564 assert!(result.is_failed());
2565 let msg = result.failure_message().unwrap();
2566 println!("=== string error failure message ===\n{msg}\n===");
2567 assert_eq!(msg, "something went wrong");
2568 }
2569
2570 #[test]
2571 fn anyhow_error() {
2572 let result = simulate_runner(|| {
2573 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
2574 let err = anyhow::anyhow!(inner).context("operation failed");
2575 let r: Result<(), anyhow::Error> = Err(err);
2576 Box::new(r)
2577 });
2578 assert!(result.is_failed());
2579 let msg = result.failure_message().unwrap();
2580 println!("=== anyhow error failure message ===\n{msg}\n===");
2581 assert!(
2582 msg.contains("operation failed"),
2583 "Expected 'operation failed', got: {msg}"
2584 );
2585 assert!(
2586 msg.contains("file not found"),
2587 "Expected 'file not found', got: {msg}"
2588 );
2589 }
2590
2591 #[test]
2592 fn std_io_error() {
2593 let result = simulate_runner(|| {
2594 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
2595 std::io::ErrorKind::NotFound,
2596 "file not found",
2597 ));
2598 Box::new(r)
2599 });
2600 assert!(result.is_failed());
2601 let msg = result.failure_message().unwrap();
2602 println!("=== std io error failure message ===\n{msg}\n===");
2603 assert_eq!(msg, "file not found");
2605 }
2606
2607 #[test]
2608 fn panic_with_location_info() {
2609 let result = simulate_runner(|| {
2610 panic!("test panic with location");
2611 #[allow(unreachable_code)]
2612 Box::new(())
2613 });
2614 assert!(result.is_failed());
2615 let cause = result.failure_cause().unwrap();
2616 match cause {
2617 FailureCause::Panic(p) => {
2618 assert!(p.location.is_some(), "Expected location info");
2619 let loc = p.location.as_ref().unwrap();
2620 assert!(
2621 loc.file.contains("internal.rs"),
2622 "Expected file to contain internal.rs, got: {}",
2623 loc.file
2624 );
2625 assert!(loc.line > 0, "Expected non-zero line number");
2626 }
2627 other => panic!("Expected Panic cause, got: {other:?}"),
2628 }
2629 }
2630
2631 #[test]
2632 fn panic_render_includes_location() {
2633 let result = simulate_runner(|| {
2634 panic!("location test");
2635 #[allow(unreachable_code)]
2636 Box::new(())
2637 });
2638 let msg = result.failure_message().unwrap();
2639 assert!(
2640 msg.contains("location test"),
2641 "Expected panic message, got: {msg}"
2642 );
2643 assert!(
2644 msg.contains("\n at "),
2645 "Expected location line in render, got: {msg}"
2646 );
2647 }
2648
2649 #[test]
2650 fn should_panic_with_message_matching() {
2651 crate::panic_hook::install_panic_hook();
2652 let test_id = crate::panic_hook::next_test_id();
2653 crate::panic_hook::set_current_test_id(test_id);
2654 let result = catch_unwind(AssertUnwindSafe(|| {
2655 panic!("expected panic message");
2656 }));
2657 let test_result = TestResult::from_result(
2658 &ShouldPanic::WithMessage("expected panic".to_string()),
2659 Duration::from_millis(1),
2660 result.map(|_| Ok(())),
2661 );
2662 crate::panic_hook::clear_current_test_id();
2663 assert!(
2664 test_result.is_passed(),
2665 "Expected test to pass with matching panic message"
2666 );
2667 }
2668
2669 #[test]
2670 fn should_panic_with_wrong_message() {
2671 crate::panic_hook::install_panic_hook();
2672 let test_id = crate::panic_hook::next_test_id();
2673 crate::panic_hook::set_current_test_id(test_id);
2674 let result = catch_unwind(AssertUnwindSafe(|| {
2675 panic!("actual panic message");
2676 }));
2677 let test_result = TestResult::from_result(
2678 &ShouldPanic::WithMessage("completely different".to_string()),
2679 Duration::from_millis(1),
2680 result.map(|_| Ok(())),
2681 );
2682 crate::panic_hook::clear_current_test_id();
2683 assert!(
2684 test_result.is_failed(),
2685 "Expected test to fail with wrong panic message"
2686 );
2687 let msg = test_result.failure_message().unwrap();
2688 assert!(
2689 msg.contains("unexpected message"),
2690 "Expected 'unexpected message' in: {msg}"
2691 );
2692 }
2693
2694 #[test]
2695 fn pretty_assertions_diff() {
2696 let result = simulate_runner(|| {
2697 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
2698 Box::new(())
2699 });
2700 assert!(result.is_failed());
2701 let cause = result.failure_cause().unwrap();
2702
2703 let panic_cause = match cause {
2705 FailureCause::Panic(p) => p,
2706 other => panic!("Expected Panic cause, got: {other:?}"),
2707 };
2708
2709 let message = panic_cause.message.as_deref().unwrap();
2711 println!("=== pretty_assertions failure message ===\n{message}\n===");
2712 assert!(
2713 message.contains("foo") && message.contains("baz"),
2714 "Expected diff with 'foo' and 'baz', got: {message}"
2715 );
2716
2717 assert!(panic_cause.location.is_some(), "Expected location info");
2719
2720 let rendered = cause.render();
2722 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
2723 assert!(
2724 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
2725 "Expected no backtrace noise in rendered output, got: {rendered}"
2726 );
2727 assert!(
2729 rendered.contains("\n at "),
2730 "Expected location in rendered output, got: {rendered}"
2731 );
2732 }
2733
2734 #[test]
2735 fn detached_thread_panic_detected() {
2736 crate::panic_hook::install_panic_hook();
2737 let test_id = crate::panic_hook::next_test_id();
2738 crate::panic_hook::set_current_test_id(test_id);
2739 crate::panic_hook::create_detached_collector(test_id);
2740
2741 let result = catch_unwind(AssertUnwindSafe(|| {
2742 let handle = crate::spawn::spawn_thread(|| {
2743 panic!("background thread panic");
2744 });
2745 let _ = handle.join();
2746 }));
2747
2748 let mut test_result = TestResult::from_result(
2749 &ShouldPanic::No,
2750 Duration::from_millis(1),
2751 result.map(|_| Ok(())),
2752 );
2753
2754 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2755 let panics = match collector.lock() {
2756 Ok(p) => p,
2757 Err(poisoned) => poisoned.into_inner(),
2758 };
2759 if !panics.is_empty() && test_result.is_passed() {
2760 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
2761 test_result = TestResult::failed(
2762 Duration::from_millis(1),
2763 FailureCause::Panic(PanicCause {
2764 message: Some(format!(
2765 "Detached task(s) panicked:\n{}",
2766 messages.join("\n---\n")
2767 )),
2768 location: panics.first().and_then(|p| p.location.clone()),
2769 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
2770 }),
2771 );
2772 }
2773 }
2774
2775 crate::panic_hook::clear_current_test_id();
2776
2777 assert!(
2778 test_result.is_failed(),
2779 "Expected test to fail due to detached panic"
2780 );
2781 let msg = test_result.failure_message().unwrap();
2782 assert!(
2783 msg.contains("Detached task(s) panicked"),
2784 "Expected detached panic message, got: {msg}"
2785 );
2786 assert!(
2787 msg.contains("background thread panic"),
2788 "Expected original panic message, got: {msg}"
2789 );
2790 }
2791
2792 #[test]
2793 fn detached_thread_panic_ignored_with_policy() {
2794 crate::panic_hook::install_panic_hook();
2795 let test_id = crate::panic_hook::next_test_id();
2796 crate::panic_hook::set_current_test_id(test_id);
2797 crate::panic_hook::create_detached_collector(test_id);
2798
2799 let result = catch_unwind(AssertUnwindSafe(|| {
2800 let handle = crate::spawn::spawn_thread(|| {
2801 panic!("ignored thread panic");
2802 });
2803 let _ = handle.join();
2804 }));
2805
2806 let test_result = TestResult::from_result(
2807 &ShouldPanic::No,
2808 Duration::from_millis(1),
2809 result.map(|_| Ok(())),
2810 );
2811
2812 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2813 let panics = match collector.lock() {
2814 Ok(p) => p,
2815 Err(poisoned) => poisoned.into_inner(),
2816 };
2817 assert!(
2819 !panics.is_empty(),
2820 "Expected panics in collector even with Ignore policy"
2821 );
2822 }
2823
2824 crate::panic_hook::clear_current_test_id();
2825
2826 assert!(
2827 test_result.is_passed(),
2828 "Expected test to pass with Ignore policy"
2829 );
2830 }
2831
2832 #[cfg(feature = "tokio")]
2833 #[test]
2834 fn detached_task_panic_detected() {
2835 let rt = tokio::runtime::Runtime::new().unwrap();
2836 rt.block_on(async {
2837 crate::panic_hook::install_panic_hook();
2838 let test_id = crate::panic_hook::next_test_id();
2839 crate::panic_hook::set_current_test_id(test_id);
2840 crate::panic_hook::create_detached_collector(test_id);
2841
2842 let handle = crate::spawn::spawn(async {
2843 panic!("detached task panic");
2844 });
2845 let _ = handle.await;
2846
2847 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
2848 let panics = collector.lock().unwrap();
2849
2850 assert_eq!(panics.len(), 1);
2851 assert!(
2852 panics[0]
2853 .message
2854 .as_ref()
2855 .unwrap()
2856 .contains("detached task panic"),
2857 "Expected panic message, got: {:?}",
2858 panics[0].message
2859 );
2860
2861 crate::panic_hook::clear_current_test_id();
2862 });
2863 }
2864
2865 #[test]
2866 fn failure_cause_variants() {
2867 let cause = FailureCause::ReturnedMessage("simple message".to_string());
2869 assert_eq!(cause.render(), "simple message");
2870 assert!(cause.panic_message().is_none());
2871
2872 let cause = FailureCause::ReturnedError {
2874 display: "display text".to_string(),
2875 debug: "debug text".to_string(),
2876 prefer_debug: false,
2877 error: Arc::new("display text".to_string()),
2878 };
2879 assert_eq!(cause.render(), "display text");
2880
2881 let cause = FailureCause::ReturnedError {
2883 display: "display text".to_string(),
2884 debug: "debug text".to_string(),
2885 prefer_debug: true,
2886 error: Arc::new("debug text".to_string()),
2887 };
2888 assert_eq!(cause.render(), "debug text");
2889
2890 let cause = FailureCause::HarnessError("harness error".to_string());
2892 assert_eq!(cause.render(), "harness error");
2893
2894 let cause = FailureCause::Panic(PanicCause {
2896 message: Some("panic msg".to_string()),
2897 location: None,
2898 backtrace: None,
2899 });
2900 assert_eq!(cause.render(), "panic msg");
2901 assert_eq!(cause.panic_message(), Some("panic msg"));
2902 }
2903}
2904
2905#[cfg(test)]
2906mod filter_tests {
2907 use super::*;
2908
2909 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
2910 RegisteredTest {
2911 name: name.to_string(),
2912 crate_name: "mycrate".to_string(),
2913 module_path: module_path.to_string(),
2914 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
2915 props: TestProperties::default(),
2916 dependencies: None,
2917 }
2918 }
2919
2920 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
2921 let mut test = make_test(name, module_path);
2922 test.props.tags = tags.into_iter().map(String::from).collect();
2923 test
2924 }
2925
2926 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
2927 Arguments {
2928 filter: filters.into_iter().map(String::from).collect(),
2929 skip: skip.into_iter().map(String::from).collect(),
2930 exact,
2931 ..Default::default()
2932 }
2933 }
2934
2935 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
2936 filter_registered_tests(args, tests)
2937 .into_iter()
2938 .map(|t| t.filterable_name())
2939 .collect()
2940 }
2941
2942 #[test]
2945 fn filter_test_substring_match() {
2946 let test = make_test("hello_world", "mod1");
2947 assert!(filter_test(&test, "hello", false));
2948 assert!(filter_test(&test, "world", false));
2949 assert!(filter_test(&test, "mod1::hello", false));
2950 assert!(!filter_test(&test, "nonexistent", false));
2951 }
2952
2953 #[test]
2954 fn filter_test_exact_match() {
2955 let test = make_test("hello_world", "mod1");
2956 assert!(filter_test(&test, "mod1::hello_world", true));
2957 assert!(!filter_test(&test, "hello_world", true));
2958 assert!(!filter_test(&test, "hello", true));
2959 }
2960
2961 #[test]
2962 fn filter_test_tag_match() {
2963 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
2964 assert!(filter_test(&test, ":tag:fast", false));
2965 assert!(filter_test(&test, ":tag:unit", false));
2966 assert!(!filter_test(&test, ":tag:slow", false));
2967 }
2968
2969 #[test]
2970 fn filter_test_tag_empty_matches_untagged() {
2971 let untagged = make_test("t1", "mod1");
2972 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
2973 assert!(filter_test(&untagged, ":tag:", false));
2974 assert!(!filter_test(&tagged, ":tag:", false));
2975 }
2976
2977 #[test]
2980 fn no_filters_includes_all() {
2981 let tests = vec![make_test("a", "m"), make_test("b", "m")];
2982 let args = make_args(vec![], vec![], false);
2983 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
2984 }
2985
2986 #[test]
2987 fn single_filter_substring() {
2988 let tests = vec![
2989 make_test("alpha", "m"),
2990 make_test("beta", "m"),
2991 make_test("alphabet", "m"),
2992 ];
2993 let args = make_args(vec!["alpha"], vec![], false);
2994 assert_eq!(
2995 filtered_names(&args, &tests),
2996 vec!["m::alpha", "m::alphabet"]
2997 );
2998 }
2999
3000 #[test]
3001 fn multiple_filters_or_semantics() {
3002 let tests = vec![
3003 make_test("alpha", "m"),
3004 make_test("beta", "m"),
3005 make_test("gamma", "m"),
3006 ];
3007 let args = make_args(vec!["alpha", "gamma"], vec![], false);
3008 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
3009 }
3010
3011 #[test]
3012 fn multiple_filters_exact() {
3013 let tests = vec![
3014 make_test("alpha", "m"),
3015 make_test("alphabet", "m"),
3016 make_test("beta", "m"),
3017 ];
3018 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
3019 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
3020 }
3021
3022 #[test]
3025 fn skip_substring_match() {
3026 let tests = vec![
3027 make_test("fast_test", "m"),
3028 make_test("slow_test", "m"),
3029 make_test("slower_test", "m"),
3030 ];
3031 let args = make_args(vec![], vec!["slow"], false);
3032 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
3033 }
3034
3035 #[test]
3036 fn skip_exact_match() {
3037 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
3038 let args = make_args(vec![], vec!["m::slow_test"], true);
3039 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
3040 }
3041
3042 #[test]
3043 fn skip_with_tag() {
3044 let tests = vec![
3045 make_tagged_test("t1", "m", vec!["slow"]),
3046 make_tagged_test("t2", "m", vec!["fast"]),
3047 make_test("t3", "m"),
3048 ];
3049 let args = make_args(vec![], vec![":tag:slow"], false);
3050 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
3051 }
3052
3053 #[test]
3056 fn include_and_skip_combined() {
3057 let tests = vec![
3058 make_test("alpha_fast", "m"),
3059 make_test("alpha_slow", "m"),
3060 make_test("beta_fast", "m"),
3061 ];
3062 let args = make_args(vec!["alpha"], vec!["slow"], false);
3064 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
3065 }
3066
3067 #[test]
3068 fn skip_wins_over_include() {
3069 let tests = vec![make_test("target", "m")];
3070 let args = make_args(vec!["target"], vec!["target"], false);
3072 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
3073 }
3074
3075 #[test]
3078 fn filter_test_tag_or_expression() {
3079 let test_a = make_tagged_test("t1", "m", vec!["a"]);
3081 let test_b = make_tagged_test("t2", "m", vec!["b"]);
3082 let test_c = make_tagged_test("t3", "m", vec!["c"]);
3083 assert!(filter_test(&test_a, ":tag:a|b", false));
3084 assert!(filter_test(&test_b, ":tag:a|b", false));
3085 assert!(!filter_test(&test_c, ":tag:a|b", false));
3086 }
3087
3088 #[test]
3089 fn filter_test_tag_and_expression() {
3090 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
3092 let test_a = make_tagged_test("t2", "m", vec!["a"]);
3093 let test_b = make_tagged_test("t3", "m", vec!["b"]);
3094 assert!(filter_test(&test_ab, ":tag:a&b", false));
3095 assert!(!filter_test(&test_a, ":tag:a&b", false));
3096 assert!(!filter_test(&test_b, ":tag:a&b", false));
3097 }
3098
3099 #[test]
3100 fn filter_test_tag_mixed_and_or() {
3101 let test_a = make_tagged_test("t1", "m", vec!["a"]);
3103 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
3104 let test_b = make_tagged_test("t3", "m", vec!["b"]);
3105 let test_c = make_tagged_test("t4", "m", vec!["c"]);
3106 let test_none = make_test("t5", "m");
3107 assert!(filter_test(&test_a, ":tag:a|b&c", false));
3108 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
3109 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
3110 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
3111 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
3112 }
3113
3114 #[test]
3115 fn filter_test_tag_exact_flag_does_not_affect_tags() {
3116 let test = make_tagged_test("t1", "m", vec!["fast"]);
3118 assert!(filter_test(&test, ":tag:fast", true));
3119 assert!(!filter_test(&test, ":tag:slow", true));
3120 }
3121
3122 #[test]
3123 fn include_by_tag_or_expression() {
3124 let tests = vec![
3125 make_tagged_test("t1", "m", vec!["unit"]),
3126 make_tagged_test("t2", "m", vec!["integration"]),
3127 make_tagged_test("t3", "m", vec!["e2e"]),
3128 ];
3129 let args = make_args(vec![":tag:unit|integration"], vec![], false);
3130 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
3131 }
3132
3133 #[test]
3134 fn skip_by_tag_and_expression() {
3135 let tests = vec![
3136 make_tagged_test("t1", "m", vec!["slow", "network"]),
3137 make_tagged_test("t2", "m", vec!["slow"]),
3138 make_tagged_test("t3", "m", vec!["network"]),
3139 make_test("t4", "m"),
3140 ];
3141 let args = make_args(vec![], vec![":tag:slow&network"], false);
3143 assert_eq!(
3144 filtered_names(&args, &tests),
3145 vec!["m::t2", "m::t3", "m::t4"]
3146 );
3147 }
3148
3149 #[test]
3158 fn matrix_dim_case_tag_selects_exactly_one_case() {
3159 let tests = vec![
3163 make_tagged_test("my_test_postgres", "m", vec!["db_postgres", "fast"]),
3164 make_tagged_test("my_test_sqlite", "m", vec!["db_sqlite", "fast"]),
3165 ];
3166 let args = make_args(vec![":tag:db_postgres"], vec![], false);
3168 assert_eq!(filtered_names(&args, &tests), vec!["m::my_test_postgres"]);
3169 }
3170
3171 #[test]
3172 fn matrix_dim_case_tags_select_subset_per_dimension() {
3173 let tests = vec![
3176 make_tagged_test("combo_postgres_ts", "m", vec!["db_postgres", "lang_ts"]),
3177 make_tagged_test("combo_postgres_rust", "m", vec!["db_postgres", "lang_rust"]),
3178 make_tagged_test("combo_sqlite_ts", "m", vec!["db_sqlite", "lang_ts"]),
3179 make_tagged_test("combo_sqlite_rust", "m", vec!["db_sqlite", "lang_rust"]),
3180 ];
3181 let args = make_args(vec![":tag:db_postgres"], vec![], false);
3183 assert_eq!(
3184 filtered_names(&args, &tests),
3185 vec!["m::combo_postgres_ts", "m::combo_postgres_rust"]
3186 );
3187 let args = make_args(vec![":tag:db_sqlite&lang_rust"], vec![], false);
3189 assert_eq!(filtered_names(&args, &tests), vec!["m::combo_sqlite_rust"]);
3190 }
3191
3192 #[test]
3193 fn matrix_auto_tag_coexists_with_explicit_tags() {
3194 let tests = vec![
3198 make_tagged_test("t_postgres", "m", vec!["db_postgres", "fast"]),
3199 make_tagged_test("t_sqlite", "m", vec!["db_sqlite", "fast"]),
3200 ];
3201 let args = make_args(vec![":tag:fast"], vec![], false);
3202 assert_eq!(
3203 filtered_names(&args, &tests),
3204 vec!["m::t_postgres", "m::t_sqlite"]
3205 );
3206 let args = make_args(vec![":tag:db_sqlite"], vec![], false);
3207 assert_eq!(filtered_names(&args, &tests), vec!["m::t_sqlite"]);
3208 }
3209}