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 struct HostedRpcOwnerCell {
698 inner: Mutex<Box<dyn HostedRpcDispatcher>>,
699}
700
701impl HostedRpcOwnerCell {
702 pub fn from_owner<T: HostedRpcDep>(owner: T) -> Self {
705 Self {
706 inner: Mutex::new(Box::new(owner) as Box<dyn HostedRpcDispatcher>),
707 }
708 }
709
710 pub fn dispatch(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
718 let dispatch_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
726 let mut guard = match self.inner.lock() {
727 Ok(g) => g,
728 Err(_) => return Err("hosted rpc owner poisoned".to_string()),
729 };
730 guard.dispatch(method_idx, args)
731 }));
732 match dispatch_result {
733 Ok(r) => r,
734 Err(payload) => {
735 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
736 (*s).to_string()
737 } else if let Some(s) = payload.downcast_ref::<String>() {
738 s.clone()
739 } else {
740 "<non-string panic payload>".to_string()
741 };
742 Err(format!("hosted rpc owner panicked: {msg}"))
743 }
744 }
745 }
746}
747
748pub struct HostedBothShared {
768 descriptor_bytes: Vec<u8>,
769 rpc_cell: Arc<HostedRpcOwnerCell>,
770}
771
772impl HostedBothShared {
773 pub fn new(descriptor_bytes: Vec<u8>, rpc_cell: Arc<HostedRpcOwnerCell>) -> Self {
776 Self {
777 descriptor_bytes,
778 rpc_cell,
779 }
780 }
781
782 pub fn descriptor_bytes(&self) -> &[u8] {
785 &self.descriptor_bytes
786 }
787
788 pub fn rpc_cell(&self) -> Arc<HostedRpcOwnerCell> {
792 self.rpc_cell.clone()
793 }
794}
795
796#[derive(Debug, Clone)]
798pub enum HostedRpcError {
799 Dispatch(String),
802 Transport(String),
805}
806
807impl std::fmt::Display for HostedRpcError {
808 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
809 match self {
810 HostedRpcError::Dispatch(s) => write!(f, "hosted rpc dispatch error: {s}"),
811 HostedRpcError::Transport(s) => write!(f, "hosted rpc transport error: {s}"),
812 }
813 }
814}
815
816impl std::error::Error for HostedRpcError {}
817
818pub trait HostedRpcTransport: Send + Sync {
823 fn call(&self, dep_id: &str, method_idx: u32, args: Vec<u8>)
827 -> Result<Vec<u8>, HostedRpcError>;
828}
829
830pub struct HostedRpcChannel {
836 dep_id: String,
837 transport: Arc<dyn HostedRpcTransport>,
838}
839
840impl HostedRpcChannel {
841 pub fn new(dep_id: String, transport: Arc<dyn HostedRpcTransport>) -> Self {
844 Self { dep_id, transport }
845 }
846
847 pub fn dep_id(&self) -> &str {
850 &self.dep_id
851 }
852
853 pub fn call(&self, method_idx: u32, args: Vec<u8>) -> Result<Vec<u8>, HostedRpcError> {
878 self.transport.call(&self.dep_id, method_idx, args)
879 }
880}
881
882impl Clone for HostedRpcChannel {
883 fn clone(&self) -> Self {
884 Self {
885 dep_id: self.dep_id.clone(),
886 transport: self.transport.clone(),
887 }
888 }
889}
890
891pub struct InProcessHostedRpcTransport {
895 cells: HashMap<String, Arc<HostedRpcOwnerCell>>,
896}
897
898impl InProcessHostedRpcTransport {
899 pub fn new(cells: HashMap<String, Arc<HostedRpcOwnerCell>>) -> Self {
900 Self { cells }
901 }
902}
903
904impl HostedRpcTransport for InProcessHostedRpcTransport {
905 fn call(
906 &self,
907 dep_id: &str,
908 method_idx: u32,
909 args: Vec<u8>,
910 ) -> Result<Vec<u8>, HostedRpcError> {
911 let cell = self.cells.get(dep_id).ok_or_else(|| {
912 HostedRpcError::Transport(format!("in-process HostedRpc: unknown dep id '{dep_id}'"))
913 })?;
914 cell.dispatch(method_idx, &args)
915 .map_err(HostedRpcError::Dispatch)
916 }
917}
918
919#[derive(Clone)]
924#[allow(clippy::type_complexity)]
925pub struct RpcFactory {
926 pub owner_into_cell: Arc<
929 dyn (Fn(Arc<dyn Any + Send + Sync>) -> Arc<HostedRpcOwnerCell>) + Send + Sync + 'static,
930 >,
931 pub build_stub:
934 Arc<dyn (Fn(HostedRpcChannel) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
935}
936
937#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
942pub enum DepScope {
943 #[default]
947 Shared,
948 PerWorker,
951 Cloneable,
955 Hosted,
964 HostedRpc,
970}
971
972impl DepScope {
973 pub fn requires_single_thread_when_capturing(&self) -> bool {
976 matches!(self, DepScope::Shared)
977 }
978
979 pub fn parent_must_materialize_under_spawn_workers(&self) -> bool {
985 matches!(
986 self,
987 DepScope::Cloneable | DepScope::Hosted | DepScope::HostedRpc
988 )
989 }
990}
991
992#[derive(Clone)]
997#[allow(clippy::type_complexity)]
998pub enum WorkerReconstructor {
999 Sync(
1000 Arc<
1001 dyn (Fn(
1002 Arc<dyn Any + Send + Sync>,
1003 Arc<dyn DependencyView + Send + Sync>,
1004 ) -> Arc<dyn Any + Send + Sync + 'static>)
1005 + Send
1006 + Sync
1007 + 'static,
1008 >,
1009 ),
1010 Async(
1011 Arc<
1012 dyn (Fn(
1013 Arc<dyn Any + Send + Sync>,
1014 Arc<dyn DependencyView + Send + Sync>,
1015 ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
1016 + Send
1017 + Sync
1018 + 'static,
1019 >,
1020 ),
1021}
1022
1023#[derive(Clone)]
1027#[allow(clippy::type_complexity)]
1028pub struct CloneableCodec {
1029 pub to_wire: Arc<dyn (Fn(Arc<dyn Any + Send + Sync>) -> Vec<u8>) + Send + Sync + 'static>,
1032 pub from_wire_bytes: Arc<dyn (Fn(&[u8]) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
1035}
1036
1037#[derive(Clone)]
1038pub struct RegisteredDependency {
1039 pub name: String, pub crate_name: String,
1041 pub module_path: String,
1042 pub constructor: DependencyConstructor,
1043 pub dependencies: Vec<String>,
1044 pub scope: DepScope,
1047 pub worker_fn: Option<WorkerReconstructor>,
1052 pub cloneable_codec: Option<CloneableCodec>,
1056 pub hosted_codec: Option<CloneableCodec>,
1063 pub rpc_factory: Option<RpcFactory>,
1068 pub companions: Vec<String>,
1087}
1088
1089impl RegisteredDependency {
1090 pub fn new_shared(
1094 name: String,
1095 crate_name: String,
1096 module_path: String,
1097 constructor: DependencyConstructor,
1098 dependencies: Vec<String>,
1099 ) -> Self {
1100 Self {
1101 name,
1102 crate_name,
1103 module_path,
1104 constructor,
1105 dependencies,
1106 scope: DepScope::Shared,
1107 worker_fn: None,
1108 cloneable_codec: None,
1109 hosted_codec: None,
1110 rpc_factory: None,
1111 companions: Vec::new(),
1112 }
1113 }
1114}
1115
1116impl Debug for RegisteredDependency {
1117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1118 f.debug_struct("RegisteredDependency")
1119 .field("name", &self.name)
1120 .field("crate_name", &self.crate_name)
1121 .field("module_path", &self.module_path)
1122 .finish()
1123 }
1124}
1125
1126impl PartialEq for RegisteredDependency {
1127 fn eq(&self, other: &Self) -> bool {
1128 self.name == other.name
1129 }
1130}
1131
1132impl Eq for RegisteredDependency {}
1133
1134impl Hash for RegisteredDependency {
1135 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1136 self.name.hash(state);
1137 }
1138}
1139
1140impl RegisteredDependency {
1141 pub fn crate_and_module(&self) -> String {
1142 [&self.crate_name, &self.module_path]
1143 .into_iter()
1144 .filter(|s| !s.is_empty())
1145 .cloned()
1146 .collect::<Vec<String>>()
1147 .join("::")
1148 }
1149
1150 pub fn qualified_id(&self) -> String {
1155 [&self.crate_name, &self.module_path, &self.name]
1156 .into_iter()
1157 .filter(|s| !s.is_empty())
1158 .cloned()
1159 .collect::<Vec<String>>()
1160 .join("::")
1161 }
1162}
1163
1164pub static REGISTERED_DEPENDENCY_CONSTRUCTORS: Mutex<Vec<RegisteredDependency>> =
1165 Mutex::new(Vec::new());
1166
1167#[derive(Debug, Clone)]
1168pub enum RegisteredTestSuiteProperty {
1169 Sequential {
1170 name: String,
1171 crate_name: String,
1172 module_path: String,
1173 },
1174 Tag {
1175 name: String,
1176 crate_name: String,
1177 module_path: String,
1178 tag: String,
1179 },
1180 Timeout {
1181 name: String,
1182 crate_name: String,
1183 module_path: String,
1184 timeout: Duration,
1185 },
1186}
1187
1188impl RegisteredTestSuiteProperty {
1189 pub fn crate_name(&self) -> &String {
1190 match self {
1191 RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
1192 RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
1193 RegisteredTestSuiteProperty::Timeout { crate_name, .. } => crate_name,
1194 }
1195 }
1196
1197 pub fn module_path(&self) -> &String {
1198 match self {
1199 RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
1200 RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
1201 RegisteredTestSuiteProperty::Timeout { module_path, .. } => module_path,
1202 }
1203 }
1204
1205 pub fn name(&self) -> &String {
1206 match self {
1207 RegisteredTestSuiteProperty::Sequential { name, .. } => name,
1208 RegisteredTestSuiteProperty::Tag { name, .. } => name,
1209 RegisteredTestSuiteProperty::Timeout { name, .. } => name,
1210 }
1211 }
1212
1213 pub fn crate_and_module(&self) -> String {
1214 [self.crate_name(), self.module_path(), self.name()]
1215 .into_iter()
1216 .filter(|s| !s.is_empty())
1217 .cloned()
1218 .collect::<Vec<String>>()
1219 .join("::")
1220 }
1221}
1222
1223pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
1224 Mutex::new(Vec::new());
1225
1226#[derive(Clone)]
1227#[allow(clippy::type_complexity)]
1228pub enum TestGeneratorFunction {
1229 Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
1230 Async(
1231 Arc<
1232 dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
1233 + Send
1234 + Sync
1235 + 'static,
1236 >,
1237 ),
1238}
1239
1240pub struct DynamicTestRegistration {
1241 tests: Vec<GeneratedTest>,
1242}
1243
1244impl Default for DynamicTestRegistration {
1245 fn default() -> Self {
1246 Self::new()
1247 }
1248}
1249
1250impl DynamicTestRegistration {
1251 pub fn new() -> Self {
1252 Self { tests: Vec::new() }
1253 }
1254
1255 pub fn to_vec(self) -> Vec<GeneratedTest> {
1256 self.tests
1257 }
1258
1259 pub fn add_sync_test<R: TestReturnValue + 'static>(
1260 &mut self,
1261 name: impl AsRef<str>,
1262 props: TestProperties,
1263 dependencies: Option<Vec<String>>,
1264 run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
1265 ) {
1266 self.tests.push(GeneratedTest {
1267 name: name.as_ref().to_string(),
1268 run: TestFunction::Sync(Arc::new(move |deps| {
1269 Box::new(run(deps)) as Box<dyn TestReturnValue>
1270 })),
1271 props,
1272 dependencies,
1273 });
1274 }
1275
1276 #[cfg(feature = "tokio")]
1277 pub fn add_async_test<R: TestReturnValue + 'static>(
1278 &mut self,
1279 name: impl AsRef<str>,
1280 props: TestProperties,
1281 dependencies: Option<Vec<String>>,
1282 run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
1283 + Send
1284 + Sync
1285 + Clone
1286 + 'static,
1287 ) {
1288 self.tests.push(GeneratedTest {
1289 name: name.as_ref().to_string(),
1290 run: TestFunction::Async(Arc::new(move |deps| {
1291 let run = run.clone();
1292 Box::pin(async move {
1293 let r = run(deps).await;
1294 Box::new(r) as Box<dyn TestReturnValue>
1295 })
1296 })),
1297 props,
1298 dependencies,
1299 });
1300 }
1301}
1302
1303#[derive(Clone)]
1304pub struct GeneratedTest {
1305 pub name: String,
1306 pub run: TestFunction,
1307 pub props: TestProperties,
1308 pub dependencies: Option<Vec<String>>,
1309}
1310
1311#[derive(Clone)]
1312pub struct RegisteredTestGenerator {
1313 pub name: String,
1314 pub crate_name: String,
1315 pub module_path: String,
1316 pub run: TestGeneratorFunction,
1317 pub is_ignored: bool,
1318}
1319
1320impl RegisteredTestGenerator {
1321 pub fn crate_and_module(&self) -> String {
1322 [&self.crate_name, &self.module_path]
1323 .into_iter()
1324 .filter(|s| !s.is_empty())
1325 .cloned()
1326 .collect::<Vec<String>>()
1327 .join("::")
1328 }
1329}
1330
1331pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
1332
1333pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
1334 if let Some(tag_list) = filter.strip_prefix(":tag:") {
1335 if tag_list.is_empty() {
1336 test.props.tags.is_empty()
1338 } else {
1339 let or_tags = tag_list.split('|').collect::<Vec<&str>>();
1340 let mut result = false;
1341 for or_tag in or_tags {
1342 let and_tags = or_tag.split('&').collect::<Vec<&str>>();
1343 let mut and_result = true;
1344 for and_tag in and_tags {
1345 if !test.props.tags.contains(&and_tag.to_string()) {
1346 and_result = false;
1347 break;
1348 }
1349 }
1350 if and_result {
1351 result = true;
1352 break;
1353 }
1354 }
1355 result
1356 }
1357 } else if exact {
1358 test.filterable_name() == filter
1359 } else {
1360 test.filterable_name().contains(filter)
1361 }
1362}
1363
1364pub(crate) fn apply_suite_props_to_tests(
1365 tests: &[RegisteredTest],
1366 props: &[RegisteredTestSuiteProperty],
1367) -> Vec<RegisteredTest> {
1368 let props_with_prefix = props
1369 .iter()
1370 .map(|prop| (prop.crate_and_module(), prop))
1371 .collect::<Vec<_>>();
1372
1373 let mut result = Vec::new();
1374 for test in tests {
1375 let mut test = test.clone();
1376 for (prefix, prop) in &props_with_prefix {
1377 if test.crate_and_module().starts_with(prefix) {
1378 match prop {
1379 RegisteredTestSuiteProperty::Tag { tag, .. } => {
1380 test.props.tags.push(tag.clone());
1381 }
1382 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
1383 if test.props.timeout.is_none() {
1384 test.props.timeout = Some(*timeout);
1385 }
1386 }
1387 RegisteredTestSuiteProperty::Sequential { .. } => {
1388 }
1390 }
1391 }
1392 }
1393 result.push(test);
1394 }
1395 result
1396}
1397
1398pub(crate) fn filter_registered_tests(
1399 args: &Arguments,
1400 registered_tests: &[RegisteredTest],
1401) -> Vec<RegisteredTest> {
1402 registered_tests
1403 .iter()
1404 .filter(|registered_test| {
1405 !args
1406 .skip
1407 .iter()
1408 .any(|skip| filter_test(registered_test, skip, args.exact))
1409 })
1410 .filter(|registered_test| {
1411 args.filter.is_empty()
1412 || args
1413 .filter
1414 .iter()
1415 .any(|filter| filter_test(registered_test, filter, args.exact))
1416 })
1417 .filter(|registered_tests| {
1418 (args.bench && registered_tests.run.is_bench())
1419 || (args.test && !registered_tests.run.is_bench())
1420 || (!args.bench && !args.test)
1421 })
1422 .filter(|registered_test| {
1423 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
1424 })
1425 .cloned()
1426 .collect::<Vec<_>>()
1427}
1428
1429fn add_generated_tests(
1430 target: &mut Vec<RegisteredTest>,
1431 generator: &RegisteredTestGenerator,
1432 generated: Vec<GeneratedTest>,
1433) {
1434 target.extend(generated.into_iter().map(|mut test| {
1435 test.props.is_ignored |= generator.is_ignored;
1436 RegisteredTest {
1437 name: format!("{}::{}", generator.name, test.name),
1438 crate_name: generator.crate_name.clone(),
1439 module_path: generator.module_path.clone(),
1440 run: test.run,
1441 props: test.props,
1442 dependencies: test.dependencies,
1443 }
1444 }));
1445}
1446
1447#[cfg(feature = "tokio")]
1448pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1449 let mut result = Vec::new();
1450 for generator in generators {
1451 match &generator.run {
1452 TestGeneratorFunction::Sync(generator_fn) => {
1453 let tests = generator_fn();
1454 add_generated_tests(&mut result, generator, tests);
1455 }
1456 TestGeneratorFunction::Async(generator_fn) => {
1457 let tests = generator_fn().await;
1458 add_generated_tests(&mut result, generator, tests);
1459 }
1460 }
1461 }
1462 result
1463}
1464
1465pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1466 let mut result = Vec::new();
1467 for generator in generators {
1468 match &generator.run {
1469 TestGeneratorFunction::Sync(generator_fn) => {
1470 let tests = generator_fn();
1471 add_generated_tests(&mut result, generator, tests);
1472 }
1473 TestGeneratorFunction::Async(_) => {
1474 panic!("Async test generators are not supported in sync mode")
1475 }
1476 }
1477 }
1478 result
1479}
1480
1481pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
1482 let should_ensure_time = match test.props.ensure_time_control {
1483 ReportTimeControl::Default => args.ensure_time,
1484 ReportTimeControl::Enabled => true,
1485 ReportTimeControl::Disabled => false,
1486 };
1487 if should_ensure_time {
1488 match test.props.test_type {
1489 TestType::UnitTest => Some(args.unit_test_threshold()),
1490 TestType::IntegrationTest => Some(args.integration_test_threshold()),
1491 }
1492 } else {
1493 None
1494 }
1495}
1496
1497#[derive(Clone)]
1498pub enum TestResult {
1499 Passed {
1500 captured: Vec<CapturedOutput>,
1501 exec_time: Duration,
1502 },
1503 Benchmarked {
1504 captured: Vec<CapturedOutput>,
1505 exec_time: Duration,
1506 ns_iter_summ: Summary,
1507 mb_s: usize,
1508 },
1509 Failed {
1510 cause: FailureCause,
1511 captured: Vec<CapturedOutput>,
1512 exec_time: Duration,
1513 },
1514 Ignored {
1515 captured: Vec<CapturedOutput>,
1516 },
1517}
1518
1519impl TestResult {
1520 pub fn passed(exec_time: Duration) -> Self {
1521 TestResult::Passed {
1522 captured: Vec::new(),
1523 exec_time,
1524 }
1525 }
1526
1527 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
1528 TestResult::Benchmarked {
1529 captured: Vec::new(),
1530 exec_time,
1531 ns_iter_summ,
1532 mb_s,
1533 }
1534 }
1535
1536 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
1537 TestResult::Failed {
1538 cause,
1539 captured: Vec::new(),
1540 exec_time,
1541 }
1542 }
1543
1544 pub fn ignored() -> Self {
1545 TestResult::Ignored {
1546 captured: Vec::new(),
1547 }
1548 }
1549
1550 pub(crate) fn is_passed(&self) -> bool {
1551 matches!(self, TestResult::Passed { .. })
1552 }
1553
1554 pub(crate) fn is_benchmarked(&self) -> bool {
1555 matches!(self, TestResult::Benchmarked { .. })
1556 }
1557
1558 pub(crate) fn is_failed(&self) -> bool {
1559 matches!(self, TestResult::Failed { .. })
1560 }
1561
1562 pub(crate) fn is_ignored(&self) -> bool {
1563 matches!(self, TestResult::Ignored { .. })
1564 }
1565
1566 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
1567 match self {
1568 TestResult::Passed { captured, .. } => captured,
1569 TestResult::Failed { captured, .. } => captured,
1570 TestResult::Ignored { captured, .. } => captured,
1571 TestResult::Benchmarked { captured, .. } => captured,
1572 }
1573 }
1574
1575 pub(crate) fn stats(&self) -> Option<&Summary> {
1576 match self {
1577 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
1578 _ => None,
1579 }
1580 }
1581
1582 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
1583 match self {
1584 TestResult::Passed {
1585 captured: captured_ref,
1586 ..
1587 } => *captured_ref = captured,
1588 TestResult::Failed {
1589 captured: captured_ref,
1590 ..
1591 } => *captured_ref = captured,
1592 TestResult::Ignored {
1593 captured: captured_ref,
1594 } => *captured_ref = captured,
1595 TestResult::Benchmarked {
1596 captured: captured_ref,
1597 ..
1598 } => *captured_ref = captured,
1599 }
1600 }
1601
1602 pub(crate) fn from_result<A>(
1603 should_panic: &ShouldPanic,
1604 elapsed: Duration,
1605 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
1606 ) -> Self {
1607 match result {
1608 Ok(Ok(_)) => {
1609 if should_panic == &ShouldPanic::No {
1610 TestResult::passed(elapsed)
1611 } else {
1612 TestResult::failed(
1613 elapsed,
1614 FailureCause::HarnessError("Test did not panic as expected".to_string()),
1615 )
1616 }
1617 }
1618 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
1619 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
1620 }
1621 }
1622
1623 pub(crate) fn from_summary(
1624 should_panic: &ShouldPanic,
1625 elapsed: Duration,
1626 result: Result<Summary, Box<dyn Any + Send>>,
1627 bytes: u64,
1628 ) -> Self {
1629 match result {
1630 Ok(summary) => {
1631 let ns_iter = max(summary.median as u64, 1);
1632 let mb_s = bytes * 1000 / ns_iter;
1633 TestResult::benchmarked(elapsed, summary, mb_s as usize)
1634 }
1635 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
1636 }
1637 }
1638
1639 fn from_panic(
1640 should_panic: &ShouldPanic,
1641 elapsed: Duration,
1642 panic: Box<dyn Any + Send>,
1643 ) -> Self {
1644 let captured = crate::panic_hook::take_current_panic_capture();
1645
1646 let panic_cause = if let Some(cause) = captured {
1647 cause
1648 } else {
1649 let message = panic
1650 .downcast_ref::<String>()
1651 .cloned()
1652 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
1653 PanicCause {
1654 message,
1655 location: None,
1656 backtrace: None,
1657 }
1658 };
1659
1660 match should_panic {
1661 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
1662 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
1663 _ => TestResult::failed(
1664 elapsed,
1665 FailureCause::Panic(PanicCause {
1666 message: Some(format!(
1667 "Test panicked with unexpected message: {}",
1668 panic_cause.message.as_deref().unwrap_or_default()
1669 )),
1670 location: None,
1671 backtrace: None,
1672 }),
1673 ),
1674 },
1675 ShouldPanic::Yes => TestResult::passed(elapsed),
1676 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
1677 }
1678 }
1679
1680 pub(crate) fn failure_message(&self) -> Option<String> {
1681 self.failure_cause().map(|c| c.render())
1682 }
1683
1684 pub fn failure_cause(&self) -> Option<&FailureCause> {
1685 match self {
1686 TestResult::Failed { cause, .. } => Some(cause),
1687 _ => None,
1688 }
1689 }
1690}
1691
1692pub struct SuiteResult {
1693 pub passed: usize,
1694 pub failed: usize,
1695 pub ignored: usize,
1696 pub measured: usize,
1697 pub filtered_out: usize,
1698 pub exec_time: Duration,
1699}
1700
1701impl SuiteResult {
1702 pub fn from_test_results(
1703 registered_tests: &[RegisteredTest],
1704 results: &[(RegisteredTest, TestResult)],
1705 exec_time: Duration,
1706 ) -> Self {
1707 let passed = results
1708 .iter()
1709 .filter(|(_, result)| result.is_passed())
1710 .count();
1711 let measured = results
1712 .iter()
1713 .filter(|(_, result)| result.is_benchmarked())
1714 .count();
1715 let failed = results
1716 .iter()
1717 .filter(|(_, result)| result.is_failed())
1718 .count();
1719 let ignored = results
1720 .iter()
1721 .filter(|(_, result)| result.is_ignored())
1722 .count();
1723 let filtered_out = registered_tests.len() - results.len();
1724
1725 Self {
1726 passed,
1727 failed,
1728 ignored,
1729 measured,
1730 filtered_out,
1731 exec_time,
1732 }
1733 }
1734
1735 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
1736 if results.iter().any(|(_, result)| result.is_failed()) {
1737 ExitCode::from(101)
1738 } else {
1739 ExitCode::SUCCESS
1740 }
1741 }
1742}
1743
1744pub trait DependencyView: Debug {
1745 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
1746}
1747
1748impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
1749 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
1750 self.as_ref().get(name)
1751 }
1752}
1753
1754#[derive(Debug, Clone, Eq, PartialEq)]
1755pub enum CapturedOutput {
1756 Stdout { timestamp: SystemTime, line: String },
1757 Stderr { timestamp: SystemTime, line: String },
1758}
1759
1760impl CapturedOutput {
1761 pub fn stdout(line: String) -> Self {
1762 CapturedOutput::Stdout {
1763 timestamp: SystemTime::now(),
1764 line,
1765 }
1766 }
1767
1768 pub fn stderr(line: String) -> Self {
1769 CapturedOutput::Stderr {
1770 timestamp: SystemTime::now(),
1771 line,
1772 }
1773 }
1774
1775 pub fn timestamp(&self) -> SystemTime {
1776 match self {
1777 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
1778 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
1779 }
1780 }
1781
1782 pub fn line(&self) -> &str {
1783 match self {
1784 CapturedOutput::Stdout { line, .. } => line,
1785 CapturedOutput::Stderr { line, .. } => line,
1786 }
1787 }
1788}
1789
1790impl PartialOrd for CapturedOutput {
1791 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1792 Some(self.cmp(other))
1793 }
1794}
1795
1796impl Ord for CapturedOutput {
1797 fn cmp(&self, other: &Self) -> Ordering {
1798 self.timestamp().cmp(&other.timestamp())
1799 }
1800}
1801
1802#[cfg(test)]
1803mod error_reporting_tests {
1804 use super::*;
1805 use std::panic::{catch_unwind, AssertUnwindSafe};
1806 use std::time::Duration;
1807
1808 fn simulate_runner(
1809 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
1810 ) -> TestResult {
1811 crate::panic_hook::install_panic_hook();
1812 let test_id = crate::panic_hook::next_test_id();
1813 crate::panic_hook::set_current_test_id(test_id);
1814 let result = catch_unwind(AssertUnwindSafe(move || {
1815 let ret = test_fn();
1816 ret.into_result()?;
1817 Ok(())
1818 }));
1819 let test_result =
1820 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
1821 crate::panic_hook::clear_current_test_id();
1822 test_result
1823 }
1824
1825 #[test]
1826 fn panic_with_assert_eq() {
1827 let result = simulate_runner(|| {
1828 assert_eq!(1, 2);
1829 Box::new(())
1830 });
1831 assert!(result.is_failed());
1832 let msg = result.failure_message().unwrap();
1833 println!("=== panic assert_eq failure message ===\n{msg}\n===");
1834 assert!(
1835 msg.contains("assertion `left == right` failed"),
1836 "Expected assertion message, got: {msg}"
1837 );
1838 assert!(
1839 msg.contains("at "),
1840 "Expected location info in message, got: {msg}"
1841 );
1842 }
1843
1844 #[test]
1845 fn string_error() {
1846 let result = simulate_runner(|| {
1847 let r: Result<(), String> = Err("something went wrong".to_string());
1848 Box::new(r)
1849 });
1850 assert!(result.is_failed());
1851 let msg = result.failure_message().unwrap();
1852 println!("=== string error failure message ===\n{msg}\n===");
1853 assert_eq!(msg, "something went wrong");
1854 }
1855
1856 #[test]
1857 fn anyhow_error() {
1858 let result = simulate_runner(|| {
1859 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
1860 let err = anyhow::anyhow!(inner).context("operation failed");
1861 let r: Result<(), anyhow::Error> = Err(err);
1862 Box::new(r)
1863 });
1864 assert!(result.is_failed());
1865 let msg = result.failure_message().unwrap();
1866 println!("=== anyhow error failure message ===\n{msg}\n===");
1867 assert!(
1868 msg.contains("operation failed"),
1869 "Expected 'operation failed', got: {msg}"
1870 );
1871 assert!(
1872 msg.contains("file not found"),
1873 "Expected 'file not found', got: {msg}"
1874 );
1875 }
1876
1877 #[test]
1878 fn std_io_error() {
1879 let result = simulate_runner(|| {
1880 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
1881 std::io::ErrorKind::NotFound,
1882 "file not found",
1883 ));
1884 Box::new(r)
1885 });
1886 assert!(result.is_failed());
1887 let msg = result.failure_message().unwrap();
1888 println!("=== std io error failure message ===\n{msg}\n===");
1889 assert_eq!(msg, "file not found");
1891 }
1892
1893 #[test]
1894 fn panic_with_location_info() {
1895 let result = simulate_runner(|| {
1896 panic!("test panic with location");
1897 #[allow(unreachable_code)]
1898 Box::new(())
1899 });
1900 assert!(result.is_failed());
1901 let cause = result.failure_cause().unwrap();
1902 match cause {
1903 FailureCause::Panic(p) => {
1904 assert!(p.location.is_some(), "Expected location info");
1905 let loc = p.location.as_ref().unwrap();
1906 assert!(
1907 loc.file.contains("internal.rs"),
1908 "Expected file to contain internal.rs, got: {}",
1909 loc.file
1910 );
1911 assert!(loc.line > 0, "Expected non-zero line number");
1912 }
1913 other => panic!("Expected Panic cause, got: {other:?}"),
1914 }
1915 }
1916
1917 #[test]
1918 fn panic_render_includes_location() {
1919 let result = simulate_runner(|| {
1920 panic!("location test");
1921 #[allow(unreachable_code)]
1922 Box::new(())
1923 });
1924 let msg = result.failure_message().unwrap();
1925 assert!(
1926 msg.contains("location test"),
1927 "Expected panic message, got: {msg}"
1928 );
1929 assert!(
1930 msg.contains("\n at "),
1931 "Expected location line in render, got: {msg}"
1932 );
1933 }
1934
1935 #[test]
1936 fn should_panic_with_message_matching() {
1937 crate::panic_hook::install_panic_hook();
1938 let test_id = crate::panic_hook::next_test_id();
1939 crate::panic_hook::set_current_test_id(test_id);
1940 let result = catch_unwind(AssertUnwindSafe(|| {
1941 panic!("expected panic message");
1942 }));
1943 let test_result = TestResult::from_result(
1944 &ShouldPanic::WithMessage("expected panic".to_string()),
1945 Duration::from_millis(1),
1946 result.map(|_| Ok(())),
1947 );
1948 crate::panic_hook::clear_current_test_id();
1949 assert!(
1950 test_result.is_passed(),
1951 "Expected test to pass with matching panic message"
1952 );
1953 }
1954
1955 #[test]
1956 fn should_panic_with_wrong_message() {
1957 crate::panic_hook::install_panic_hook();
1958 let test_id = crate::panic_hook::next_test_id();
1959 crate::panic_hook::set_current_test_id(test_id);
1960 let result = catch_unwind(AssertUnwindSafe(|| {
1961 panic!("actual panic message");
1962 }));
1963 let test_result = TestResult::from_result(
1964 &ShouldPanic::WithMessage("completely different".to_string()),
1965 Duration::from_millis(1),
1966 result.map(|_| Ok(())),
1967 );
1968 crate::panic_hook::clear_current_test_id();
1969 assert!(
1970 test_result.is_failed(),
1971 "Expected test to fail with wrong panic message"
1972 );
1973 let msg = test_result.failure_message().unwrap();
1974 assert!(
1975 msg.contains("unexpected message"),
1976 "Expected 'unexpected message' in: {msg}"
1977 );
1978 }
1979
1980 #[test]
1981 fn pretty_assertions_diff() {
1982 let result = simulate_runner(|| {
1983 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
1984 Box::new(())
1985 });
1986 assert!(result.is_failed());
1987 let cause = result.failure_cause().unwrap();
1988
1989 let panic_cause = match cause {
1991 FailureCause::Panic(p) => p,
1992 other => panic!("Expected Panic cause, got: {other:?}"),
1993 };
1994
1995 let message = panic_cause.message.as_deref().unwrap();
1997 println!("=== pretty_assertions failure message ===\n{message}\n===");
1998 assert!(
1999 message.contains("foo") && message.contains("baz"),
2000 "Expected diff with 'foo' and 'baz', got: {message}"
2001 );
2002
2003 assert!(panic_cause.location.is_some(), "Expected location info");
2005
2006 let rendered = cause.render();
2008 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
2009 assert!(
2010 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
2011 "Expected no backtrace noise in rendered output, got: {rendered}"
2012 );
2013 assert!(
2015 rendered.contains("\n at "),
2016 "Expected location in rendered output, got: {rendered}"
2017 );
2018 }
2019
2020 #[test]
2021 fn detached_thread_panic_detected() {
2022 crate::panic_hook::install_panic_hook();
2023 let test_id = crate::panic_hook::next_test_id();
2024 crate::panic_hook::set_current_test_id(test_id);
2025 crate::panic_hook::create_detached_collector(test_id);
2026
2027 let result = catch_unwind(AssertUnwindSafe(|| {
2028 let handle = crate::spawn::spawn_thread(|| {
2029 panic!("background thread panic");
2030 });
2031 let _ = handle.join();
2032 }));
2033
2034 let mut test_result = TestResult::from_result(
2035 &ShouldPanic::No,
2036 Duration::from_millis(1),
2037 result.map(|_| Ok(())),
2038 );
2039
2040 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2041 let panics = match collector.lock() {
2042 Ok(p) => p,
2043 Err(poisoned) => poisoned.into_inner(),
2044 };
2045 if !panics.is_empty() && test_result.is_passed() {
2046 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
2047 test_result = TestResult::failed(
2048 Duration::from_millis(1),
2049 FailureCause::Panic(PanicCause {
2050 message: Some(format!(
2051 "Detached task(s) panicked:\n{}",
2052 messages.join("\n---\n")
2053 )),
2054 location: panics.first().and_then(|p| p.location.clone()),
2055 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
2056 }),
2057 );
2058 }
2059 }
2060
2061 crate::panic_hook::clear_current_test_id();
2062
2063 assert!(
2064 test_result.is_failed(),
2065 "Expected test to fail due to detached panic"
2066 );
2067 let msg = test_result.failure_message().unwrap();
2068 assert!(
2069 msg.contains("Detached task(s) panicked"),
2070 "Expected detached panic message, got: {msg}"
2071 );
2072 assert!(
2073 msg.contains("background thread panic"),
2074 "Expected original panic message, got: {msg}"
2075 );
2076 }
2077
2078 #[test]
2079 fn detached_thread_panic_ignored_with_policy() {
2080 crate::panic_hook::install_panic_hook();
2081 let test_id = crate::panic_hook::next_test_id();
2082 crate::panic_hook::set_current_test_id(test_id);
2083 crate::panic_hook::create_detached_collector(test_id);
2084
2085 let result = catch_unwind(AssertUnwindSafe(|| {
2086 let handle = crate::spawn::spawn_thread(|| {
2087 panic!("ignored thread panic");
2088 });
2089 let _ = handle.join();
2090 }));
2091
2092 let test_result = TestResult::from_result(
2093 &ShouldPanic::No,
2094 Duration::from_millis(1),
2095 result.map(|_| Ok(())),
2096 );
2097
2098 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2099 let panics = match collector.lock() {
2100 Ok(p) => p,
2101 Err(poisoned) => poisoned.into_inner(),
2102 };
2103 assert!(
2105 !panics.is_empty(),
2106 "Expected panics in collector even with Ignore policy"
2107 );
2108 }
2109
2110 crate::panic_hook::clear_current_test_id();
2111
2112 assert!(
2113 test_result.is_passed(),
2114 "Expected test to pass with Ignore policy"
2115 );
2116 }
2117
2118 #[cfg(feature = "tokio")]
2119 #[test]
2120 fn detached_task_panic_detected() {
2121 let rt = tokio::runtime::Runtime::new().unwrap();
2122 rt.block_on(async {
2123 crate::panic_hook::install_panic_hook();
2124 let test_id = crate::panic_hook::next_test_id();
2125 crate::panic_hook::set_current_test_id(test_id);
2126 crate::panic_hook::create_detached_collector(test_id);
2127
2128 let handle = crate::spawn::spawn(async {
2129 panic!("detached task panic");
2130 });
2131 let _ = handle.await;
2132
2133 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
2134 let panics = collector.lock().unwrap();
2135
2136 assert_eq!(panics.len(), 1);
2137 assert!(
2138 panics[0]
2139 .message
2140 .as_ref()
2141 .unwrap()
2142 .contains("detached task panic"),
2143 "Expected panic message, got: {:?}",
2144 panics[0].message
2145 );
2146
2147 crate::panic_hook::clear_current_test_id();
2148 });
2149 }
2150
2151 #[test]
2152 fn failure_cause_variants() {
2153 let cause = FailureCause::ReturnedMessage("simple message".to_string());
2155 assert_eq!(cause.render(), "simple message");
2156 assert!(cause.panic_message().is_none());
2157
2158 let cause = FailureCause::ReturnedError {
2160 display: "display text".to_string(),
2161 debug: "debug text".to_string(),
2162 prefer_debug: false,
2163 error: Arc::new("display text".to_string()),
2164 };
2165 assert_eq!(cause.render(), "display text");
2166
2167 let cause = FailureCause::ReturnedError {
2169 display: "display text".to_string(),
2170 debug: "debug text".to_string(),
2171 prefer_debug: true,
2172 error: Arc::new("debug text".to_string()),
2173 };
2174 assert_eq!(cause.render(), "debug text");
2175
2176 let cause = FailureCause::HarnessError("harness error".to_string());
2178 assert_eq!(cause.render(), "harness error");
2179
2180 let cause = FailureCause::Panic(PanicCause {
2182 message: Some("panic msg".to_string()),
2183 location: None,
2184 backtrace: None,
2185 });
2186 assert_eq!(cause.render(), "panic msg");
2187 assert_eq!(cause.panic_message(), Some("panic msg"));
2188 }
2189}
2190
2191#[cfg(test)]
2192mod filter_tests {
2193 use super::*;
2194
2195 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
2196 RegisteredTest {
2197 name: name.to_string(),
2198 crate_name: "mycrate".to_string(),
2199 module_path: module_path.to_string(),
2200 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
2201 props: TestProperties::default(),
2202 dependencies: None,
2203 }
2204 }
2205
2206 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
2207 let mut test = make_test(name, module_path);
2208 test.props.tags = tags.into_iter().map(String::from).collect();
2209 test
2210 }
2211
2212 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
2213 Arguments {
2214 filter: filters.into_iter().map(String::from).collect(),
2215 skip: skip.into_iter().map(String::from).collect(),
2216 exact,
2217 ..Default::default()
2218 }
2219 }
2220
2221 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
2222 filter_registered_tests(args, tests)
2223 .into_iter()
2224 .map(|t| t.filterable_name())
2225 .collect()
2226 }
2227
2228 #[test]
2231 fn filter_test_substring_match() {
2232 let test = make_test("hello_world", "mod1");
2233 assert!(filter_test(&test, "hello", false));
2234 assert!(filter_test(&test, "world", false));
2235 assert!(filter_test(&test, "mod1::hello", false));
2236 assert!(!filter_test(&test, "nonexistent", false));
2237 }
2238
2239 #[test]
2240 fn filter_test_exact_match() {
2241 let test = make_test("hello_world", "mod1");
2242 assert!(filter_test(&test, "mod1::hello_world", true));
2243 assert!(!filter_test(&test, "hello_world", true));
2244 assert!(!filter_test(&test, "hello", true));
2245 }
2246
2247 #[test]
2248 fn filter_test_tag_match() {
2249 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
2250 assert!(filter_test(&test, ":tag:fast", false));
2251 assert!(filter_test(&test, ":tag:unit", false));
2252 assert!(!filter_test(&test, ":tag:slow", false));
2253 }
2254
2255 #[test]
2256 fn filter_test_tag_empty_matches_untagged() {
2257 let untagged = make_test("t1", "mod1");
2258 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
2259 assert!(filter_test(&untagged, ":tag:", false));
2260 assert!(!filter_test(&tagged, ":tag:", false));
2261 }
2262
2263 #[test]
2266 fn no_filters_includes_all() {
2267 let tests = vec![make_test("a", "m"), make_test("b", "m")];
2268 let args = make_args(vec![], vec![], false);
2269 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
2270 }
2271
2272 #[test]
2273 fn single_filter_substring() {
2274 let tests = vec![
2275 make_test("alpha", "m"),
2276 make_test("beta", "m"),
2277 make_test("alphabet", "m"),
2278 ];
2279 let args = make_args(vec!["alpha"], vec![], false);
2280 assert_eq!(
2281 filtered_names(&args, &tests),
2282 vec!["m::alpha", "m::alphabet"]
2283 );
2284 }
2285
2286 #[test]
2287 fn multiple_filters_or_semantics() {
2288 let tests = vec![
2289 make_test("alpha", "m"),
2290 make_test("beta", "m"),
2291 make_test("gamma", "m"),
2292 ];
2293 let args = make_args(vec!["alpha", "gamma"], vec![], false);
2294 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
2295 }
2296
2297 #[test]
2298 fn multiple_filters_exact() {
2299 let tests = vec![
2300 make_test("alpha", "m"),
2301 make_test("alphabet", "m"),
2302 make_test("beta", "m"),
2303 ];
2304 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
2305 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
2306 }
2307
2308 #[test]
2311 fn skip_substring_match() {
2312 let tests = vec![
2313 make_test("fast_test", "m"),
2314 make_test("slow_test", "m"),
2315 make_test("slower_test", "m"),
2316 ];
2317 let args = make_args(vec![], vec!["slow"], false);
2318 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
2319 }
2320
2321 #[test]
2322 fn skip_exact_match() {
2323 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
2324 let args = make_args(vec![], vec!["m::slow_test"], true);
2325 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
2326 }
2327
2328 #[test]
2329 fn skip_with_tag() {
2330 let tests = vec![
2331 make_tagged_test("t1", "m", vec!["slow"]),
2332 make_tagged_test("t2", "m", vec!["fast"]),
2333 make_test("t3", "m"),
2334 ];
2335 let args = make_args(vec![], vec![":tag:slow"], false);
2336 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
2337 }
2338
2339 #[test]
2342 fn include_and_skip_combined() {
2343 let tests = vec![
2344 make_test("alpha_fast", "m"),
2345 make_test("alpha_slow", "m"),
2346 make_test("beta_fast", "m"),
2347 ];
2348 let args = make_args(vec!["alpha"], vec!["slow"], false);
2350 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
2351 }
2352
2353 #[test]
2354 fn skip_wins_over_include() {
2355 let tests = vec![make_test("target", "m")];
2356 let args = make_args(vec!["target"], vec!["target"], false);
2358 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
2359 }
2360
2361 #[test]
2364 fn filter_test_tag_or_expression() {
2365 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2367 let test_b = make_tagged_test("t2", "m", vec!["b"]);
2368 let test_c = make_tagged_test("t3", "m", vec!["c"]);
2369 assert!(filter_test(&test_a, ":tag:a|b", false));
2370 assert!(filter_test(&test_b, ":tag:a|b", false));
2371 assert!(!filter_test(&test_c, ":tag:a|b", false));
2372 }
2373
2374 #[test]
2375 fn filter_test_tag_and_expression() {
2376 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
2378 let test_a = make_tagged_test("t2", "m", vec!["a"]);
2379 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2380 assert!(filter_test(&test_ab, ":tag:a&b", false));
2381 assert!(!filter_test(&test_a, ":tag:a&b", false));
2382 assert!(!filter_test(&test_b, ":tag:a&b", false));
2383 }
2384
2385 #[test]
2386 fn filter_test_tag_mixed_and_or() {
2387 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2389 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
2390 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2391 let test_c = make_tagged_test("t4", "m", vec!["c"]);
2392 let test_none = make_test("t5", "m");
2393 assert!(filter_test(&test_a, ":tag:a|b&c", false));
2394 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
2395 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
2396 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
2397 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
2398 }
2399
2400 #[test]
2401 fn filter_test_tag_exact_flag_does_not_affect_tags() {
2402 let test = make_tagged_test("t1", "m", vec!["fast"]);
2404 assert!(filter_test(&test, ":tag:fast", true));
2405 assert!(!filter_test(&test, ":tag:slow", true));
2406 }
2407
2408 #[test]
2409 fn include_by_tag_or_expression() {
2410 let tests = vec![
2411 make_tagged_test("t1", "m", vec!["unit"]),
2412 make_tagged_test("t2", "m", vec!["integration"]),
2413 make_tagged_test("t3", "m", vec!["e2e"]),
2414 ];
2415 let args = make_args(vec![":tag:unit|integration"], vec![], false);
2416 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
2417 }
2418
2419 #[test]
2420 fn skip_by_tag_and_expression() {
2421 let tests = vec![
2422 make_tagged_test("t1", "m", vec!["slow", "network"]),
2423 make_tagged_test("t2", "m", vec!["slow"]),
2424 make_tagged_test("t3", "m", vec!["network"]),
2425 make_test("t4", "m"),
2426 ];
2427 let args = make_args(vec![], vec![":tag:slow&network"], false);
2429 assert_eq!(
2430 filtered_names(&args, &tests),
2431 vec!["m::t2", "m::t3", "m::t4"]
2432 );
2433 }
2434}