mirror of
https://codeberg.org/JasterV/test-context.git
synced 2026-04-26 18:10:06 +00:00
Initial version
This commit is contained in:
parent
c51fa67447
commit
daf14d6449
7 changed files with 396 additions and 0 deletions
55
.github/workflows/ci.yml
vendored
Normal file
55
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
name: Rust
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets -- -D warnings
|
||||
|
||||
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "test-context"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
description = "A library for providing custom setup/teardown for Rust tests without needing a test harness"
|
||||
homepage = "https://github.com/markhildreth/test-context"
|
||||
repository = "https://github.com/markhildreth/test-context"
|
||||
readme = "README.md"
|
||||
authors = ["Mark Hildreth <mark.k.hildreth@gmail.com>"]
|
||||
license = "MIT"
|
||||
categories = ["development-tools::testing"]
|
||||
|
||||
[dependencies]
|
||||
test-context-macros = { version = "0.1.0", path = "macros" }
|
||||
async-trait = "0.1.42"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.0", features = ["macros", "rt"]}
|
||||
futures = "0.3.12"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"macros",
|
||||
]
|
||||
56
README.md
Normal file
56
README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# test-context
|
||||
|
||||
A library for providing custom setup/teardown for Rust tests without needing a test harness.
|
||||
|
||||
```rust
|
||||
use test_context::{test_context, TestContext};
|
||||
|
||||
struct MyContext {
|
||||
value: String
|
||||
}
|
||||
|
||||
impl TestContext for MyContext {
|
||||
fn setup() -> MyContext {
|
||||
MyContext { value: "Hello, world!".to_string() }
|
||||
}
|
||||
|
||||
fn teardown(self) {
|
||||
// Perform any teardown you wish.
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(MyContext)]
|
||||
#[test]
|
||||
fn test_works(ctx: &mut MyContext) {
|
||||
assert_eq!(ctx.value, "Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Works with other 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) that turn your test function into an async
|
||||
function.
|
||||
|
||||
```rust
|
||||
use test_context::{test_context, AsyncTestContext};
|
||||
|
||||
struct MyAsyncContext {
|
||||
value: String
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AsyncTestContext for MyAsyncContext {
|
||||
async fn setup() -> MyAsyncContext {
|
||||
MyAsyncContext { value: "Hello, world!".to_string() }
|
||||
}
|
||||
|
||||
async fn teardown(self) {
|
||||
// Perform any teradown you wish.
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(MyAsyncContext)]
|
||||
#[tokio::test]
|
||||
async fn test_works(ctx: &mut MyAsyncContext) {
|
||||
assert_eq!(ctx.value, "Hello, World!");
|
||||
}
|
||||
```
|
||||
17
macros/Cargo.toml
Normal file
17
macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "test-context-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
description = "Macro crate for test-context"
|
||||
homepage = "https://github.com/markhildreth/test-context"
|
||||
repository = "https://github.com/markhildreth/test-context"
|
||||
authors = ["Mark Hildreth <mark.k.hildreth@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.3"
|
||||
syn = { version = "^1", features = ["full"] }
|
||||
85
macros/src/lib.rs
Normal file
85
macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
/// Macro to use on tests to add the setup/teardown functionality of your context.
|
||||
///
|
||||
/// Ordering of this attribute is important, and typically `test_context` should come
|
||||
/// before other test attributes. For example, the following is valid:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[test_context(MyContext)]
|
||||
/// #[test]
|
||||
/// #[ignore]
|
||||
/// fn my_test() {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The following is NOT valid...
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[test]
|
||||
/// #[ignore]
|
||||
/// #[test_context(MyContext)]
|
||||
/// fn my_test() {
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn test_context(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let context_type = syn::parse_macro_input!(attr as syn::Ident);
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let inner_body = &input.block;
|
||||
let attrs = &input.attrs;
|
||||
let is_async = input.sig.asyncness.is_some();
|
||||
|
||||
let wrapped_name = format_ident!("__test_context_wrapped_{}", name);
|
||||
|
||||
let outer_body = if is_async {
|
||||
quote! {
|
||||
{
|
||||
use futures::FutureExt;
|
||||
let mut ctx = <#context_type as test_context::AsyncTestContext>::setup().await;
|
||||
let wrapped_ctx = &mut ctx;
|
||||
let result = async move {
|
||||
std::panic::AssertUnwindSafe(
|
||||
#wrapped_name(wrapped_ctx)
|
||||
).catch_unwind().await
|
||||
}.await;
|
||||
<#context_type as test_context::AsyncTestContext>::teardown(ctx).await;
|
||||
if let Err(err) = result {
|
||||
std::panic::resume_unwind(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
{
|
||||
let mut ctx = <#context_type as test_context::TestContext>::setup();
|
||||
let mut wrapper = std::panic::AssertUnwindSafe(&mut ctx);
|
||||
let result = std::panic::catch_unwind(move || {
|
||||
#wrapped_name(*wrapper);
|
||||
});
|
||||
<#context_type as test_context::TestContext>::teardown(ctx);
|
||||
if let Err(err) = result {
|
||||
std::panic::resume_unwind(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let async_tag = if is_async {
|
||||
quote! { async }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let result = quote! {
|
||||
#(#attrs)*
|
||||
#async_tag fn #name() #ret #outer_body
|
||||
|
||||
#async_tag fn #wrapped_name(ctx: &mut #context_type) #ret #inner_body
|
||||
};
|
||||
result.into()
|
||||
}
|
||||
83
src/lib.rs
Normal file
83
src/lib.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
//! A library for providing custom setup/teardown for Rust tests without needing a test harness.
|
||||
//!
|
||||
//! ```
|
||||
//! use test_context::{test_context, TestContext};
|
||||
//!
|
||||
//! struct MyContext {
|
||||
//! value: String
|
||||
//! }
|
||||
//!
|
||||
//! impl TestContext for MyContext {
|
||||
//! fn setup() -> MyContext {
|
||||
//! MyContext { value: "Hello, world!".to_string() }
|
||||
//! }
|
||||
//!
|
||||
//! fn teardown(self) {
|
||||
//! // Perform any teardown you wish.
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[test_context(MyContext)]
|
||||
//! #[test]
|
||||
//! fn test_works(ctx: &mut MyContext) {
|
||||
//! assert_eq!(ctx.value, "Hello, world!");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Works with other 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) that turn your test function into an async
|
||||
//! function.
|
||||
//!
|
||||
//! ```
|
||||
//! use test_context::{test_context, AsyncTestContext};
|
||||
//!
|
||||
//! struct MyAsyncContext {
|
||||
//! value: String
|
||||
//! }
|
||||
//!
|
||||
//! #[async_trait::async_trait]
|
||||
//! impl AsyncTestContext for MyAsyncContext {
|
||||
//! async fn setup() -> MyAsyncContext {
|
||||
//! MyAsyncContext { value: "Hello, world!".to_string() }
|
||||
//! }
|
||||
//!
|
||||
//! async fn teardown(self) {
|
||||
//! // Perform any teradown you wish.
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[test_context(MyAsyncContext)]
|
||||
//! #[tokio::test]
|
||||
//! async fn test_works(ctx: &mut MyAsyncContext) {
|
||||
//! assert_eq!(ctx.value, "Hello, World!");
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub use test_context_macros::test_context;
|
||||
|
||||
/// The trait to implement to get setup/teardown functionality for tests.
|
||||
pub trait TestContext
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Create the context. This is run once before each test that uses the context.
|
||||
fn setup() -> Self;
|
||||
|
||||
/// Perform any additional cleanup of the context besides that already provided by
|
||||
/// normal "drop" semantics.
|
||||
fn teardown(self) {}
|
||||
}
|
||||
|
||||
/// The trait to implement to get setup/teardown functionality for async tests.
|
||||
#[async_trait::async_trait]
|
||||
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) {}
|
||||
}
|
||||
76
tests/test.rs
Normal file
76
tests/test.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use test_context::{test_context, AsyncTestContext, TestContext};
|
||||
|
||||
struct Context {
|
||||
n: u32,
|
||||
}
|
||||
|
||||
impl TestContext for Context {
|
||||
fn setup() -> Self {
|
||||
Self { n: 1 }
|
||||
}
|
||||
|
||||
fn teardown(self) {
|
||||
if self.n != 1 {
|
||||
panic!("Number changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
#[test]
|
||||
fn test_sync_setup(ctx: &mut Context) {
|
||||
assert_eq!(ctx.n, 1);
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
#[test]
|
||||
#[should_panic(expected = "Number changed")]
|
||||
fn test_sync_teardown(ctx: &mut Wrapper) {
|
||||
ctx.n = 2;
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
#[test]
|
||||
#[should_panic(expected = "Number changed")]
|
||||
fn test_panicking_teardown(ctx: &mut Context) {
|
||||
ctx.n = 2;
|
||||
panic!("First panic");
|
||||
}
|
||||
|
||||
struct AsyncContext {
|
||||
n: u32,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
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) {
|
||||
assert_eq!(ctx.n, 1);
|
||||
}
|
||||
|
||||
#[test_context(AsyncContext)]
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "Number changed")]
|
||||
async fn test_async_teardown(ctx: &mut AsyncContext) {
|
||||
ctx.n = 2;
|
||||
}
|
||||
|
||||
#[test_context(AsyncContext)]
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "Number changed")]
|
||||
async fn test_async_panicking_teardown(ctx: &mut AsyncContext) {
|
||||
ctx.n = 2;
|
||||
panic!("First panic");
|
||||
}
|
||||
Loading…
Reference in a new issue