mirror of
https://codeberg.org/JasterV/test-context.git
synced 2026-04-26 18:10:06 +00:00
[FIX-CONFLICT-WITH-RSTEST-V2]: semi-stable version, fix test_context function, remove unnecessary field, remove wrapper function, add ability to work with rstest and same
This commit is contained in:
parent
5ce361bc24
commit
71c8cb64c3
3 changed files with 287 additions and 215 deletions
|
|
@ -3,7 +3,7 @@ mod args;
|
|||
use args::TestContextArgs;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::Ident;
|
||||
use syn::{Block, Ident};
|
||||
|
||||
/// Macro to use on tests to add the setup/teardown functionality of your context.
|
||||
///
|
||||
|
|
@ -30,55 +30,44 @@ pub fn test_context(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
let args = syn::parse_macro_input!(attr as TestContextArgs);
|
||||
|
||||
let input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
let ret = &input.sig.output;
|
||||
let name = &input.sig.ident;
|
||||
let arguments = &input.sig.inputs;
|
||||
let 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 (new_input, context_arg_name) = extract_and_remove_context_arg(input.clone());
|
||||
|
||||
let wrapper_body = if is_async {
|
||||
async_wrapper_body(args, &wrapped_name)
|
||||
async_wrapper_body(args, &context_arg_name, &input.block)
|
||||
} else {
|
||||
sync_wrapper_body(args, &wrapped_name)
|
||||
sync_wrapper_body(args, &context_arg_name, &input.block)
|
||||
};
|
||||
|
||||
let async_tag = if is_async {
|
||||
quote! { async }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let mut result_input = new_input;
|
||||
result_input.block = Box::new(syn::parse2(wrapper_body).unwrap());
|
||||
|
||||
quote! {
|
||||
#(#attrs)*
|
||||
#async_tag fn #name() #ret #wrapper_body
|
||||
|
||||
#async_tag fn #wrapped_name(#arguments) #ret #body
|
||||
}
|
||||
.into()
|
||||
quote! { #result_input }.into()
|
||||
}
|
||||
|
||||
fn async_wrapper_body(args: TestContextArgs, wrapped_name: &Ident) -> proc_macro2::TokenStream {
|
||||
fn async_wrapper_body(
|
||||
args: TestContextArgs,
|
||||
context_arg_name: &Option<syn::Ident>,
|
||||
body: &Box<Block>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let context_type = args.context_type;
|
||||
let result_name = format_ident!("wrapped_result");
|
||||
|
||||
let binding = format_ident!("test_ctx");
|
||||
let context_name = context_arg_name.as_ref().unwrap_or(&binding);
|
||||
|
||||
let body = if args.skip_teardown {
|
||||
quote! {
|
||||
let ctx = <#context_type as test_context::AsyncTestContext>::setup().await;
|
||||
let #result_name = std::panic::AssertUnwindSafe(
|
||||
#wrapped_name(ctx)
|
||||
).catch_unwind().await;
|
||||
let #context_name = <#context_type as test_context::AsyncTestContext>::setup().await;
|
||||
let #result_name = std::panic::AssertUnwindSafe( async { #body } ).catch_unwind().await;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let mut ctx = <#context_type as test_context::AsyncTestContext>::setup().await;
|
||||
let ctx_reference = &mut ctx;
|
||||
let #result_name = std::panic::AssertUnwindSafe(
|
||||
#wrapped_name(ctx_reference)
|
||||
).catch_unwind().await;
|
||||
<#context_type as test_context::AsyncTestContext>::teardown(ctx).await;
|
||||
let mut #context_name = <#context_type as test_context::AsyncTestContext>::setup().await;
|
||||
let #result_name = std::panic::AssertUnwindSafe( async { #body } ).catch_unwind().await;
|
||||
<#context_type as test_context::AsyncTestContext>::teardown(#context_name).await;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -93,25 +82,31 @@ fn async_wrapper_body(args: TestContextArgs, wrapped_name: &Ident) -> proc_macro
|
|||
}
|
||||
}
|
||||
|
||||
fn sync_wrapper_body(args: TestContextArgs, wrapped_name: &Ident) -> proc_macro2::TokenStream {
|
||||
fn sync_wrapper_body(
|
||||
args: TestContextArgs,
|
||||
context_arg_name: &Option<syn::Ident>,
|
||||
body: &Box<Block>,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let context_type = args.context_type;
|
||||
let result_name = format_ident!("wrapped_result");
|
||||
|
||||
let binding = format_ident!("test_ctx");
|
||||
let context_name = context_arg_name.as_ref().unwrap_or(&binding);
|
||||
|
||||
let body = if args.skip_teardown {
|
||||
quote! {
|
||||
let ctx = <#context_type as test_context::TestContext>::setup();
|
||||
let #result_name = std::panic::catch_unwind(move || {
|
||||
#wrapped_name(ctx)
|
||||
let #context_name = <#context_type as test_context::TestContext>::setup();
|
||||
let #result_name = std::panic::catch_unwind(|| {
|
||||
#body
|
||||
});
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let mut ctx = <#context_type as test_context::TestContext>::setup();
|
||||
let mut pointer = std::panic::AssertUnwindSafe(&mut ctx);
|
||||
let #result_name = std::panic::catch_unwind(move || {
|
||||
#wrapped_name(*pointer)
|
||||
let mut #context_name = <#context_type as test_context::TestContext>::setup();
|
||||
let #result_name = std::panic::catch_unwind(|| {
|
||||
#body
|
||||
});
|
||||
<#context_type as test_context::TestContext>::teardown(ctx);
|
||||
<#context_type as test_context::TestContext>::teardown(#context_name);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -135,3 +130,24 @@ fn handle_result(result_name: Ident) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_and_remove_context_arg(mut input: syn::ItemFn) -> (syn::ItemFn, Option<syn::Ident>) {
|
||||
let mut context_arg_name = None;
|
||||
let mut new_args = syn::punctuated::Punctuated::new();
|
||||
|
||||
for arg in &input.sig.inputs {
|
||||
if let syn::FnArg::Typed(pat_type) = arg {
|
||||
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||
if let syn::Type::Reference(type_ref) = &*pat_type.ty {
|
||||
// Сохраняем имя аргумента контекста
|
||||
context_arg_name = Some(pat_ident.ident.clone());
|
||||
continue; // Пропускаем этот аргумент
|
||||
}
|
||||
}
|
||||
}
|
||||
new_args.push(arg.clone());
|
||||
}
|
||||
|
||||
input.sig.inputs = new_args;
|
||||
(input, context_arg_name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ test-context-macros = { version = "0.4.0", path = "../test-context-macros/" }
|
|||
futures = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.26.1"
|
||||
tokio = { version = "1.0", features = ["macros", "rt"] }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use rstest::rstest;
|
||||
use test_context::{test_context, AsyncTestContext, TestContext};
|
||||
|
||||
struct Context {
|
||||
|
|
@ -24,191 +25,245 @@ 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 Context) {
|
||||
ctx.n = 2;
|
||||
// #[test_context(Context)]
|
||||
// #[test]
|
||||
// #[should_panic(expected = "Number changed")]
|
||||
// fn test_sync_teardown(ctx: &mut Context) {
|
||||
// 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");
|
||||
// }
|
||||
|
||||
// #[test_context(Context)]
|
||||
// fn return_value_func(ctx: &mut Context) -> u32 {
|
||||
// ctx.n
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn includes_return_value() {
|
||||
// assert_eq!(return_value_func(), 1);
|
||||
// }
|
||||
|
||||
// struct ContextGeneric<T> {
|
||||
// n: u32,
|
||||
// _marker: PhantomData<T>,
|
||||
// }
|
||||
|
||||
// struct ContextGenericType1;
|
||||
// impl TestContext for ContextGeneric<ContextGenericType1> {
|
||||
// fn setup() -> Self {
|
||||
// Self {
|
||||
// n: 1,
|
||||
// _marker: PhantomData,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test_context(ContextGeneric<ContextGenericType1>)]
|
||||
// #[test]
|
||||
// fn test_generic_type(ctx: &mut ContextGeneric<ContextGenericType1>) {
|
||||
// assert_eq!(ctx.n, 1);
|
||||
// }
|
||||
|
||||
// struct ContextGenericType2;
|
||||
// impl TestContext for ContextGeneric<ContextGenericType2> {
|
||||
// fn setup() -> Self {
|
||||
// Self {
|
||||
// n: 2,
|
||||
// _marker: PhantomData,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test_context(ContextGeneric<ContextGenericType2>)]
|
||||
// #[test]
|
||||
// fn test_generic_type_other(ctx: &mut ContextGeneric<ContextGenericType2>) {
|
||||
// assert_eq!(ctx.n, 2);
|
||||
// }
|
||||
|
||||
// struct AsyncContext {
|
||||
// n: u32,
|
||||
// }
|
||||
|
||||
// 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");
|
||||
// }
|
||||
|
||||
// #[test_context(AsyncContext)]
|
||||
// async fn async_return_value_func(ctx: &mut AsyncContext) -> u32 {
|
||||
// ctx.n
|
||||
// }
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn async_includes_return_value() {
|
||||
// assert_eq!(async_return_value_func().await, 1);
|
||||
// }
|
||||
|
||||
// #[test_context(AsyncContext)]
|
||||
// #[test]
|
||||
// fn async_auto_impls_sync(ctx: &mut AsyncContext) {
|
||||
// assert_eq!(ctx.n, 1);
|
||||
// }
|
||||
|
||||
// #[test_context(Context)]
|
||||
// #[test]
|
||||
// fn use_different_name(test_data: &mut Context) {
|
||||
// assert_eq!(test_data.n, 1);
|
||||
// }
|
||||
|
||||
// #[test_context(AsyncContext)]
|
||||
// #[tokio::test]
|
||||
// async fn use_different_name_async(test_data: &mut AsyncContext) {
|
||||
// assert_eq!(test_data.n, 1);
|
||||
// }
|
||||
|
||||
// struct TeardownPanicContext {}
|
||||
|
||||
// impl AsyncTestContext for TeardownPanicContext {
|
||||
// async fn setup() -> Self {
|
||||
// Self {}
|
||||
// }
|
||||
|
||||
// async fn teardown(self) {
|
||||
// panic!("boom!");
|
||||
// }
|
||||
// }
|
||||
|
||||
// // #[test_context(TeardownPanicContext, skip_teardown)]
|
||||
// // #[tokio::test]
|
||||
// // async fn test_async_skip_teardown(mut _ctx: TeardownPanicContext) {}
|
||||
|
||||
// // #[test_context(TeardownPanicContext, skip_teardown)]
|
||||
// // #[test]
|
||||
// // fn test_sync_skip_teardown(mut _ctx: TeardownPanicContext) {}
|
||||
|
||||
// struct GenericContext<T> {
|
||||
// contents: T,
|
||||
// }
|
||||
|
||||
// impl TestContext for GenericContext<u32> {
|
||||
// fn setup() -> Self {
|
||||
// Self { contents: 1 }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl TestContext for GenericContext<String> {
|
||||
// fn setup() -> Self {
|
||||
// Self {
|
||||
// contents: "hello world".to_string(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl AsyncTestContext for GenericContext<u64> {
|
||||
// async fn setup() -> Self {
|
||||
// Self { contents: 1 }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test_context(GenericContext<u32>)]
|
||||
// #[test]
|
||||
// fn test_generic_with_u32(ctx: &mut GenericContext<u32>) {
|
||||
// assert_eq!(ctx.contents, 1);
|
||||
// }
|
||||
|
||||
// #[test_context(GenericContext<String>)]
|
||||
// #[test]
|
||||
// fn test_generic_with_string(ctx: &mut GenericContext<String>) {
|
||||
// assert_eq!(ctx.contents, "hello world");
|
||||
// }
|
||||
|
||||
// #[test_context(GenericContext<u64>)]
|
||||
// #[tokio::test]
|
||||
// async fn test_async_generic(test_ctx: &mut GenericContext<u64>) {
|
||||
// assert_eq!(test_ctx.contents, 1);
|
||||
// }
|
||||
|
||||
struct MyAsyncContext {
|
||||
what_the_of_life: u32,
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
#[test]
|
||||
#[should_panic(expected = "Number changed")]
|
||||
fn test_panicking_teardown(ctx: &mut Context) {
|
||||
ctx.n = 2;
|
||||
panic!("First panic");
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
fn return_value_func(ctx: &mut Context) -> u32 {
|
||||
ctx.n
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn includes_return_value() {
|
||||
assert_eq!(return_value_func(), 1);
|
||||
}
|
||||
|
||||
struct ContextGeneric<T> {
|
||||
n: u32,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
struct ContextGenericType1;
|
||||
impl TestContext for ContextGeneric<ContextGenericType1> {
|
||||
fn setup() -> Self {
|
||||
Self {
|
||||
n: 1,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(ContextGeneric<ContextGenericType1>)]
|
||||
#[test]
|
||||
fn test_generic_type(ctx: &mut ContextGeneric<ContextGenericType1>) {
|
||||
assert_eq!(ctx.n, 1);
|
||||
}
|
||||
|
||||
struct ContextGenericType2;
|
||||
impl TestContext for ContextGeneric<ContextGenericType2> {
|
||||
fn setup() -> Self {
|
||||
Self {
|
||||
n: 2,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(ContextGeneric<ContextGenericType2>)]
|
||||
#[test]
|
||||
fn test_generic_type_other(ctx: &mut ContextGeneric<ContextGenericType2>) {
|
||||
assert_eq!(ctx.n, 2);
|
||||
}
|
||||
|
||||
struct AsyncContext {
|
||||
n: u32,
|
||||
}
|
||||
|
||||
impl AsyncTestContext for AsyncContext {
|
||||
impl AsyncTestContext for MyAsyncContext {
|
||||
async fn setup() -> Self {
|
||||
Self { n: 1 }
|
||||
println!("I guess...");
|
||||
MyAsyncContext {
|
||||
what_the_of_life: 42,
|
||||
}
|
||||
}
|
||||
|
||||
async fn teardown(self) {
|
||||
if self.n != 1 {
|
||||
panic!("Number changed");
|
||||
println!("Answer is {}", self.what_the_of_life);
|
||||
drop(self);
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(MyAsyncContext)]
|
||||
#[rstest]
|
||||
#[case("Hello, World!")]
|
||||
#[tokio::test]
|
||||
async fn test_async_generic_sync(#[case] value: String, test_ctx: &mut MyAsyncContext) {
|
||||
println!("Something happens sync... {}", value);
|
||||
assert_eq!(test_ctx.what_the_of_life, 42);
|
||||
}
|
||||
|
||||
struct MyContext {
|
||||
what_the_of_life: u32,
|
||||
}
|
||||
|
||||
impl TestContext for MyContext {
|
||||
fn setup() -> Self {
|
||||
println!("I guess...");
|
||||
MyContext {
|
||||
what_the_of_life: 42,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
|
||||
#[test_context(AsyncContext)]
|
||||
async fn async_return_value_func(ctx: &mut AsyncContext) -> u32 {
|
||||
ctx.n
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_includes_return_value() {
|
||||
assert_eq!(async_return_value_func().await, 1);
|
||||
}
|
||||
|
||||
#[test_context(AsyncContext)]
|
||||
#[test]
|
||||
fn async_auto_impls_sync(ctx: &mut AsyncContext) {
|
||||
assert_eq!(ctx.n, 1);
|
||||
}
|
||||
|
||||
#[test_context(Context)]
|
||||
#[test]
|
||||
fn use_different_name(test_data: &mut Context) {
|
||||
assert_eq!(test_data.n, 1);
|
||||
}
|
||||
|
||||
#[test_context(AsyncContext)]
|
||||
#[tokio::test]
|
||||
async fn use_different_name_async(test_data: &mut AsyncContext) {
|
||||
assert_eq!(test_data.n, 1);
|
||||
}
|
||||
|
||||
struct TeardownPanicContext {}
|
||||
|
||||
impl AsyncTestContext for TeardownPanicContext {
|
||||
async fn setup() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
async fn teardown(self) {
|
||||
panic!("boom!");
|
||||
fn teardown(self) {
|
||||
println!("Answer is {}", self.what_the_of_life);
|
||||
drop(self);
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(TeardownPanicContext, skip_teardown)]
|
||||
#[tokio::test]
|
||||
async fn test_async_skip_teardown(mut _ctx: TeardownPanicContext) {}
|
||||
|
||||
#[test_context(TeardownPanicContext, skip_teardown)]
|
||||
#[test_context(MyContext)]
|
||||
#[rstest]
|
||||
#[case("Hello, World!")]
|
||||
#[test]
|
||||
fn test_sync_skip_teardown(mut _ctx: TeardownPanicContext) {}
|
||||
|
||||
struct GenericContext<T> {
|
||||
contents: T,
|
||||
}
|
||||
|
||||
impl TestContext for GenericContext<u32> {
|
||||
fn setup() -> Self {
|
||||
Self { contents: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl TestContext for GenericContext<String> {
|
||||
fn setup() -> Self {
|
||||
Self {
|
||||
contents: "hello world".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncTestContext for GenericContext<u64> {
|
||||
async fn setup() -> Self {
|
||||
Self { contents: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[test_context(GenericContext<u32>)]
|
||||
#[test]
|
||||
fn test_generic_with_u32(ctx: &mut GenericContext<u32>) {
|
||||
assert_eq!(ctx.contents, 1);
|
||||
}
|
||||
|
||||
#[test_context(GenericContext<String>)]
|
||||
#[test]
|
||||
fn test_generic_with_string(ctx: &mut GenericContext<String>) {
|
||||
assert_eq!(ctx.contents, "hello world");
|
||||
}
|
||||
|
||||
#[test_context(GenericContext<u64>)]
|
||||
#[tokio::test]
|
||||
async fn test_async_generic(ctx: &mut GenericContext<u64>) {
|
||||
assert_eq!(ctx.contents, 1);
|
||||
fn test_async_generic_with_async(test_ctx: &mut MyContext, #[case] value: String) {
|
||||
println!("Something happens async... {}", value);
|
||||
assert_eq!(test_ctx.what_the_of_life, 42);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue