From 2ae5761eebee999b60dfe0d73eb532e6d790b5a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Nov 2021 17:25:09 +0000 Subject: [PATCH 1/5] Update inventory requirement from 0.1.10 to 0.2.0 Updates the requirements on [inventory](https://github.com/dtolnay/inventory) to permit the latest version. - [Release notes](https://github.com/dtolnay/inventory/releases) - [Commits](https://github.com/dtolnay/inventory/compare/0.1.10...0.2.0) --- updated-dependencies: - dependency-name: inventory dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8e719c13..2c4f7015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ structopt = "0.3.25" # "macros" feature dependencies cucumber-codegen = { version = "0.11.0-dev", path = "./codegen", optional = true } -inventory = { version = "0.1.10", optional = true } +inventory = { version = "0.2.0", optional = true } # "output-junit" feature dependencies junit-report = { version = "0.7", optional = true } From 5e1aeb054c0e475e01e7d3cfb0247bd129f97891 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 11 Nov 2021 12:36:40 +0300 Subject: [PATCH 2/5] Upgrade to inventory 0.2 --- README.md | 1 - codegen/src/attribute.rs | 39 +++-- codegen/src/derive.rs | 369 +++++++++++++++++++++++++-------------- src/cli.rs | 14 +- src/codegen.rs | 118 +++++-------- src/cucumber.rs | 99 ++++++----- src/lib.rs | 5 +- src/step.rs | 7 +- 8 files changed, 379 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index 95a214e9..e42dc4a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ Cucumber testing framework for Rust [![Documentation](https://docs.rs/cucumber/badge.svg)](https://docs.rs/cucumber) [![CI](https://github.com/cucumber-rs/cucumber/workflows/CI/badge.svg?branch=master "CI")](https://github.com/cucumber-rs/cucumber/actions?query=workflow%3ACI+branch%3Amaster) [![Rust 1.56+](https://img.shields.io/badge/rustc-1.56+-lightgray.svg "Rust 1.56+")](https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html) -[![Unsafe Forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance) An implementation of the [Cucumber] testing framework for Rust. Fully native, no external test runners or dependencies. diff --git a/codegen/src/attribute.rs b/codegen/src/attribute.rs index 6a65c448..7d73dd89 100644 --- a/codegen/src/attribute.rs +++ b/codegen/src/attribute.rs @@ -12,6 +12,7 @@ use std::mem; +use inflections::case::to_pascal_case; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::{self, Regex}; @@ -101,7 +102,7 @@ impl Step { let func_name = &func.sig.ident; let world = parse_world_from_args(&self.func.sig)?; - let constructor_method = self.constructor_method(); + let step_type = self.step_type(); let (func_args, addon_parsing) = self.fn_arguments_and_additional_parsing()?; @@ -129,7 +130,8 @@ impl Step { ::std::boxed::Box::pin(f) } - #caller_name + let f: ::cucumber::Step<#world> = #caller_name; + f } }; @@ -138,19 +140,26 @@ impl Step { #[automatically_derived] ::cucumber::codegen::submit!( - #![crate = ::cucumber::codegen] { - <#world as ::cucumber::codegen::WorldInventory< - _, _, _, - >>::#constructor_method( + { + // SAFETY + // `func` argument is transmuted from `cucumber::Step`. + unsafe { <#world as ::cucumber::codegen::WorldInventory>:: + #step_type::new( ::cucumber::step::Location { - path: ::std::convert::From::from(::std::file!()), + path: ::std::file!(), line: ::std::line!(), column: ::std::column!(), }, - ::cucumber::codegen::Regex::new(#step_matcher) - .unwrap(), - #step_caller, - ) + #step_matcher, + // SAFETY + // As we transmute fn pointer into `StepHack`, which is + // `#[repr(C)]` over `*const ()`, this is safe on + // platforms where fn pointers and data pointers have + // the same sizes. Realistically this is every platform + // supported by Rust. + // https://bit.ly/3ogfQaZ + unsafe { std::mem::transmute(#step_caller) }, + ) } } ); }) @@ -238,10 +247,10 @@ impl Step { } } - /// Composes a name of the `cucumber::codegen::WorldInventory` method to - /// wire this [`Step`] with. - fn constructor_method(&self) -> syn::Ident { - format_ident!("new_{}", self.attr_name) + /// Composes a name of the `cucumber::codegen::WorldInventory` associated + /// type to wire this [`Step`] with. + fn step_type(&self) -> syn::Ident { + format_ident!("{}", to_pascal_case(self.attr_name)) } /// Returns [`syn::Ident`] and parsing code of the given function's diff --git a/codegen/src/derive.rs b/codegen/src/derive.rs index 507de42a..d9a08168 100644 --- a/codegen/src/derive.rs +++ b/codegen/src/derive.rs @@ -10,11 +10,12 @@ //! `#[derive(WorldInit)]` macro implementation. -use inflections::case::to_pascal_case; -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; +use inflections::case::{to_pascal_case, to_snake_case}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; /// Generates code of `#[derive(WorldInit)]` macro expansion. +#[allow(clippy::similar_names)] pub(crate) fn world_init( input: TokenStream, steps: &[&str], @@ -22,14 +23,23 @@ pub(crate) fn world_init( let input = syn::parse2::(input)?; let world = &input.ident; + let (impl_gen, ty_gen, where_clause) = input.generics.split_for_impl(); let step_types = step_types(steps, world); let step_structs = generate_step_structs(steps, &input); + let (given_mod, given_ty) = step_types[0].clone(); + let (when_mod, when_ty) = step_types[1].clone(); + let (then_mod, then_ty) = step_types[2].clone(); + Ok(quote! { - impl ::cucumber::codegen::WorldInventory< - #( #step_types, )* - > for #world {} + impl<#impl_gen> ::cucumber::codegen::WorldInventory for #world #ty_gen + #where_clause + { + type Given = #given_mod::#given_ty; + type When = #when_mod::#when_ty; + type Then = #then_mod::#then_ty; + } #( #step_structs )* }) @@ -38,12 +48,21 @@ pub(crate) fn world_init( /// Generates [`syn::Ident`]s of generic types for private trait impl. /// /// [`syn::Ident`]: struct@syn::Ident -fn step_types(steps: &[&str], world: &syn::Ident) -> Vec { +fn step_types( + steps: &[&str], + world: &syn::Ident, +) -> Vec<(syn::Ident, syn::Ident)> { steps .iter() .map(|step| { let step = to_pascal_case(step); - format_ident!("Cucumber{}{}", step, world) + let ty = format!("Cucumber{}{}", step, world); + let m = to_snake_case(&ty); + + ( + syn::Ident::new(&m, Span::call_site()), + syn::Ident::new(&ty, Span::call_site()), + ) }) .collect() } @@ -53,50 +72,73 @@ fn generate_step_structs( steps: &[&str], world: &syn::DeriveInput, ) -> Vec { - let (world, world_vis) = (&world.ident, &world.vis); + let (impl_gen, ty_gen, where_clause) = world.generics.split_for_impl(); + let world = &world.ident; step_types(steps, world) .iter() - .map(|ty| { + .map(|(m, ty)| { quote! { #[automatically_derived] #[doc(hidden)] - #world_vis struct #ty { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + pub mod #m { + use super::*; + #[automatically_derived] #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + pub struct #ty { + #[doc(hidden)] + loc: ::cucumber::step::Location, - #[doc(hidden)] - pub func: ::cucumber::Step<#world>, - } + #[doc(hidden)] + regex: &'static str, - #[automatically_derived] - impl ::cucumber::codegen::StepConstructor<#world> for #ty { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step<#world>, - ) -> Self { - Self { loc, regex, func } + #[doc(hidden)] + func: ::cucumber::codegen::SyncHack, } - fn inner(&self) -> ( - ::cucumber::step::Location, - ::cucumber::codegen::Regex, - ::cucumber::Step<#world>, - ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + #[automatically_derived] + impl #ty { + #[doc(hidden)] + /// # Safety + /// + /// `func` argument has to be [`transmute`]d from + /// [`cucumber::Step`]. + /// + /// [`transmute`]: std::mem::transmute + pub const unsafe fn new ( + loc: ::cucumber::step::Location, + regex: &'static str, + func: ::cucumber::codegen::SyncHack, + ) -> Self { + Self { loc, regex, func } + } } - } - #[automatically_derived] - ::cucumber::codegen::collect!(#ty); + #[automatically_derived] + impl<#impl_gen> ::cucumber::codegen::StepConstructor< + #world #ty_gen + > for #ty #where_clause { + fn inner(&self) -> ( + ::cucumber::step::Location, + &'static str, + ::cucumber::Step<#world #ty_gen>, + ) { + ( + self.loc.clone(), + self.regex.clone(), + // SAFETY + // As the only way to construct `Self` in + // calling `Self::new()` method, which enforces + // right invariants. + unsafe { ::std::mem::transmute(self.func) }, + ) + } + } + + #[automatically_derived] + ::cucumber::codegen::collect!(#ty); + } } }) .collect() @@ -114,132 +156,197 @@ mod spec { }; let output = quote! { - impl ::cucumber::codegen::WorldInventory< - CucumberGivenWorld, CucumberWhenWorld, CucumberThenWorld, - > for World {} + impl<> ::cucumber::codegen::WorldInventory for World { + type Given = cucumber_given_world::CucumberGivenWorld; + type When = cucumber_when_world::CucumberWhenWorld; + type Then = cucumber_then_world::CucumberThenWorld; + } #[automatically_derived] #[doc(hidden)] - pub struct CucumberGivenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + pub mod cucumber_given_world { + use super::*; + #[automatically_derived] #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + pub struct CucumberGivenWorld { + #[doc(hidden)] + loc: ::cucumber::step::Location, - #[doc(hidden)] - pub func: ::cucumber::Step, - } + #[doc(hidden)] + regex: &'static str, - #[automatically_derived] - impl ::cucumber::codegen::StepConstructor for - CucumberGivenWorld - { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } + #[doc(hidden)] + func: ::cucumber::codegen::SyncHack, } - fn inner(&self) -> ( - ::cucumber::step::Location, - ::cucumber::codegen::Regex, - ::cucumber::Step, - ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + #[automatically_derived] + impl CucumberGivenWorld { + // TODO: Remove this method, once + // `::Assoc { .. }` is supported. + // https://github.com/rust-lang/rust/issues/86935 + #[doc(hidden)] + /// # Safety + /// + /// `func` argument has to be [`transmute`]d from + /// [`cucumber::Step`]. + /// + /// [`transmute`]: std::mem::transmute + pub const unsafe fn new ( + loc: ::cucumber::step::Location, + regex: &'static str, + func: ::cucumber::codegen::SyncHack, + ) -> Self { + Self { loc, regex, func } + } } - } - #[automatically_derived] - ::cucumber::codegen::collect!(CucumberGivenWorld); + #[automatically_derived] + impl<> ::cucumber::codegen::StepConstructor for + CucumberGivenWorld + { + fn inner(&self) -> ( + ::cucumber::step::Location, + &'static str, + ::cucumber::Step, + ) { + ( + self.loc.clone(), + self.regex.clone(), + // SAFETY + // As the only way to construct `Self` in + // calling `Self::new()` method, which enforces + // right invariants. + unsafe { ::std::mem::transmute(self.func) }, + ) + } + } + + #[automatically_derived] + ::cucumber::codegen::collect!(CucumberGivenWorld); + } #[automatically_derived] #[doc(hidden)] - pub struct CucumberWhenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + pub mod cucumber_when_world { + use super::*; + #[automatically_derived] #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + pub struct CucumberWhenWorld { + #[doc(hidden)] + loc: ::cucumber::step::Location, - #[doc(hidden)] - pub func: ::cucumber::Step, - } + #[doc(hidden)] + regex: &'static str, - #[automatically_derived] - impl ::cucumber::codegen::StepConstructor for - CucumberWhenWorld - { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } + #[doc(hidden)] + func: ::cucumber::codegen::SyncHack, } - fn inner(&self) -> ( - ::cucumber::step::Location, - ::cucumber::codegen::Regex, - ::cucumber::Step, - ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + #[automatically_derived] + impl CucumberWhenWorld { + #[doc(hidden)] + /// # Safety + /// + /// `func` argument has to be [`transmute`]d from + /// [`cucumber::Step`]. + /// + /// [`transmute`]: std::mem::transmute + pub const unsafe fn new ( + loc: ::cucumber::step::Location, + regex: &'static str, + func: ::cucumber::codegen::SyncHack, + ) -> Self { + Self { loc, regex, func } + } } - } - #[automatically_derived] - ::cucumber::codegen::collect!(CucumberWhenWorld); + #[automatically_derived] + impl<> ::cucumber::codegen::StepConstructor for + CucumberWhenWorld + { + fn inner(&self) -> ( + ::cucumber::step::Location, + &'static str, + ::cucumber::Step, + ) { + ( + self.loc.clone(), + self.regex.clone(), + // SAFETY + // As the only way to construct `Self` in + // calling `Self::new()` method, which enforces + // right invariants. + unsafe { ::std::mem::transmute(self.func) }, + ) + } + } + + #[automatically_derived] + ::cucumber::codegen::collect!(CucumberWhenWorld); + } #[automatically_derived] #[doc(hidden)] - pub struct CucumberThenWorld { - #[doc(hidden)] - pub loc: ::cucumber::step::Location, + pub mod cucumber_then_world { + use super::*; + #[automatically_derived] #[doc(hidden)] - pub regex: ::cucumber::codegen::Regex, + pub struct CucumberThenWorld { + #[doc(hidden)] + loc: ::cucumber::step::Location, - #[doc(hidden)] - pub func: ::cucumber::Step, - } + #[doc(hidden)] + regex: &'static str, - #[automatically_derived] - impl ::cucumber::codegen::StepConstructor for - CucumberThenWorld - { - fn new ( - loc: ::cucumber::step::Location, - regex: ::cucumber::codegen::Regex, - func: ::cucumber::Step, - ) -> Self { - Self { loc, regex, func } + #[doc(hidden)] + func: ::cucumber::codegen::SyncHack, } - fn inner(&self) -> ( - ::cucumber::step::Location, - ::cucumber::codegen::Regex, - ::cucumber::Step, - ) { - ( - self.loc.clone(), - self.regex.clone(), - self.func.clone(), - ) + #[automatically_derived] + impl CucumberThenWorld { + #[doc(hidden)] + /// # Safety + /// + /// `func` argument has to be [`transmute`]d from + /// [`cucumber::Step`]. + /// + /// [`transmute`]: std::mem::transmute + pub const unsafe fn new ( + loc: ::cucumber::step::Location, + regex: &'static str, + func: ::cucumber::codegen::SyncHack, + ) -> Self { + Self { loc, regex, func } + } } - } - #[automatically_derived] - ::cucumber::codegen::collect!(CucumberThenWorld); + #[automatically_derived] + impl<> ::cucumber::codegen::StepConstructor for + CucumberThenWorld + { + fn inner(&self) -> ( + ::cucumber::step::Location, + &'static str, + ::cucumber::Step, + ) { + ( + self.loc.clone(), + self.regex.clone(), + // SAFETY + // As the only way to construct `Self` in + // calling `Self::new()` method, which enforces + // right invariants. + unsafe { ::std::mem::transmute(self.func) }, + ) + } + } + + #[automatically_derived] + ::cucumber::codegen::collect!(CucumberThenWorld); + } }; assert_eq!( diff --git a/src/cli.rs b/src/cli.rs index af66cb4c..3dfd5054 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,8 @@ and may be extended with custom CLI options additionally. # } # } # -# let fut = async { +# fn main() { +# let fut = async { #[derive(StructOpt)] struct CustomOpts { /// Additional time to wait in before hook. @@ -85,11 +86,12 @@ MyWorld::cucumber() .await; # }; # -# tokio::runtime::Builder::new_current_thread() -# .enable_all() -# .build() -# .unwrap() -# .block_on(fut); +# tokio::runtime::Builder::new_current_thread() +# .enable_all() +# .build() +# .unwrap() +# .block_on(fut); +# } ``` [`Cucumber`]: crate::Cucumber diff --git a/src/codegen.rs b/src/codegen.rs index 3aef7d0c..8279d09f 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -22,13 +22,7 @@ pub use regex::Regex; /// [`World`] extension with auto-wiring capabilities. #[async_trait(?Send)] -pub trait WorldInit: WorldInventory -where - Self: Debug, - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, -{ +pub trait WorldInit: Debug + WorldInventory { /// Returns runner for tests with auto-wired steps marked by [`given`], /// [`when`] and [`then`] attributes. /// @@ -39,19 +33,34 @@ where fn collection() -> step::Collection { let mut out = step::Collection::new(); - for given in Self::cucumber_given() { + for given in inventory::iter:: { let (loc, regex, fun) = given.inner(); - out = out.given(Some(loc), regex, fun); + out = out.given( + Some(loc), + Regex::new(regex) + .unwrap_or_else(|e| panic!("Regex failed: {}", e)), + fun, + ); } - for when in Self::cucumber_when() { + for when in inventory::iter:: { let (loc, regex, fun) = when.inner(); - out = out.when(Some(loc), regex, fun); + out = out.when( + Some(loc), + Regex::new(regex) + .unwrap_or_else(|e| panic!("Regex failed: {}", e)), + fun, + ); } - for then in Self::cucumber_then() { + for then in inventory::iter:: { let (loc, regex, fun) = then.inner(); - out = out.then(Some(loc), regex, fun); + out = out.then( + Some(loc), + Regex::new(regex) + .unwrap_or_else(|e| panic!("Regex failed: {}", e)), + fun, + ); } out @@ -112,70 +121,24 @@ where } } -impl WorldInit for E -where - Self: Debug, - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, - E: WorldInventory, -{ -} +impl WorldInit for T where T: WorldInventory + Debug {} /// [`World`] extension allowing to register steps in [`inventory`]. -pub trait WorldInventory: World -where - G: StepConstructor + inventory::Collect, - W: StepConstructor + inventory::Collect, - T: StepConstructor + inventory::Collect, -{ - /// Returns an [`Iterator`] over items with [`given`] attribute. +pub trait WorldInventory: World { + /// Struct [`submit`]ted in [`given`] macro. /// /// [`given`]: crate::given - #[must_use] - fn cucumber_given() -> inventory::iter { - inventory::iter - } - - /// Creates a new [`Given`] [`Step`] value. Used by [`given`] attribute. - /// - /// [`given`]: crate::given - /// [Given]: https://cucumber.io/docs/gherkin/reference/#given - fn new_given(loc: step::Location, regex: Regex, fun: Step) -> G { - G::new(loc, regex, fun) - } + type Given: inventory::Collect + StepConstructor; - /// Returns an [`Iterator`] over items with [`when`] attribute. + /// Struct [`submit`]ted in [`when`] macro. /// /// [`when`]: crate::when - #[must_use] - fn cucumber_when() -> inventory::iter { - inventory::iter - } - - /// Creates a new [`When`] [`Step`] value. Used by [`when`] attribute. - /// - /// [`when`]: crate::when - /// [When]: https://cucumber.io/docs/gherkin/reference/#when - fn new_when(loc: step::Location, regex: Regex, fun: Step) -> W { - W::new(loc, regex, fun) - } + type When: inventory::Collect + StepConstructor; - /// Returns an [`Iterator`] over items with [`then`] attribute. + /// Struct [`submit`]ted in [`then`] macro. /// /// [`then`]: crate::then - #[must_use] - fn cucumber_then() -> inventory::iter { - inventory::iter - } - - /// Creates a new [`Then`] [`Step`] value. Used by [`then`] attribute. - /// - /// [`then`]: crate::then - /// [Then]: https://cucumber.io/docs/gherkin/reference/#then - fn new_then(loc: step::Location, regex: Regex, fun: Step) -> T { - T::new(loc, regex, fun) - } + type Then: inventory::Collect + StepConstructor; } /// Trait for creating [`Step`]s to be registered by [`given`], [`when`] and @@ -185,10 +148,21 @@ where /// [`when`]: crate::when /// [`then`]: crate::then pub trait StepConstructor { - /// Creates a new [`Step`] with the corresponding [`Regex`]. - #[must_use] - fn new(_: step::Location, _: Regex, _: Step) -> Self; - /// Returns an inner [`Step`] with the corresponding [`Regex`]. - fn inner(&self) -> (step::Location, Regex, Step); + fn inner(&self) -> (step::Location, &'static str, Step); } + +/// Type used to hack around [`inventory::Collect`] requiring [`Sync`]. +/// +/// # Safety +/// +/// As the only way to get this type is to [`transmute`] some other type, you +/// have to be sure, that [`transmute`]d type is [`Sync`]. +/// +/// [`transmute`]: std::mem::transmute +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct SyncHack(*const ()); + +#[allow(unsafe_code)] +unsafe impl Sync for SyncHack {} diff --git a/src/cucumber.rs b/src/cucumber.rs index 49206a98..11d0c213 100644 --- a/src/cucumber.rs +++ b/src/cucumber.rs @@ -195,14 +195,15 @@ where /// # } /// # } /// # - /// # let fut = async { + /// # fn main() { + /// # let fut = async { /// MyWorld::cucumber() /// .repeat_skipped() /// .run_and_exit("tests/features/readme") /// .await; - /// # }; - /// # - /// # futures::executor::block_on(fut); + /// # }; + /// # futures::executor::block_on(fut); + /// # } /// ``` ///