From 0fc785fb71baa48dba00bd4d81bb84d12c7278c7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 30 Oct 2025 16:36:21 -0500 Subject: [PATCH 1/4] feat(harness): Allow Box, Arc Cases --- crates/libtest2-harness/src/case.rs | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/libtest2-harness/src/case.rs b/crates/libtest2-harness/src/case.rs index 5662d06..2e7c0a1 100644 --- a/crates/libtest2-harness/src/case.rs +++ b/crates/libtest2-harness/src/case.rs @@ -15,6 +15,44 @@ pub trait Case: Send + Sync + 'static { fn run(&self, state: &TestContext) -> Result<(), RunError>; } +impl Case for Box { + fn name(&self) -> &str { + self.as_ref().name() + } + fn kind(&self) -> TestKind { + self.as_ref().kind() + } + fn source(&self) -> Option<&Source> { + self.as_ref().source() + } + fn exclusive(&self, state: &TestContext) -> bool { + self.as_ref().exclusive(state) + } + + fn run(&self, state: &TestContext) -> Result<(), RunError> { + self.as_ref().run(state) + } +} + +impl Case for std::sync::Arc { + fn name(&self) -> &str { + self.as_ref().name() + } + fn kind(&self) -> TestKind { + self.as_ref().kind() + } + fn source(&self) -> Option<&Source> { + self.as_ref().source() + } + fn exclusive(&self, state: &TestContext) -> bool { + self.as_ref().exclusive(state) + } + + fn run(&self, state: &TestContext) -> Result<(), RunError> { + self.as_ref().run(state) + } +} + /// Type of the test according to the [rust book](https://doc.rust-lang.org/cargo/guide/tests.html) /// conventions. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] From 0336ea11480bb95375c7d52a0172c4f860c1d53a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 30 Oct 2025 16:36:41 -0500 Subject: [PATCH 2/4] fix(test2)!: Dont require boxing in FnCase --- crates/libtest2/examples/tidy.rs | 7 ++++--- crates/libtest2/src/case.rs | 22 ++++++++++++---------- crates/libtest2/src/lib.rs | 2 +- crates/libtest2/src/macros.rs | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/libtest2/examples/tidy.rs b/crates/libtest2/examples/tidy.rs index 752c85d..11cbe5c 100644 --- a/crates/libtest2/examples/tidy.rs +++ b/crates/libtest2/examples/tidy.rs @@ -1,3 +1,4 @@ +use libtest2::Case; use libtest2::FnCase; use libtest2::RunError; use libtest2::RunResult; @@ -23,8 +24,8 @@ fn main() -> std::io::Result<()> { /// Creates one test for each `.rs` file in the current directory or /// sub-directories of the current directory. -fn collect_tests() -> std::io::Result> { - fn visit_dir(path: &std::path::Path, tests: &mut Vec) -> std::io::Result<()> { +fn collect_tests() -> std::io::Result>> { + fn visit_dir(path: &std::path::Path, tests: &mut Vec>) -> std::io::Result<()> { let current_dir = std::env::current_dir()?; for entry in std::fs::read_dir(path)? { let entry = entry?; @@ -45,7 +46,7 @@ fn collect_tests() -> std::io::Result> { .into_owned(); let test = FnCase::test(name, move |_| check_file(&path)); - tests.push(test); + tests.push(Box::new(test)); } } else if file_type.is_dir() { // Handle directories diff --git a/crates/libtest2/src/case.rs b/crates/libtest2/src/case.rs index 079e45b..970eea6 100644 --- a/crates/libtest2/src/case.rs +++ b/crates/libtest2/src/case.rs @@ -27,25 +27,27 @@ impl Case for DynCase { } } -pub struct FnCase { +pub struct FnCase { name: String, - #[allow(clippy::type_complexity)] - runner: Box RunResult + Send + Sync>, + runner: R, } -impl FnCase { - pub fn test( - name: impl Into, - runner: impl Fn(&TestContext) -> RunResult + Send + Sync + 'static, - ) -> Self { +impl FnCase +where + R: Fn(&TestContext) -> RunResult + Send + Sync + 'static, +{ + pub fn test(name: impl Into, runner: R) -> Self { Self { name: name.into(), - runner: Box::new(runner), + runner, } } } -impl Case for FnCase { +impl Case for FnCase +where + R: Fn(&TestContext) -> RunResult + Send + Sync + 'static, +{ fn name(&self) -> &str { &self.name } diff --git a/crates/libtest2/src/lib.rs b/crates/libtest2/src/lib.rs index 900cae8..a39aefc 100644 --- a/crates/libtest2/src/lib.rs +++ b/crates/libtest2/src/lib.rs @@ -55,7 +55,6 @@ mod macros; pub mod _private { pub use distributed_list::push; pub use distributed_list::DistributedList; - pub use libtest2_harness::Case; pub use libtest2_harness::Source; pub use libtest2_harness::TestKind; @@ -67,6 +66,7 @@ pub mod _private { pub use case::main; pub use case::FnCase; +pub use libtest2_harness::Case; pub use libtest2_harness::IntoRunResult; pub use libtest2_harness::RunError; pub use libtest2_harness::RunResult; diff --git a/crates/libtest2/src/macros.rs b/crates/libtest2/src/macros.rs index aa3b852..9c2342d 100644 --- a/crates/libtest2/src/macros.rs +++ b/crates/libtest2/src/macros.rs @@ -33,7 +33,7 @@ macro_rules! _test_parse { #[allow(non_camel_case_types)] struct $name; - impl $crate::_private::Case for $name { + impl $crate::Case for $name { fn name(&self) -> &str { $crate::_private::push!(crate::TESTS, _: $crate::_private::DynCase = $crate::_private::DynCase(&$name)); From ad057c102421f8fe63dc1157a1ed27f556e16ccd Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 30 Oct 2025 16:50:47 -0500 Subject: [PATCH 3/4] fix(test2): Allow const FnCase --- crates/libtest2/examples/tidy.rs | 4 +++- crates/libtest2/src/case.rs | 9 +++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/libtest2/examples/tidy.rs b/crates/libtest2/examples/tidy.rs index 11cbe5c..ee26502 100644 --- a/crates/libtest2/examples/tidy.rs +++ b/crates/libtest2/examples/tidy.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use libtest2::Case; use libtest2::FnCase; use libtest2::RunError; @@ -45,7 +47,7 @@ fn collect_tests() -> std::io::Result>> { .to_string_lossy() .into_owned(); - let test = FnCase::test(name, move |_| check_file(&path)); + let test = FnCase::test(Cow::Owned(name), move |_| check_file(&path)); tests.push(Box::new(test)); } } else if file_type.is_dir() { diff --git a/crates/libtest2/src/case.rs b/crates/libtest2/src/case.rs index 970eea6..8fb14e1 100644 --- a/crates/libtest2/src/case.rs +++ b/crates/libtest2/src/case.rs @@ -28,7 +28,7 @@ impl Case for DynCase { } pub struct FnCase { - name: String, + name: std::borrow::Cow<'static, str>, runner: R, } @@ -36,11 +36,8 @@ impl FnCase where R: Fn(&TestContext) -> RunResult + Send + Sync + 'static, { - pub fn test(name: impl Into, runner: R) -> Self { - Self { - name: name.into(), - runner, - } + pub const fn test(name: std::borrow::Cow<'static, str>, runner: R) -> Self { + Self { name, runner } } } From a134afefd77672742537ff38c40ea18e31eda7b2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 30 Oct 2025 16:51:34 -0500 Subject: [PATCH 4/4] refactor(test2): Don't create a type per test --- crates/libtest2/src/macros.rs | 52 ++++++++++++++--------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/crates/libtest2/src/macros.rs b/crates/libtest2/src/macros.rs index 9c2342d..8991fb6 100644 --- a/crates/libtest2/src/macros.rs +++ b/crates/libtest2/src/macros.rs @@ -30,39 +30,27 @@ macro_rules! _parse_ignore { #[allow(clippy::crate_in_macro_def)] // accessing item defined by `_main_parse` macro_rules! _test_parse { (#[test] $(#[$($attr:tt)*])* fn $name:ident $($item:tt)*) => { - #[allow(non_camel_case_types)] - struct $name; - - impl $crate::Case for $name { - fn name(&self) -> &str { - $crate::_private::push!(crate::TESTS, _: $crate::_private::DynCase = $crate::_private::DynCase(&$name)); - - stringify!($name) - } - fn kind(&self) -> $crate::_private::TestKind { - Default::default() - } - fn source(&self) -> Option<&$crate::_private::Source> { - None - } - fn exclusive(&self, _: &$crate::TestContext) -> bool { - false - } - - fn run(&self, context: &$crate::TestContext) -> $crate::RunResult { - fn run $($item)* - - $( - match $crate::_private::parse_ignore!($($attr)*) { - ::std::option::Option::None => context.ignore()?, - ::std::option::Option::Some(reason) => context.ignore_for(reason)?, + const _: () = { + $crate::_private::push!( + crate::TESTS, + _: $crate::_private::DynCase = $crate::_private::DynCase(&$crate::FnCase::test( + ::std::borrow::Cow::Borrowed(stringify!($name)), + |context| { + $( + match $crate::_private::parse_ignore!($($attr)*) { + ::std::option::Option::None => context.ignore()?, + ::std::option::Option::Some(reason) => context.ignore_for(reason)?, + } + )* + + use $crate::IntoRunResult; + let result = $name(context); + IntoRunResult::into_run_result(result) } - )* + )) + ); + }; - use $crate::IntoRunResult; - let result = run(context); - IntoRunResult::into_run_result(result) - } - } + fn $name $($item)* }; }