[![Workflow Status](https://github.com/droundy/test-with-tokio/actions/workflows/rust.yml/badge.svg)](https://github.com/droundy/test-with-tokio/actions/workflows/rust.yml) # An attribute macro for tests using tokio with cases and async guards. This crate provides a single polite attribute macro `#[test_with_tokio::please]` which allows you to write tests that do some not-async code before running async code within tokio. This is similar to `#[tokio::test]` but with two features: async code can be run prior to the tokio runtime being started, and a single test can be written to generate multiple tests handling multiple cases of the same test. With a bit of work, this enables you to run most of your tests in parallel, but to have a few that cannot be run concurrently. # Examples At the most basic level, this crate enables you to easily write tests that run non-async code that will be run prior to async code. ```rust // The async in `async fn` below is optional and ignored. #[test_with_tokio::please] async fn test_me() { println!("This code will be run before the tokio runtime is started."); async_std::println!("This code will be run with a tokio runtime").await; } ``` ## Holding a lock The motivating reason for this crate is to enable use of a lock to run tests concurrently: ```rust static DIRECTORY_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(()); #[test_with_tokio::please] fn test_run_exclusively() { let _guard = DIRECTORY_LOCK.write().unwrap(); async_std::println!("This code will be run with exclusive access to the directory.").await; } #[test_with_tokio::please] fn test_run_cooperatively() { let _guard = DIRECTORY_LOCK.read().unwrap(); async_std::println!("This code will be run concurrently with other cooperative tests..").await; } ``` You might wonder, why not take the lock within the `async` block, or perhaps simply within a function marked with `#[tokio::test]`? The answer lies in the lack of an `async` `Drop`. This means that a test may not be fully cleaned up until *after* the tokio runtime exits, which is *after* the body of your test function has exited and released the lock, meaning you may still have race conditions in your tests, with a lock taken concurrently. ## Multiple cases If you can write code that generates multiple related tests by assigning a variable to `match CASE { ... }` where each case matches a string literal that is a valid suffix for an identifier. ```rust #[test_with_tokio::please] fn test_contains() { let container = match CASE { "hello" => "hello world", "this_test" => vec!["this_test"], }; assert!(container.contains(CASE)); } ``` This example will create two functions each marked `#[test]`, one named `test_contains_hello` and the other `test_contains_this_test`. The body of the first function will look like: ```rust #[test] fn test_contains_hello() { const CASE: &str = "hello"; let container = "hello world"; assert!(container.contains(CASE)); } ```