Skip to content

Commit

Permalink
Make Send bound optional for AsyncTestContext
Browse files Browse the repository at this point in the history
  • Loading branch information
funbiscuit committed Apr 9, 2023
1 parent 2573779 commit 9dfafe4
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ authors = ["Mark Hildreth <[email protected]>"]
license = "MIT"
categories = ["development-tools::testing"]

[features]
default = ["async_send"]
# Require async futures to be Send
async_send = ["test-context-macros/async_send"]

[dependencies]
test-context-macros = { version = "0.1.4", path = "macros" }
async-trait = "0.1.42"
Expand Down
4 changes: 4 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ license = "MIT"
[lib]
proc-macro = true

[features]
# Require async futures to be Send
async_send = []

[dependencies]
quote = "1.0.3"
syn = { version = "^1", features = ["full"] }
76 changes: 76 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! value: String
//! }
//!
//! # #[cfg(feature = "async_send")]
//! #[async_trait::async_trait]
//! impl AsyncTestContext for MyAsyncContext {
//! async fn setup() -> MyAsyncContext {
Expand All @@ -45,12 +46,44 @@
//! }
//! }
//!
//! # #[cfg(feature = "async_send")]
//! #[test_context(MyAsyncContext)]
//! fn test_works(ctx: &mut MyAsyncContext) {
//! assert_eq!(ctx.value, "Hello, World!");
//! }
//! ```
//!
//! In case your setup and teardown futures are not `Send`, you can disable
//! default feature `async_send` and use `async_trait(?Send)`.
//!
//! ```
//! use std::rc::Rc;
//! use test_context::{test_context, AsyncTestContext};
//!
//! // Rc is not Send so Future's are also not Send
//! struct MyAsyncContext {
//! value: Rc<String>
//! }
//!
//! # #[cfg(not(feature = "async_send"))]
//! #[async_trait::async_trait(?Send)]
//! impl AsyncTestContext for MyAsyncContext {
//! async fn setup() -> MyAsyncContext {
//! MyAsyncContext { value: Rc::new("Hello, world!".to_string()) }
//! }
//!
//! async fn teardown(self) {
//! // Perform any teardown you wish.
//! }
//! }
//!
//! # #[cfg(not(feature = "async_send"))]
//! #[test_context(MyAsyncContext)]
//! fn test_works(ctx: &mut MyAsyncContext) {
//! assert_eq!(*ctx.value, "Hello, World!");
//! }
//! ```
//!
//! The `AsyncTestContext` works well with async test wrappers like
//! [`actix_rt::test`](https://docs.rs/actix-rt/1.1.1/actix_rt/attr.test.html) or
//! [`tokio::test`](https://docs.rs/tokio/1.0.2/tokio/attr.test.html).
Expand All @@ -60,6 +93,17 @@
//! # struct MyAsyncContext {
//! # value: String
//! # }
//! # #[cfg(not(feature = "async_send"))]
//! # #[async_trait::async_trait(?Send)]
//! # impl AsyncTestContext for MyAsyncContext {
//! # async fn setup() -> MyAsyncContext {
//! # MyAsyncContext { value: "Hello, world!".to_string() }
//! # }
//! # async fn teardown(self) {
//! # // Perform any teardown you wish.
//! # }
//! # }
//! # #[cfg(feature = "async_send")]
//! # #[async_trait::async_trait]
//! # impl AsyncTestContext for MyAsyncContext {
//! # async fn setup() -> MyAsyncContext {
Expand Down Expand Up @@ -95,6 +139,7 @@ where
}

/// The trait to implement to get setup/teardown functionality for async tests.
#[cfg(feature = "async_send")]
#[async_trait::async_trait]
pub trait AsyncTestContext
where
Expand All @@ -108,11 +153,27 @@ where
async fn teardown(self) {}
}

/// The trait to implement to get setup/teardown functionality for async tests.
#[cfg(not(feature = "async_send"))]
#[async_trait::async_trait(?Send)]
pub trait AsyncTestContext
where
Self: Sized,
{
/// Create the context. This is run once before each test that uses the context.
async fn setup() -> Self;

/// Perform any additional cleanup of the context besides that already provided by
/// normal "drop" semantics.
async fn teardown(self) {}
}

// Automatically impl TestContext for anything Send that impls AsyncTestContext.
//
// A future improvement may be to use feature flags to enable using a specific runtime
// to run the future synchronously. This is the easiest way to implement it, though, and
// introduces no new dependencies.
#[cfg(feature = "async_send")]
impl<T> TestContext for T
where
T: AsyncTestContext + Send,
Expand All @@ -125,3 +186,18 @@ where
futures::executor::block_on(<T as AsyncTestContext>::teardown(self))
}
}

// Automatically impl TestContext for anything that impls AsyncTestContext but not Send.
#[cfg(not(feature = "async_send"))]
impl<T> TestContext for T
where
T: AsyncTestContext,
{
fn setup() -> Self {
futures::executor::block_on(<T as AsyncTestContext>::setup())
}

fn teardown(self) {
futures::executor::block_on(<T as AsyncTestContext>::teardown(self))
}
}
15 changes: 15 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct AsyncContext {
n: u32,
}

#[cfg(feature = "async_send")]
#[async_trait::async_trait]
impl AsyncTestContext for AsyncContext {
async fn setup() -> Self {
Expand All @@ -64,6 +65,20 @@ impl AsyncTestContext for AsyncContext {
}
}

#[cfg(not(feature = "async_send"))]
#[async_trait::async_trait(?Send)]
impl AsyncTestContext for AsyncContext {
async fn setup() -> Self {
Self { n: 1 }
}

async fn teardown(self) {
if self.n != 1 {
panic!("Number changed");
}
}
}

#[test_context(AsyncContext)]
#[tokio::test]
async fn test_async_setup(ctx: &mut AsyncContext) {
Expand Down

0 comments on commit 9dfafe4

Please sign in to comment.