Skip to content

Commit

Permalink
Merge pull request #22 from jbr/impl-termination-and-eliding-harness-…
Browse files Browse the repository at this point in the history
…name

feat: add support for #[test(harness)] and pass through any impl Termination return type
  • Loading branch information
jbr authored Jan 2, 2024
2 parents 6afb4b6 + 2aed44c commit 27658f7
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 20 deletions.
86 changes: 78 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,44 @@ the correct arguments in the harness.
```rust
use test_harness::test;

fn my_test_harness<F>(test: F)
fn my_test_harness(test: impl FnOnce(String)) {
let string = std::iter::repeat_with(fastrand::alphanumeric).take(10).collect();
test(string)
}

#[test(harness = my_test_harness)]
fn my_test(random_string: String) {
assert_eq!(string.len(), 10);
}
```


This expands to the following, with no further macro magic

```rust
fn my_test_harness<F>(test: impl FnOnce(String)) {
let string = std::iter::repeat_with(fastrand::alphanumeric).take(10).collect();
test(string)
}

#[test]
fn my_test() -> impl std::process::Termination {
fn my_test(random_string: String) -> Result<(), &'static str> {
assert_eq!(string.len(), 10);
Ok(())
}
my_test_harness(my_test)
}
```

## Returning Result
```rust
use test_harness::test;

fn my_test_harness<F>(test: F) -> Result<(), &'static str>
where F: FnOnce(String) -> Result<(), &'static str> {
let string = std::iter::repeat_with(fastrand::alphanumeric).take(10).collect();
test(string).expect("test success");
test(string)
}

#[test(harness = my_test_harness)]
Expand All @@ -26,22 +60,23 @@ fn my_test(random_string: String) -> Result<(), &'static str> {
This expands to the following, with no further macro magic

```rust
fn my_test_harness<F>(test: F)
fn my_test_harness<F>(test: F) -> Result<(), &'static str>
where F: FnOnce(String) -> Result<(), &'static str> {
let string = std::iter::repeat_with(fastrand::alphanumeric).take(10).collect();
test(string).expect("test success");
test(string)
}

#[test]
fn my_test() {
fn my_test() -> impl std::process::Termination {
fn my_test(random_string: String) -> Result<(), &'static str> {
assert_eq!(string.len(), 10);
Ok(())
}
my_test_harness(my_test);
my_test_harness(my_test)
}
```


## Async example

You can use this to set up an async runtime and spawn or block on the test.
Expand All @@ -50,12 +85,12 @@ You can use this to set up an async runtime and spawn or block on the test.
use test_harness::test;

mod my_mod {
pub fn set_up<F, Fut>(test: F)
pub fn set_up<F, Fut>(test: F) -> Result<(), Box<dyn std::error::Error>>
where
F: FnOnce(&'static str) -> Fut,
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error>>> + Send + 'static,
{
futures_lite::future::block_on(test("hello")).unwrap()
futures_lite::future::block_on(test("hello"))
}
}

Expand All @@ -66,6 +101,40 @@ async fn my_test(s: &'static str) -> Result<(), Box<dyn std::error::Error>> {
}
```


# Eliding harness name

If you name your harness `harness`, you can elide the harness name, like so:

```rust
use test_harness::test;

pub fn harness<F, Fut, Out>(test: F) -> Out
where
F: FnOnce(&'static str) -> Fut,
Fut: std::future::Future<Output = Out> + Send + 'static,
Out: std::process::Termination
{
futures_lite::future::block_on(test("hello"))
}


#[test(harness)]
async fn test_one(s: &'static str) -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(s, "hello");
Ok(())
}

#[test(harness)]
async fn test_two(s: &'static str) {
assert_eq!(s, "hello");
}

```




# Drop down to standard #[test]

If this macro is used without any additional arguments, it works identically to the built-in `#[test]` macro.
Expand All @@ -77,3 +146,4 @@ fn normal_test() {
assert!(true);
}
```

30 changes: 18 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input, Expr, ExprAssign, ExprPath, ItemFn, Path,
parse_macro_input, ExprPath, ItemFn, Path, Token,
};

struct Args {
Expand All @@ -27,15 +27,21 @@ impl Parse for Args {
return Ok(Self { harness: None });
}

let ExprAssign { left, right, .. } = ExprAssign::parse(input)?;
match (*left, *right) {
(
Expr::Path(ExprPath { path: left, .. }),
Expr::Path(ExprPath { path: harness, .. }),
) if left.is_ident("harness") => Ok(Self {
harness: Some(harness),
}),
_ => Err(input.error("we only recognize test(harness = some::path)")),
let harness_ident: ExprPath = input.parse()?;
if !harness_ident.path.is_ident("harness") {
Err(input.error(
"we only recognize #[test(harness = some::path)], #[test(harness)], and #[test]",
))
} else if input.peek(Token![=]) {
let syn::token::Eq { .. } = input.parse()?;
let ExprPath { path, .. } = input.parse()?;
Ok(Self {
harness: Some(path),
})
} else {
Ok(Self {
harness: Some(harness_ident.path),
})
}
}
}
Expand Down Expand Up @@ -66,9 +72,9 @@ fn with_harness(harness: Path, input: TokenStream) -> TokenStream {
let fn_name = input.sig.ident.clone();
quote! {
#[::core::prelude::v1::test]
fn #fn_name() {
fn #fn_name() -> impl ::std::process::Termination {
#input
#harness(#fn_name);
#harness(#fn_name)
}
}
.into()
Expand Down
125 changes: 125 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
type StrResult = Result<(), &'static str>;

mod returning_unit {
use test_harness::test;
fn harness(test: impl Fn(bool)) {
test(true)
}

#[test(harness)]
fn test_elided_harness_name(pass: bool) {
assert!(pass);
}

#[test(harness = harness)]
fn test_harness_name_is_harness(pass: bool) {
assert!(pass);
}

fn custom_harness_name(test: impl Fn(bool)) {
test(true)
}

#[test(harness = custom_harness_name)]
fn test_custom_harness_name(pass: bool) {
assert!(pass);
}

#[test]
fn test_basic() {
assert!(true);
}
}

mod with_result {
use super::StrResult;
use test_harness::test;

fn harness(test: impl Fn(bool) -> StrResult) -> StrResult {
test(true)
}

#[test(harness)]
fn test_elided_harness_name(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}

#[test(harness = harness)]
fn test_harness_name_is_harness(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}

fn custom_harness_name(test: impl Fn(bool) -> StrResult) -> StrResult {
test(true)
}

#[test(harness = custom_harness_name)]
fn test_custom_harness_name(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}
}

mod optional_result {
use super::StrResult;
use test_harness::test;

fn harness<T: std::process::Termination>(test: impl Fn(bool) -> T) -> T {
test(true)
}

#[test(harness)]
fn test_elided_harness_name_returning_result(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}

#[test(harness)]
fn test_elided_harness_name_returning_unit(pass: bool) {
assert!(pass);
}

#[test(harness = harness)]
fn test_harness_name_is_harness_returning_result(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}

#[test(harness = harness)]
fn test_harness_name_is_harness_returning_unit(pass: bool) {
assert!(pass);
}

fn custom_harness_name<T: std::process::Termination>(test: impl Fn(bool) -> T) -> T {
test(true)
}

#[test(harness = custom_harness_name)]
fn test_custom_harness_name_returning_result(pass: bool) -> StrResult {
assert!(pass);
Ok(())
}

#[test(harness = custom_harness_name)]
fn test_custom_harness_name_returning_unit(pass: bool) {
assert!(pass);
}
}

mod passthrough {
use super::StrResult;
use test_harness::test;

#[test]
fn test_returning_result() -> StrResult {
assert!(true);
Ok(())
}

#[test]
fn test_returning_unit() {
assert!(true);
}
}

0 comments on commit 27658f7

Please sign in to comment.