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 dispatch(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
943 match &self.inner {
944 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
945 #[cfg(feature = "tokio")]
946 HostedRpcOwnerCellInner::Async(_) => Err(
947 "hosted rpc owner cell uses the async dispatch path; use dispatch_async or dispatch_blocking"
948 .to_string(),
949 ),
950 }
951 }
952
953 #[cfg(feature = "tokio")]
962 pub async fn dispatch_async(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
963 match &self.inner {
964 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
965 HostedRpcOwnerCellInner::Async(cell) => {
966 async_dispatch_inner(cell, method_idx, args).await
967 }
968 }
969 }
970
971 #[cfg(feature = "tokio")]
982 pub fn dispatch_blocking(&self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
983 match &self.inner {
984 HostedRpcOwnerCellInner::Sync(mtx) => sync_dispatch_inner(mtx, method_idx, args),
985 HostedRpcOwnerCellInner::Async(cell) => {
986 let handle = tokio::runtime::Handle::try_current().map_err(|_| {
987 "hosted rpc owner is async-only and no Tokio runtime is active at the dispatch site"
988 .to_string()
989 })?;
990 if !matches!(
996 handle.runtime_flavor(),
997 tokio::runtime::RuntimeFlavor::MultiThread
998 ) {
999 return Err(
1000 "hosted rpc owner is async-only and the current Tokio runtime is not multi-threaded"
1001 .to_string(),
1002 );
1003 }
1004 tokio::task::block_in_place(|| {
1005 handle.block_on(async_dispatch_inner(cell, method_idx, args))
1006 })
1007 }
1008 }
1009 }
1010}
1011
1012fn sync_dispatch_inner(
1013 mtx: &Mutex<Box<dyn HostedRpcDispatcher>>,
1014 method_idx: u32,
1015 args: &[u8],
1016) -> Result<Vec<u8>, String> {
1017 let dispatch_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1025 let mut guard = match mtx.lock() {
1026 Ok(g) => g,
1027 Err(_) => return Err("hosted rpc owner poisoned".to_string()),
1028 };
1029 guard.dispatch(method_idx, args)
1030 }));
1031 panic_payload_to_err(dispatch_result)
1032}
1033
1034#[cfg(feature = "tokio")]
1035async fn async_dispatch_inner(
1036 cell: &AsyncOwnerCell,
1037 method_idx: u32,
1038 args: &[u8],
1039) -> Result<Vec<u8>, String> {
1040 use futures::FutureExt;
1041 use std::sync::atomic::Ordering;
1042
1043 if cell.poisoned.load(Ordering::SeqCst) {
1046 return Err("hosted rpc owner poisoned".to_string());
1047 }
1048 let mut guard = cell.inner.lock().await;
1049 if cell.poisoned.load(Ordering::SeqCst) {
1057 return Err("hosted rpc owner poisoned".to_string());
1058 }
1059 let fut = std::panic::AssertUnwindSafe(async {
1060 AsyncHostedRpcDispatcher::dispatch(&mut **guard, method_idx, args).await
1061 });
1062 let outcome = fut.catch_unwind().await;
1063 match outcome {
1064 Ok(r) => {
1065 drop(guard);
1066 r
1067 }
1068 Err(payload) => {
1069 cell.poisoned.store(true, Ordering::SeqCst);
1074 drop(guard);
1075 let msg = panic_payload_to_string(&payload);
1076 Err(format!("hosted rpc owner panicked: {msg}"))
1077 }
1078 }
1079}
1080
1081fn panic_payload_to_err(
1082 dispatch_result: Result<Result<Vec<u8>, String>, Box<dyn Any + Send>>,
1083) -> Result<Vec<u8>, String> {
1084 match dispatch_result {
1085 Ok(r) => r,
1086 Err(payload) => {
1087 let msg = panic_payload_to_string(&payload);
1088 Err(format!("hosted rpc owner panicked: {msg}"))
1089 }
1090 }
1091}
1092
1093fn panic_payload_to_string(payload: &Box<dyn Any + Send>) -> String {
1094 if let Some(s) = payload.downcast_ref::<&str>() {
1095 (*s).to_string()
1096 } else if let Some(s) = payload.downcast_ref::<String>() {
1097 s.clone()
1098 } else {
1099 "<non-string panic payload>".to_string()
1100 }
1101}
1102
1103pub struct HostedBothShared {
1123 descriptor_bytes: Vec<u8>,
1124 rpc_cell: Arc<HostedRpcOwnerCell>,
1125}
1126
1127impl HostedBothShared {
1128 pub fn new(descriptor_bytes: Vec<u8>, rpc_cell: Arc<HostedRpcOwnerCell>) -> Self {
1131 Self {
1132 descriptor_bytes,
1133 rpc_cell,
1134 }
1135 }
1136
1137 pub fn descriptor_bytes(&self) -> &[u8] {
1140 &self.descriptor_bytes
1141 }
1142
1143 pub fn rpc_cell(&self) -> Arc<HostedRpcOwnerCell> {
1147 self.rpc_cell.clone()
1148 }
1149}
1150
1151#[derive(Debug, Clone)]
1153pub enum HostedRpcError {
1154 Dispatch(String),
1157 Transport(String),
1160}
1161
1162impl std::fmt::Display for HostedRpcError {
1163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1164 match self {
1165 HostedRpcError::Dispatch(s) => write!(f, "hosted rpc dispatch error: {s}"),
1166 HostedRpcError::Transport(s) => write!(f, "hosted rpc transport error: {s}"),
1167 }
1168 }
1169}
1170
1171impl std::error::Error for HostedRpcError {}
1172
1173pub trait HostedRpcTransport: Send + Sync {
1178 fn call(&self, dep_id: &str, method_idx: u32, args: Vec<u8>)
1182 -> Result<Vec<u8>, HostedRpcError>;
1183}
1184
1185pub struct HostedRpcChannel {
1191 dep_id: String,
1192 transport: Arc<dyn HostedRpcTransport>,
1193}
1194
1195impl HostedRpcChannel {
1196 pub fn new(dep_id: String, transport: Arc<dyn HostedRpcTransport>) -> Self {
1199 Self { dep_id, transport }
1200 }
1201
1202 pub fn dep_id(&self) -> &str {
1205 &self.dep_id
1206 }
1207
1208 pub fn call(&self, method_idx: u32, args: Vec<u8>) -> Result<Vec<u8>, HostedRpcError> {
1233 self.transport.call(&self.dep_id, method_idx, args)
1234 }
1235}
1236
1237impl Clone for HostedRpcChannel {
1238 fn clone(&self) -> Self {
1239 Self {
1240 dep_id: self.dep_id.clone(),
1241 transport: self.transport.clone(),
1242 }
1243 }
1244}
1245
1246pub struct InProcessHostedRpcTransport {
1250 cells: HashMap<String, Arc<HostedRpcOwnerCell>>,
1251}
1252
1253impl InProcessHostedRpcTransport {
1254 pub fn new(cells: HashMap<String, Arc<HostedRpcOwnerCell>>) -> Self {
1255 Self { cells }
1256 }
1257}
1258
1259impl HostedRpcTransport for InProcessHostedRpcTransport {
1260 fn call(
1261 &self,
1262 dep_id: &str,
1263 method_idx: u32,
1264 args: Vec<u8>,
1265 ) -> Result<Vec<u8>, HostedRpcError> {
1266 let cell = self.cells.get(dep_id).ok_or_else(|| {
1267 HostedRpcError::Transport(format!("in-process HostedRpc: unknown dep id '{dep_id}'"))
1268 })?;
1269 #[cfg(feature = "tokio")]
1274 let result = cell.dispatch_blocking(method_idx, &args);
1275 #[cfg(not(feature = "tokio"))]
1276 let result = cell.dispatch(method_idx, &args);
1277 result.map_err(HostedRpcError::Dispatch)
1278 }
1279}
1280
1281#[derive(Clone)]
1286#[allow(clippy::type_complexity)]
1287pub struct RpcFactory {
1288 pub owner_into_cell: Arc<
1291 dyn (Fn(Arc<dyn Any + Send + Sync>) -> Arc<HostedRpcOwnerCell>) + Send + Sync + 'static,
1292 >,
1293 pub build_stub:
1296 Arc<dyn (Fn(HostedRpcChannel) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
1297}
1298
1299#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)]
1304pub enum DepScope {
1305 #[default]
1309 Shared,
1310 PerWorker,
1313 Cloneable,
1317 Hosted,
1326 HostedRpc,
1333}
1334
1335impl DepScope {
1336 pub fn requires_single_thread_when_capturing(&self) -> bool {
1339 matches!(self, DepScope::Shared)
1340 }
1341
1342 pub fn parent_must_materialize_under_spawn_workers(&self) -> bool {
1348 matches!(
1349 self,
1350 DepScope::Cloneable | DepScope::Hosted | DepScope::HostedRpc
1351 )
1352 }
1353}
1354
1355#[derive(Clone)]
1360#[allow(clippy::type_complexity)]
1361pub enum WorkerReconstructor {
1362 Sync(
1363 Arc<
1364 dyn (Fn(
1365 Arc<dyn Any + Send + Sync>,
1366 Arc<dyn DependencyView + Send + Sync>,
1367 ) -> Arc<dyn Any + Send + Sync + 'static>)
1368 + Send
1369 + Sync
1370 + 'static,
1371 >,
1372 ),
1373 Async(
1374 Arc<
1375 dyn (Fn(
1376 Arc<dyn Any + Send + Sync>,
1377 Arc<dyn DependencyView + Send + Sync>,
1378 ) -> Pin<Box<dyn Future<Output = Arc<dyn Any + Send + Sync>>>>)
1379 + Send
1380 + Sync
1381 + 'static,
1382 >,
1383 ),
1384}
1385
1386#[derive(Clone)]
1390#[allow(clippy::type_complexity)]
1391pub struct CloneableCodec {
1392 pub to_wire: Arc<dyn (Fn(Arc<dyn Any + Send + Sync>) -> Vec<u8>) + Send + Sync + 'static>,
1395 pub from_wire_bytes: Arc<dyn (Fn(&[u8]) -> Arc<dyn Any + Send + Sync>) + Send + Sync + 'static>,
1398}
1399
1400#[derive(Clone)]
1401pub struct RegisteredDependency {
1402 pub name: String, pub crate_name: String,
1404 pub module_path: String,
1405 pub constructor: DependencyConstructor,
1406 pub dependencies: Vec<String>,
1407 pub scope: DepScope,
1410 pub worker_fn: Option<WorkerReconstructor>,
1415 pub cloneable_codec: Option<CloneableCodec>,
1419 pub hosted_codec: Option<CloneableCodec>,
1426 pub rpc_factory: Option<RpcFactory>,
1431 pub companions: Vec<String>,
1450}
1451
1452impl RegisteredDependency {
1453 pub fn new_shared(
1457 name: String,
1458 crate_name: String,
1459 module_path: String,
1460 constructor: DependencyConstructor,
1461 dependencies: Vec<String>,
1462 ) -> Self {
1463 Self {
1464 name,
1465 crate_name,
1466 module_path,
1467 constructor,
1468 dependencies,
1469 scope: DepScope::Shared,
1470 worker_fn: None,
1471 cloneable_codec: None,
1472 hosted_codec: None,
1473 rpc_factory: None,
1474 companions: Vec::new(),
1475 }
1476 }
1477}
1478
1479impl Debug for RegisteredDependency {
1480 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1481 f.debug_struct("RegisteredDependency")
1482 .field("name", &self.name)
1483 .field("crate_name", &self.crate_name)
1484 .field("module_path", &self.module_path)
1485 .finish()
1486 }
1487}
1488
1489impl PartialEq for RegisteredDependency {
1490 fn eq(&self, other: &Self) -> bool {
1491 self.name == other.name
1492 }
1493}
1494
1495impl Eq for RegisteredDependency {}
1496
1497impl Hash for RegisteredDependency {
1498 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1499 self.name.hash(state);
1500 }
1501}
1502
1503impl RegisteredDependency {
1504 pub fn crate_and_module(&self) -> String {
1505 [&self.crate_name, &self.module_path]
1506 .into_iter()
1507 .filter(|s| !s.is_empty())
1508 .cloned()
1509 .collect::<Vec<String>>()
1510 .join("::")
1511 }
1512
1513 pub fn qualified_id(&self) -> String {
1518 [&self.crate_name, &self.module_path, &self.name]
1519 .into_iter()
1520 .filter(|s| !s.is_empty())
1521 .cloned()
1522 .collect::<Vec<String>>()
1523 .join("::")
1524 }
1525}
1526
1527pub static REGISTERED_DEPENDENCY_CONSTRUCTORS: Mutex<Vec<RegisteredDependency>> =
1528 Mutex::new(Vec::new());
1529
1530#[derive(Debug, Clone)]
1531pub enum RegisteredTestSuiteProperty {
1532 Sequential {
1533 name: String,
1534 crate_name: String,
1535 module_path: String,
1536 },
1537 Tag {
1538 name: String,
1539 crate_name: String,
1540 module_path: String,
1541 tag: String,
1542 },
1543 Timeout {
1544 name: String,
1545 crate_name: String,
1546 module_path: String,
1547 timeout: Duration,
1548 },
1549}
1550
1551impl RegisteredTestSuiteProperty {
1552 pub fn crate_name(&self) -> &String {
1553 match self {
1554 RegisteredTestSuiteProperty::Sequential { crate_name, .. } => crate_name,
1555 RegisteredTestSuiteProperty::Tag { crate_name, .. } => crate_name,
1556 RegisteredTestSuiteProperty::Timeout { crate_name, .. } => crate_name,
1557 }
1558 }
1559
1560 pub fn module_path(&self) -> &String {
1561 match self {
1562 RegisteredTestSuiteProperty::Sequential { module_path, .. } => module_path,
1563 RegisteredTestSuiteProperty::Tag { module_path, .. } => module_path,
1564 RegisteredTestSuiteProperty::Timeout { module_path, .. } => module_path,
1565 }
1566 }
1567
1568 pub fn name(&self) -> &String {
1569 match self {
1570 RegisteredTestSuiteProperty::Sequential { name, .. } => name,
1571 RegisteredTestSuiteProperty::Tag { name, .. } => name,
1572 RegisteredTestSuiteProperty::Timeout { name, .. } => name,
1573 }
1574 }
1575
1576 pub fn crate_and_module(&self) -> String {
1577 [self.crate_name(), self.module_path(), self.name()]
1578 .into_iter()
1579 .filter(|s| !s.is_empty())
1580 .cloned()
1581 .collect::<Vec<String>>()
1582 .join("::")
1583 }
1584}
1585
1586pub static REGISTERED_TESTSUITE_PROPS: Mutex<Vec<RegisteredTestSuiteProperty>> =
1587 Mutex::new(Vec::new());
1588
1589#[derive(Clone)]
1590#[allow(clippy::type_complexity)]
1591pub enum TestGeneratorFunction {
1592 Sync(Arc<dyn Fn() -> Vec<GeneratedTest> + Send + Sync + 'static>),
1593 Async(
1594 Arc<
1595 dyn (Fn() -> Pin<Box<dyn Future<Output = Vec<GeneratedTest>> + Send>>)
1596 + Send
1597 + Sync
1598 + 'static,
1599 >,
1600 ),
1601}
1602
1603pub struct DynamicTestRegistration {
1604 tests: Vec<GeneratedTest>,
1605}
1606
1607impl Default for DynamicTestRegistration {
1608 fn default() -> Self {
1609 Self::new()
1610 }
1611}
1612
1613impl DynamicTestRegistration {
1614 pub fn new() -> Self {
1615 Self { tests: Vec::new() }
1616 }
1617
1618 pub fn to_vec(self) -> Vec<GeneratedTest> {
1619 self.tests
1620 }
1621
1622 pub fn add_sync_test<R: TestReturnValue + 'static>(
1623 &mut self,
1624 name: impl AsRef<str>,
1625 props: TestProperties,
1626 dependencies: Option<Vec<String>>,
1627 run: impl Fn(Arc<dyn DependencyView + Send + Sync>) -> R + Send + Sync + Clone + 'static,
1628 ) {
1629 self.tests.push(GeneratedTest {
1630 name: name.as_ref().to_string(),
1631 run: TestFunction::Sync(Arc::new(move |deps| {
1632 Box::new(run(deps)) as Box<dyn TestReturnValue>
1633 })),
1634 props,
1635 dependencies,
1636 });
1637 }
1638
1639 #[cfg(feature = "tokio")]
1640 pub fn add_async_test<R: TestReturnValue + 'static>(
1641 &mut self,
1642 name: impl AsRef<str>,
1643 props: TestProperties,
1644 dependencies: Option<Vec<String>>,
1645 run: impl (Fn(Arc<dyn DependencyView + Send + Sync>) -> Pin<Box<dyn Future<Output = R> + Send>>)
1646 + Send
1647 + Sync
1648 + Clone
1649 + 'static,
1650 ) {
1651 self.tests.push(GeneratedTest {
1652 name: name.as_ref().to_string(),
1653 run: TestFunction::Async(Arc::new(move |deps| {
1654 let run = run.clone();
1655 Box::pin(async move {
1656 let r = run(deps).await;
1657 Box::new(r) as Box<dyn TestReturnValue>
1658 })
1659 })),
1660 props,
1661 dependencies,
1662 });
1663 }
1664}
1665
1666#[derive(Clone)]
1667pub struct GeneratedTest {
1668 pub name: String,
1669 pub run: TestFunction,
1670 pub props: TestProperties,
1671 pub dependencies: Option<Vec<String>>,
1672}
1673
1674#[derive(Clone)]
1675pub struct RegisteredTestGenerator {
1676 pub name: String,
1677 pub crate_name: String,
1678 pub module_path: String,
1679 pub run: TestGeneratorFunction,
1680 pub is_ignored: bool,
1681}
1682
1683impl RegisteredTestGenerator {
1684 pub fn crate_and_module(&self) -> String {
1685 [&self.crate_name, &self.module_path]
1686 .into_iter()
1687 .filter(|s| !s.is_empty())
1688 .cloned()
1689 .collect::<Vec<String>>()
1690 .join("::")
1691 }
1692}
1693
1694pub static REGISTERED_TEST_GENERATORS: Mutex<Vec<RegisteredTestGenerator>> = Mutex::new(Vec::new());
1695
1696pub(crate) fn filter_test(test: &RegisteredTest, filter: &str, exact: bool) -> bool {
1697 if let Some(tag_list) = filter.strip_prefix(":tag:") {
1698 if tag_list.is_empty() {
1699 test.props.tags.is_empty()
1701 } else {
1702 let or_tags = tag_list.split('|').collect::<Vec<&str>>();
1703 let mut result = false;
1704 for or_tag in or_tags {
1705 let and_tags = or_tag.split('&').collect::<Vec<&str>>();
1706 let mut and_result = true;
1707 for and_tag in and_tags {
1708 if !test.props.tags.contains(&and_tag.to_string()) {
1709 and_result = false;
1710 break;
1711 }
1712 }
1713 if and_result {
1714 result = true;
1715 break;
1716 }
1717 }
1718 result
1719 }
1720 } else if exact {
1721 test.filterable_name() == filter
1722 } else {
1723 test.filterable_name().contains(filter)
1724 }
1725}
1726
1727pub(crate) fn apply_suite_props_to_tests(
1728 tests: &[RegisteredTest],
1729 props: &[RegisteredTestSuiteProperty],
1730) -> Vec<RegisteredTest> {
1731 let props_with_prefix = props
1732 .iter()
1733 .map(|prop| (prop.crate_and_module(), prop))
1734 .collect::<Vec<_>>();
1735
1736 let mut result = Vec::new();
1737 for test in tests {
1738 let mut test = test.clone();
1739 for (prefix, prop) in &props_with_prefix {
1740 if test.crate_and_module().starts_with(prefix) {
1741 match prop {
1742 RegisteredTestSuiteProperty::Tag { tag, .. } => {
1743 test.props.tags.push(tag.clone());
1744 }
1745 RegisteredTestSuiteProperty::Timeout { timeout, .. } => {
1746 if test.props.timeout.is_none() {
1747 test.props.timeout = Some(*timeout);
1748 }
1749 }
1750 RegisteredTestSuiteProperty::Sequential { .. } => {
1751 }
1753 }
1754 }
1755 }
1756 result.push(test);
1757 }
1758 result
1759}
1760
1761pub(crate) fn filter_registered_tests(
1762 args: &Arguments,
1763 registered_tests: &[RegisteredTest],
1764) -> Vec<RegisteredTest> {
1765 registered_tests
1766 .iter()
1767 .filter(|registered_test| {
1768 !args
1769 .skip
1770 .iter()
1771 .any(|skip| filter_test(registered_test, skip, args.exact))
1772 })
1773 .filter(|registered_test| {
1774 args.filter.is_empty()
1775 || args
1776 .filter
1777 .iter()
1778 .any(|filter| filter_test(registered_test, filter, args.exact))
1779 })
1780 .filter(|registered_tests| {
1781 (args.bench && registered_tests.run.is_bench())
1782 || (args.test && !registered_tests.run.is_bench())
1783 || (!args.bench && !args.test)
1784 })
1785 .filter(|registered_test| {
1786 !args.exclude_should_panic || registered_test.props.should_panic == ShouldPanic::No
1787 })
1788 .cloned()
1789 .collect::<Vec<_>>()
1790}
1791
1792fn add_generated_tests(
1793 target: &mut Vec<RegisteredTest>,
1794 generator: &RegisteredTestGenerator,
1795 generated: Vec<GeneratedTest>,
1796) {
1797 target.extend(generated.into_iter().map(|mut test| {
1798 test.props.is_ignored |= generator.is_ignored;
1799 RegisteredTest {
1800 name: format!("{}::{}", generator.name, test.name),
1801 crate_name: generator.crate_name.clone(),
1802 module_path: generator.module_path.clone(),
1803 run: test.run,
1804 props: test.props,
1805 dependencies: test.dependencies,
1806 }
1807 }));
1808}
1809
1810#[cfg(feature = "tokio")]
1811pub(crate) async fn generate_tests(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1812 let mut result = Vec::new();
1813 for generator in generators {
1814 match &generator.run {
1815 TestGeneratorFunction::Sync(generator_fn) => {
1816 let tests = generator_fn();
1817 add_generated_tests(&mut result, generator, tests);
1818 }
1819 TestGeneratorFunction::Async(generator_fn) => {
1820 let tests = generator_fn().await;
1821 add_generated_tests(&mut result, generator, tests);
1822 }
1823 }
1824 }
1825 result
1826}
1827
1828pub(crate) fn generate_tests_sync(generators: &[RegisteredTestGenerator]) -> Vec<RegisteredTest> {
1829 let mut result = Vec::new();
1830 for generator in generators {
1831 match &generator.run {
1832 TestGeneratorFunction::Sync(generator_fn) => {
1833 let tests = generator_fn();
1834 add_generated_tests(&mut result, generator, tests);
1835 }
1836 TestGeneratorFunction::Async(_) => {
1837 panic!("Async test generators are not supported in sync mode")
1838 }
1839 }
1840 }
1841 result
1842}
1843
1844pub(crate) fn get_ensure_time(args: &Arguments, test: &RegisteredTest) -> Option<TimeThreshold> {
1845 let should_ensure_time = match test.props.ensure_time_control {
1846 ReportTimeControl::Default => args.ensure_time,
1847 ReportTimeControl::Enabled => true,
1848 ReportTimeControl::Disabled => false,
1849 };
1850 if should_ensure_time {
1851 match test.props.test_type {
1852 TestType::UnitTest => Some(args.unit_test_threshold()),
1853 TestType::IntegrationTest => Some(args.integration_test_threshold()),
1854 }
1855 } else {
1856 None
1857 }
1858}
1859
1860#[derive(Clone)]
1861pub enum TestResult {
1862 Passed {
1863 captured: Vec<CapturedOutput>,
1864 exec_time: Duration,
1865 },
1866 Benchmarked {
1867 captured: Vec<CapturedOutput>,
1868 exec_time: Duration,
1869 ns_iter_summ: Summary,
1870 mb_s: usize,
1871 },
1872 Failed {
1873 cause: FailureCause,
1874 captured: Vec<CapturedOutput>,
1875 exec_time: Duration,
1876 },
1877 Ignored {
1878 captured: Vec<CapturedOutput>,
1879 },
1880}
1881
1882impl TestResult {
1883 pub fn passed(exec_time: Duration) -> Self {
1884 TestResult::Passed {
1885 captured: Vec::new(),
1886 exec_time,
1887 }
1888 }
1889
1890 pub fn benchmarked(exec_time: Duration, ns_iter_summ: Summary, mb_s: usize) -> Self {
1891 TestResult::Benchmarked {
1892 captured: Vec::new(),
1893 exec_time,
1894 ns_iter_summ,
1895 mb_s,
1896 }
1897 }
1898
1899 pub fn failed(exec_time: Duration, cause: FailureCause) -> Self {
1900 TestResult::Failed {
1901 cause,
1902 captured: Vec::new(),
1903 exec_time,
1904 }
1905 }
1906
1907 pub fn ignored() -> Self {
1908 TestResult::Ignored {
1909 captured: Vec::new(),
1910 }
1911 }
1912
1913 pub(crate) fn is_passed(&self) -> bool {
1914 matches!(self, TestResult::Passed { .. })
1915 }
1916
1917 pub(crate) fn is_benchmarked(&self) -> bool {
1918 matches!(self, TestResult::Benchmarked { .. })
1919 }
1920
1921 pub(crate) fn is_failed(&self) -> bool {
1922 matches!(self, TestResult::Failed { .. })
1923 }
1924
1925 pub(crate) fn is_ignored(&self) -> bool {
1926 matches!(self, TestResult::Ignored { .. })
1927 }
1928
1929 pub(crate) fn captured_output(&self) -> &Vec<CapturedOutput> {
1930 match self {
1931 TestResult::Passed { captured, .. } => captured,
1932 TestResult::Failed { captured, .. } => captured,
1933 TestResult::Ignored { captured, .. } => captured,
1934 TestResult::Benchmarked { captured, .. } => captured,
1935 }
1936 }
1937
1938 pub(crate) fn stats(&self) -> Option<&Summary> {
1939 match self {
1940 TestResult::Benchmarked { ns_iter_summ, .. } => Some(ns_iter_summ),
1941 _ => None,
1942 }
1943 }
1944
1945 pub(crate) fn set_captured_output(&mut self, captured: Vec<CapturedOutput>) {
1946 match self {
1947 TestResult::Passed {
1948 captured: captured_ref,
1949 ..
1950 } => *captured_ref = captured,
1951 TestResult::Failed {
1952 captured: captured_ref,
1953 ..
1954 } => *captured_ref = captured,
1955 TestResult::Ignored {
1956 captured: captured_ref,
1957 } => *captured_ref = captured,
1958 TestResult::Benchmarked {
1959 captured: captured_ref,
1960 ..
1961 } => *captured_ref = captured,
1962 }
1963 }
1964
1965 pub(crate) fn from_result<A>(
1966 should_panic: &ShouldPanic,
1967 elapsed: Duration,
1968 result: Result<Result<A, FailureCause>, Box<dyn Any + Send>>,
1969 ) -> Self {
1970 match result {
1971 Ok(Ok(_)) => {
1972 if should_panic == &ShouldPanic::No {
1973 TestResult::passed(elapsed)
1974 } else {
1975 TestResult::failed(
1976 elapsed,
1977 FailureCause::HarnessError("Test did not panic as expected".to_string()),
1978 )
1979 }
1980 }
1981 Ok(Err(cause)) => TestResult::failed(elapsed, cause),
1982 Err(panic) => TestResult::from_panic(should_panic, elapsed, panic),
1983 }
1984 }
1985
1986 pub(crate) fn from_summary(
1987 should_panic: &ShouldPanic,
1988 elapsed: Duration,
1989 result: Result<Summary, Box<dyn Any + Send>>,
1990 bytes: u64,
1991 ) -> Self {
1992 match result {
1993 Ok(summary) => {
1994 let ns_iter = max(summary.median as u64, 1);
1995 let mb_s = bytes * 1000 / ns_iter;
1996 TestResult::benchmarked(elapsed, summary, mb_s as usize)
1997 }
1998 Err(panic) => Self::from_panic(should_panic, elapsed, panic),
1999 }
2000 }
2001
2002 fn from_panic(
2003 should_panic: &ShouldPanic,
2004 elapsed: Duration,
2005 panic: Box<dyn Any + Send>,
2006 ) -> Self {
2007 let captured = crate::panic_hook::take_current_panic_capture();
2008
2009 let panic_cause = if let Some(cause) = captured {
2010 cause
2011 } else {
2012 let message = panic
2013 .downcast_ref::<String>()
2014 .cloned()
2015 .or(panic.downcast_ref::<&str>().map(|s| s.to_string()));
2016 PanicCause {
2017 message,
2018 location: None,
2019 backtrace: None,
2020 }
2021 };
2022
2023 match should_panic {
2024 ShouldPanic::WithMessage(expected) => match &panic_cause.message {
2025 Some(message) if message.contains(expected) => TestResult::passed(elapsed),
2026 _ => TestResult::failed(
2027 elapsed,
2028 FailureCause::Panic(PanicCause {
2029 message: Some(format!(
2030 "Test panicked with unexpected message: {}",
2031 panic_cause.message.as_deref().unwrap_or_default()
2032 )),
2033 location: None,
2034 backtrace: None,
2035 }),
2036 ),
2037 },
2038 ShouldPanic::Yes => TestResult::passed(elapsed),
2039 ShouldPanic::No => TestResult::failed(elapsed, FailureCause::Panic(panic_cause)),
2040 }
2041 }
2042
2043 pub(crate) fn failure_message(&self) -> Option<String> {
2044 self.failure_cause().map(|c| c.render())
2045 }
2046
2047 pub fn failure_cause(&self) -> Option<&FailureCause> {
2048 match self {
2049 TestResult::Failed { cause, .. } => Some(cause),
2050 _ => None,
2051 }
2052 }
2053}
2054
2055pub struct SuiteResult {
2056 pub passed: usize,
2057 pub failed: usize,
2058 pub ignored: usize,
2059 pub measured: usize,
2060 pub filtered_out: usize,
2061 pub exec_time: Duration,
2062}
2063
2064impl SuiteResult {
2065 pub fn from_test_results(
2066 registered_tests: &[RegisteredTest],
2067 results: &[(RegisteredTest, TestResult)],
2068 exec_time: Duration,
2069 ) -> Self {
2070 let passed = results
2071 .iter()
2072 .filter(|(_, result)| result.is_passed())
2073 .count();
2074 let measured = results
2075 .iter()
2076 .filter(|(_, result)| result.is_benchmarked())
2077 .count();
2078 let failed = results
2079 .iter()
2080 .filter(|(_, result)| result.is_failed())
2081 .count();
2082 let ignored = results
2083 .iter()
2084 .filter(|(_, result)| result.is_ignored())
2085 .count();
2086 let filtered_out = registered_tests.len() - results.len();
2087
2088 Self {
2089 passed,
2090 failed,
2091 ignored,
2092 measured,
2093 filtered_out,
2094 exec_time,
2095 }
2096 }
2097
2098 pub fn exit_code(results: &[(RegisteredTest, TestResult)]) -> ExitCode {
2099 if results.iter().any(|(_, result)| result.is_failed()) {
2100 ExitCode::from(101)
2101 } else {
2102 ExitCode::SUCCESS
2103 }
2104 }
2105}
2106
2107pub trait DependencyView: Debug {
2108 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>>;
2109}
2110
2111impl DependencyView for Arc<dyn DependencyView + Send + Sync> {
2112 fn get(&self, name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
2113 self.as_ref().get(name)
2114 }
2115}
2116
2117#[derive(Debug, Clone, Eq, PartialEq)]
2118pub enum CapturedOutput {
2119 Stdout { timestamp: SystemTime, line: String },
2120 Stderr { timestamp: SystemTime, line: String },
2121}
2122
2123impl CapturedOutput {
2124 pub fn stdout(line: String) -> Self {
2125 CapturedOutput::Stdout {
2126 timestamp: SystemTime::now(),
2127 line,
2128 }
2129 }
2130
2131 pub fn stderr(line: String) -> Self {
2132 CapturedOutput::Stderr {
2133 timestamp: SystemTime::now(),
2134 line,
2135 }
2136 }
2137
2138 pub fn timestamp(&self) -> SystemTime {
2139 match self {
2140 CapturedOutput::Stdout { timestamp, .. } => *timestamp,
2141 CapturedOutput::Stderr { timestamp, .. } => *timestamp,
2142 }
2143 }
2144
2145 pub fn line(&self) -> &str {
2146 match self {
2147 CapturedOutput::Stdout { line, .. } => line,
2148 CapturedOutput::Stderr { line, .. } => line,
2149 }
2150 }
2151}
2152
2153impl PartialOrd for CapturedOutput {
2154 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2155 Some(self.cmp(other))
2156 }
2157}
2158
2159impl Ord for CapturedOutput {
2160 fn cmp(&self, other: &Self) -> Ordering {
2161 self.timestamp().cmp(&other.timestamp())
2162 }
2163}
2164
2165#[cfg(test)]
2166mod error_reporting_tests {
2167 use super::*;
2168 use std::panic::{catch_unwind, AssertUnwindSafe};
2169 use std::time::Duration;
2170
2171 fn simulate_runner(
2172 test_fn: impl FnOnce() -> Box<dyn TestReturnValue> + std::panic::UnwindSafe,
2173 ) -> TestResult {
2174 crate::panic_hook::install_panic_hook();
2175 let test_id = crate::panic_hook::next_test_id();
2176 crate::panic_hook::set_current_test_id(test_id);
2177 let result = catch_unwind(AssertUnwindSafe(move || {
2178 let ret = test_fn();
2179 ret.into_result()?;
2180 Ok(())
2181 }));
2182 let test_result =
2183 TestResult::from_result(&ShouldPanic::No, Duration::from_millis(1), result);
2184 crate::panic_hook::clear_current_test_id();
2185 test_result
2186 }
2187
2188 #[test]
2189 fn panic_with_assert_eq() {
2190 let result = simulate_runner(|| {
2191 assert_eq!(1, 2);
2192 Box::new(())
2193 });
2194 assert!(result.is_failed());
2195 let msg = result.failure_message().unwrap();
2196 println!("=== panic assert_eq failure message ===\n{msg}\n===");
2197 assert!(
2198 msg.contains("assertion `left == right` failed"),
2199 "Expected assertion message, got: {msg}"
2200 );
2201 assert!(
2202 msg.contains("at "),
2203 "Expected location info in message, got: {msg}"
2204 );
2205 }
2206
2207 #[test]
2208 fn string_error() {
2209 let result = simulate_runner(|| {
2210 let r: Result<(), String> = Err("something went wrong".to_string());
2211 Box::new(r)
2212 });
2213 assert!(result.is_failed());
2214 let msg = result.failure_message().unwrap();
2215 println!("=== string error failure message ===\n{msg}\n===");
2216 assert_eq!(msg, "something went wrong");
2217 }
2218
2219 #[test]
2220 fn anyhow_error() {
2221 let result = simulate_runner(|| {
2222 let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
2223 let err = anyhow::anyhow!(inner).context("operation failed");
2224 let r: Result<(), anyhow::Error> = Err(err);
2225 Box::new(r)
2226 });
2227 assert!(result.is_failed());
2228 let msg = result.failure_message().unwrap();
2229 println!("=== anyhow error failure message ===\n{msg}\n===");
2230 assert!(
2231 msg.contains("operation failed"),
2232 "Expected 'operation failed', got: {msg}"
2233 );
2234 assert!(
2235 msg.contains("file not found"),
2236 "Expected 'file not found', got: {msg}"
2237 );
2238 }
2239
2240 #[test]
2241 fn std_io_error() {
2242 let result = simulate_runner(|| {
2243 let r: Result<(), std::io::Error> = Err(std::io::Error::new(
2244 std::io::ErrorKind::NotFound,
2245 "file not found",
2246 ));
2247 Box::new(r)
2248 });
2249 assert!(result.is_failed());
2250 let msg = result.failure_message().unwrap();
2251 println!("=== std io error failure message ===\n{msg}\n===");
2252 assert_eq!(msg, "file not found");
2254 }
2255
2256 #[test]
2257 fn panic_with_location_info() {
2258 let result = simulate_runner(|| {
2259 panic!("test panic with location");
2260 #[allow(unreachable_code)]
2261 Box::new(())
2262 });
2263 assert!(result.is_failed());
2264 let cause = result.failure_cause().unwrap();
2265 match cause {
2266 FailureCause::Panic(p) => {
2267 assert!(p.location.is_some(), "Expected location info");
2268 let loc = p.location.as_ref().unwrap();
2269 assert!(
2270 loc.file.contains("internal.rs"),
2271 "Expected file to contain internal.rs, got: {}",
2272 loc.file
2273 );
2274 assert!(loc.line > 0, "Expected non-zero line number");
2275 }
2276 other => panic!("Expected Panic cause, got: {other:?}"),
2277 }
2278 }
2279
2280 #[test]
2281 fn panic_render_includes_location() {
2282 let result = simulate_runner(|| {
2283 panic!("location test");
2284 #[allow(unreachable_code)]
2285 Box::new(())
2286 });
2287 let msg = result.failure_message().unwrap();
2288 assert!(
2289 msg.contains("location test"),
2290 "Expected panic message, got: {msg}"
2291 );
2292 assert!(
2293 msg.contains("\n at "),
2294 "Expected location line in render, got: {msg}"
2295 );
2296 }
2297
2298 #[test]
2299 fn should_panic_with_message_matching() {
2300 crate::panic_hook::install_panic_hook();
2301 let test_id = crate::panic_hook::next_test_id();
2302 crate::panic_hook::set_current_test_id(test_id);
2303 let result = catch_unwind(AssertUnwindSafe(|| {
2304 panic!("expected panic message");
2305 }));
2306 let test_result = TestResult::from_result(
2307 &ShouldPanic::WithMessage("expected panic".to_string()),
2308 Duration::from_millis(1),
2309 result.map(|_| Ok(())),
2310 );
2311 crate::panic_hook::clear_current_test_id();
2312 assert!(
2313 test_result.is_passed(),
2314 "Expected test to pass with matching panic message"
2315 );
2316 }
2317
2318 #[test]
2319 fn should_panic_with_wrong_message() {
2320 crate::panic_hook::install_panic_hook();
2321 let test_id = crate::panic_hook::next_test_id();
2322 crate::panic_hook::set_current_test_id(test_id);
2323 let result = catch_unwind(AssertUnwindSafe(|| {
2324 panic!("actual panic message");
2325 }));
2326 let test_result = TestResult::from_result(
2327 &ShouldPanic::WithMessage("completely different".to_string()),
2328 Duration::from_millis(1),
2329 result.map(|_| Ok(())),
2330 );
2331 crate::panic_hook::clear_current_test_id();
2332 assert!(
2333 test_result.is_failed(),
2334 "Expected test to fail with wrong panic message"
2335 );
2336 let msg = test_result.failure_message().unwrap();
2337 assert!(
2338 msg.contains("unexpected message"),
2339 "Expected 'unexpected message' in: {msg}"
2340 );
2341 }
2342
2343 #[test]
2344 fn pretty_assertions_diff() {
2345 let result = simulate_runner(|| {
2346 pretty_assertions::assert_eq!("hello world\nfoo\nbar\n", "hello world\nbaz\nbar\n");
2347 Box::new(())
2348 });
2349 assert!(result.is_failed());
2350 let cause = result.failure_cause().unwrap();
2351
2352 let panic_cause = match cause {
2354 FailureCause::Panic(p) => p,
2355 other => panic!("Expected Panic cause, got: {other:?}"),
2356 };
2357
2358 let message = panic_cause.message.as_deref().unwrap();
2360 println!("=== pretty_assertions failure message ===\n{message}\n===");
2361 assert!(
2362 message.contains("foo") && message.contains("baz"),
2363 "Expected diff with 'foo' and 'baz', got: {message}"
2364 );
2365
2366 assert!(panic_cause.location.is_some(), "Expected location info");
2368
2369 let rendered = cause.render();
2371 println!("=== pretty_assertions rendered ===\n{rendered}\n===");
2372 assert!(
2373 !rendered.contains("stack backtrace") && !rendered.contains("Stack backtrace"),
2374 "Expected no backtrace noise in rendered output, got: {rendered}"
2375 );
2376 assert!(
2378 rendered.contains("\n at "),
2379 "Expected location in rendered output, got: {rendered}"
2380 );
2381 }
2382
2383 #[test]
2384 fn detached_thread_panic_detected() {
2385 crate::panic_hook::install_panic_hook();
2386 let test_id = crate::panic_hook::next_test_id();
2387 crate::panic_hook::set_current_test_id(test_id);
2388 crate::panic_hook::create_detached_collector(test_id);
2389
2390 let result = catch_unwind(AssertUnwindSafe(|| {
2391 let handle = crate::spawn::spawn_thread(|| {
2392 panic!("background thread panic");
2393 });
2394 let _ = handle.join();
2395 }));
2396
2397 let mut test_result = TestResult::from_result(
2398 &ShouldPanic::No,
2399 Duration::from_millis(1),
2400 result.map(|_| Ok(())),
2401 );
2402
2403 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2404 let panics = match collector.lock() {
2405 Ok(p) => p,
2406 Err(poisoned) => poisoned.into_inner(),
2407 };
2408 if !panics.is_empty() && test_result.is_passed() {
2409 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
2410 test_result = TestResult::failed(
2411 Duration::from_millis(1),
2412 FailureCause::Panic(PanicCause {
2413 message: Some(format!(
2414 "Detached task(s) panicked:\n{}",
2415 messages.join("\n---\n")
2416 )),
2417 location: panics.first().and_then(|p| p.location.clone()),
2418 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
2419 }),
2420 );
2421 }
2422 }
2423
2424 crate::panic_hook::clear_current_test_id();
2425
2426 assert!(
2427 test_result.is_failed(),
2428 "Expected test to fail due to detached panic"
2429 );
2430 let msg = test_result.failure_message().unwrap();
2431 assert!(
2432 msg.contains("Detached task(s) panicked"),
2433 "Expected detached panic message, got: {msg}"
2434 );
2435 assert!(
2436 msg.contains("background thread panic"),
2437 "Expected original panic message, got: {msg}"
2438 );
2439 }
2440
2441 #[test]
2442 fn detached_thread_panic_ignored_with_policy() {
2443 crate::panic_hook::install_panic_hook();
2444 let test_id = crate::panic_hook::next_test_id();
2445 crate::panic_hook::set_current_test_id(test_id);
2446 crate::panic_hook::create_detached_collector(test_id);
2447
2448 let result = catch_unwind(AssertUnwindSafe(|| {
2449 let handle = crate::spawn::spawn_thread(|| {
2450 panic!("ignored thread panic");
2451 });
2452 let _ = handle.join();
2453 }));
2454
2455 let test_result = TestResult::from_result(
2456 &ShouldPanic::No,
2457 Duration::from_millis(1),
2458 result.map(|_| Ok(())),
2459 );
2460
2461 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
2462 let panics = match collector.lock() {
2463 Ok(p) => p,
2464 Err(poisoned) => poisoned.into_inner(),
2465 };
2466 assert!(
2468 !panics.is_empty(),
2469 "Expected panics in collector even with Ignore policy"
2470 );
2471 }
2472
2473 crate::panic_hook::clear_current_test_id();
2474
2475 assert!(
2476 test_result.is_passed(),
2477 "Expected test to pass with Ignore policy"
2478 );
2479 }
2480
2481 #[cfg(feature = "tokio")]
2482 #[test]
2483 fn detached_task_panic_detected() {
2484 let rt = tokio::runtime::Runtime::new().unwrap();
2485 rt.block_on(async {
2486 crate::panic_hook::install_panic_hook();
2487 let test_id = crate::panic_hook::next_test_id();
2488 crate::panic_hook::set_current_test_id(test_id);
2489 crate::panic_hook::create_detached_collector(test_id);
2490
2491 let handle = crate::spawn::spawn(async {
2492 panic!("detached task panic");
2493 });
2494 let _ = handle.await;
2495
2496 let collector = crate::panic_hook::take_detached_collector(test_id).unwrap();
2497 let panics = collector.lock().unwrap();
2498
2499 assert_eq!(panics.len(), 1);
2500 assert!(
2501 panics[0]
2502 .message
2503 .as_ref()
2504 .unwrap()
2505 .contains("detached task panic"),
2506 "Expected panic message, got: {:?}",
2507 panics[0].message
2508 );
2509
2510 crate::panic_hook::clear_current_test_id();
2511 });
2512 }
2513
2514 #[test]
2515 fn failure_cause_variants() {
2516 let cause = FailureCause::ReturnedMessage("simple message".to_string());
2518 assert_eq!(cause.render(), "simple message");
2519 assert!(cause.panic_message().is_none());
2520
2521 let cause = FailureCause::ReturnedError {
2523 display: "display text".to_string(),
2524 debug: "debug text".to_string(),
2525 prefer_debug: false,
2526 error: Arc::new("display text".to_string()),
2527 };
2528 assert_eq!(cause.render(), "display text");
2529
2530 let cause = FailureCause::ReturnedError {
2532 display: "display text".to_string(),
2533 debug: "debug text".to_string(),
2534 prefer_debug: true,
2535 error: Arc::new("debug text".to_string()),
2536 };
2537 assert_eq!(cause.render(), "debug text");
2538
2539 let cause = FailureCause::HarnessError("harness error".to_string());
2541 assert_eq!(cause.render(), "harness error");
2542
2543 let cause = FailureCause::Panic(PanicCause {
2545 message: Some("panic msg".to_string()),
2546 location: None,
2547 backtrace: None,
2548 });
2549 assert_eq!(cause.render(), "panic msg");
2550 assert_eq!(cause.panic_message(), Some("panic msg"));
2551 }
2552}
2553
2554#[cfg(test)]
2555mod filter_tests {
2556 use super::*;
2557
2558 fn make_test(name: &str, module_path: &str) -> RegisteredTest {
2559 RegisteredTest {
2560 name: name.to_string(),
2561 crate_name: "mycrate".to_string(),
2562 module_path: module_path.to_string(),
2563 run: TestFunction::Sync(Arc::new(|_| Box::new(()))),
2564 props: TestProperties::default(),
2565 dependencies: None,
2566 }
2567 }
2568
2569 fn make_tagged_test(name: &str, module_path: &str, tags: Vec<&str>) -> RegisteredTest {
2570 let mut test = make_test(name, module_path);
2571 test.props.tags = tags.into_iter().map(String::from).collect();
2572 test
2573 }
2574
2575 fn make_args(filters: Vec<&str>, skip: Vec<&str>, exact: bool) -> Arguments {
2576 Arguments {
2577 filter: filters.into_iter().map(String::from).collect(),
2578 skip: skip.into_iter().map(String::from).collect(),
2579 exact,
2580 ..Default::default()
2581 }
2582 }
2583
2584 fn filtered_names(args: &Arguments, tests: &[RegisteredTest]) -> Vec<String> {
2585 filter_registered_tests(args, tests)
2586 .into_iter()
2587 .map(|t| t.filterable_name())
2588 .collect()
2589 }
2590
2591 #[test]
2594 fn filter_test_substring_match() {
2595 let test = make_test("hello_world", "mod1");
2596 assert!(filter_test(&test, "hello", false));
2597 assert!(filter_test(&test, "world", false));
2598 assert!(filter_test(&test, "mod1::hello", false));
2599 assert!(!filter_test(&test, "nonexistent", false));
2600 }
2601
2602 #[test]
2603 fn filter_test_exact_match() {
2604 let test = make_test("hello_world", "mod1");
2605 assert!(filter_test(&test, "mod1::hello_world", true));
2606 assert!(!filter_test(&test, "hello_world", true));
2607 assert!(!filter_test(&test, "hello", true));
2608 }
2609
2610 #[test]
2611 fn filter_test_tag_match() {
2612 let test = make_tagged_test("t1", "mod1", vec!["fast", "unit"]);
2613 assert!(filter_test(&test, ":tag:fast", false));
2614 assert!(filter_test(&test, ":tag:unit", false));
2615 assert!(!filter_test(&test, ":tag:slow", false));
2616 }
2617
2618 #[test]
2619 fn filter_test_tag_empty_matches_untagged() {
2620 let untagged = make_test("t1", "mod1");
2621 let tagged = make_tagged_test("t2", "mod1", vec!["fast"]);
2622 assert!(filter_test(&untagged, ":tag:", false));
2623 assert!(!filter_test(&tagged, ":tag:", false));
2624 }
2625
2626 #[test]
2629 fn no_filters_includes_all() {
2630 let tests = vec![make_test("a", "m"), make_test("b", "m")];
2631 let args = make_args(vec![], vec![], false);
2632 assert_eq!(filtered_names(&args, &tests), vec!["m::a", "m::b"]);
2633 }
2634
2635 #[test]
2636 fn single_filter_substring() {
2637 let tests = vec![
2638 make_test("alpha", "m"),
2639 make_test("beta", "m"),
2640 make_test("alphabet", "m"),
2641 ];
2642 let args = make_args(vec!["alpha"], vec![], false);
2643 assert_eq!(
2644 filtered_names(&args, &tests),
2645 vec!["m::alpha", "m::alphabet"]
2646 );
2647 }
2648
2649 #[test]
2650 fn multiple_filters_or_semantics() {
2651 let tests = vec![
2652 make_test("alpha", "m"),
2653 make_test("beta", "m"),
2654 make_test("gamma", "m"),
2655 ];
2656 let args = make_args(vec!["alpha", "gamma"], vec![], false);
2657 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::gamma"]);
2658 }
2659
2660 #[test]
2661 fn multiple_filters_exact() {
2662 let tests = vec![
2663 make_test("alpha", "m"),
2664 make_test("alphabet", "m"),
2665 make_test("beta", "m"),
2666 ];
2667 let args = make_args(vec!["m::alpha", "m::beta"], vec![], true);
2668 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha", "m::beta"]);
2669 }
2670
2671 #[test]
2674 fn skip_substring_match() {
2675 let tests = vec![
2676 make_test("fast_test", "m"),
2677 make_test("slow_test", "m"),
2678 make_test("slower_test", "m"),
2679 ];
2680 let args = make_args(vec![], vec!["slow"], false);
2681 assert_eq!(filtered_names(&args, &tests), vec!["m::fast_test"]);
2682 }
2683
2684 #[test]
2685 fn skip_exact_match() {
2686 let tests = vec![make_test("slow_test", "m"), make_test("slower_test", "m")];
2687 let args = make_args(vec![], vec!["m::slow_test"], true);
2688 assert_eq!(filtered_names(&args, &tests), vec!["m::slower_test"]);
2689 }
2690
2691 #[test]
2692 fn skip_with_tag() {
2693 let tests = vec![
2694 make_tagged_test("t1", "m", vec!["slow"]),
2695 make_tagged_test("t2", "m", vec!["fast"]),
2696 make_test("t3", "m"),
2697 ];
2698 let args = make_args(vec![], vec![":tag:slow"], false);
2699 assert_eq!(filtered_names(&args, &tests), vec!["m::t2", "m::t3"]);
2700 }
2701
2702 #[test]
2705 fn include_and_skip_combined() {
2706 let tests = vec![
2707 make_test("alpha_fast", "m"),
2708 make_test("alpha_slow", "m"),
2709 make_test("beta_fast", "m"),
2710 ];
2711 let args = make_args(vec!["alpha"], vec!["slow"], false);
2713 assert_eq!(filtered_names(&args, &tests), vec!["m::alpha_fast"]);
2714 }
2715
2716 #[test]
2717 fn skip_wins_over_include() {
2718 let tests = vec![make_test("target", "m")];
2719 let args = make_args(vec!["target"], vec!["target"], false);
2721 assert_eq!(filtered_names(&args, &tests), Vec::<String>::new());
2722 }
2723
2724 #[test]
2727 fn filter_test_tag_or_expression() {
2728 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2730 let test_b = make_tagged_test("t2", "m", vec!["b"]);
2731 let test_c = make_tagged_test("t3", "m", vec!["c"]);
2732 assert!(filter_test(&test_a, ":tag:a|b", false));
2733 assert!(filter_test(&test_b, ":tag:a|b", false));
2734 assert!(!filter_test(&test_c, ":tag:a|b", false));
2735 }
2736
2737 #[test]
2738 fn filter_test_tag_and_expression() {
2739 let test_ab = make_tagged_test("t1", "m", vec!["a", "b"]);
2741 let test_a = make_tagged_test("t2", "m", vec!["a"]);
2742 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2743 assert!(filter_test(&test_ab, ":tag:a&b", false));
2744 assert!(!filter_test(&test_a, ":tag:a&b", false));
2745 assert!(!filter_test(&test_b, ":tag:a&b", false));
2746 }
2747
2748 #[test]
2749 fn filter_test_tag_mixed_and_or() {
2750 let test_a = make_tagged_test("t1", "m", vec!["a"]);
2752 let test_bc = make_tagged_test("t2", "m", vec!["b", "c"]);
2753 let test_b = make_tagged_test("t3", "m", vec!["b"]);
2754 let test_c = make_tagged_test("t4", "m", vec!["c"]);
2755 let test_none = make_test("t5", "m");
2756 assert!(filter_test(&test_a, ":tag:a|b&c", false));
2757 assert!(filter_test(&test_bc, ":tag:a|b&c", false));
2758 assert!(!filter_test(&test_b, ":tag:a|b&c", false));
2759 assert!(!filter_test(&test_c, ":tag:a|b&c", false));
2760 assert!(!filter_test(&test_none, ":tag:a|b&c", false));
2761 }
2762
2763 #[test]
2764 fn filter_test_tag_exact_flag_does_not_affect_tags() {
2765 let test = make_tagged_test("t1", "m", vec!["fast"]);
2767 assert!(filter_test(&test, ":tag:fast", true));
2768 assert!(!filter_test(&test, ":tag:slow", true));
2769 }
2770
2771 #[test]
2772 fn include_by_tag_or_expression() {
2773 let tests = vec![
2774 make_tagged_test("t1", "m", vec!["unit"]),
2775 make_tagged_test("t2", "m", vec!["integration"]),
2776 make_tagged_test("t3", "m", vec!["e2e"]),
2777 ];
2778 let args = make_args(vec![":tag:unit|integration"], vec![], false);
2779 assert_eq!(filtered_names(&args, &tests), vec!["m::t1", "m::t2"]);
2780 }
2781
2782 #[test]
2783 fn skip_by_tag_and_expression() {
2784 let tests = vec![
2785 make_tagged_test("t1", "m", vec!["slow", "network"]),
2786 make_tagged_test("t2", "m", vec!["slow"]),
2787 make_tagged_test("t3", "m", vec!["network"]),
2788 make_test("t4", "m"),
2789 ];
2790 let args = make_args(vec![], vec![":tag:slow&network"], false);
2792 assert_eq!(
2793 filtered_names(&args, &tests),
2794 vec!["m::t2", "m::t3", "m::t4"]
2795 );
2796 }
2797}