Skip to content

Commit

Permalink
Fix multiple WorldInit derivers conflicting implementations in a si…
Browse files Browse the repository at this point in the history
…ngle module (#150, #148)
  • Loading branch information
ilslv authored Nov 3, 2021
1 parent 17718ca commit 15b5fe6
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 6 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ All user visible changes to `cucumber` crate will be documented in this file. Th



## [0.10.2] · ???
[0.10.2]: /../../tree/v0.10.2

[Diff](/../../compare/v0.10.1...v0.10.2) | [Milestone](/../../milestone/5)

### Fixed

- Multiple `WorldInit` derivers conflicting implementations in a single module. ([#150])

[#150]: /../../pull/150




## [0.10.1] · 2021-10-29
[0.10.1]: /../../tree/v0.10.1

Expand Down
5 changes: 5 additions & 0 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ tokio = { version = "1.12", features = ["macros", "rt-multi-thread", "time"] }
name = "example"
path = "tests/example.rs"
harness = false

[[test]]
name = "two_worlds"
path = "tests/two_worlds.rs"
harness = false
161 changes: 155 additions & 6 deletions codegen/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ pub(crate) fn world_init(
) -> syn::Result<TokenStream> {
let input = syn::parse2::<syn::DeriveInput>(input)?;

let step_types = step_types(steps);
let step_structs = generate_step_structs(steps, &input);

let world = &input.ident;

let step_types = step_types(steps, world);
let step_structs = generate_step_structs(steps, &input);

Ok(quote! {
impl ::cucumber::codegen::WorldInventory<
#( #step_types, )*
Expand All @@ -38,12 +38,12 @@ 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]) -> Vec<syn::Ident> {
fn step_types(steps: &[&str], world: &syn::Ident) -> Vec<syn::Ident> {
steps
.iter()
.map(|step| {
let step = to_pascal_case(step);
format_ident!("Cucumber{}", step)
format_ident!("Cucumber{}{}", step, world)
})
.collect()
}
Expand All @@ -55,7 +55,7 @@ fn generate_step_structs(
) -> Vec<TokenStream> {
let (world, world_vis) = (&world.ident, &world.vis);

step_types(steps)
step_types(steps, world)
.iter()
.map(|ty| {
quote! {
Expand Down Expand Up @@ -101,3 +101,152 @@ fn generate_step_structs(
})
.collect()
}

#[cfg(test)]
mod spec {
use quote::quote;
use syn::parse_quote;

#[test]
fn expand() {
let input = parse_quote! {
pub struct World;
};

let output = quote! {
impl ::cucumber::codegen::WorldInventory<
CucumberGivenWorld, CucumberWhenWorld, CucumberThenWorld,
> for World {}

#[automatically_derived]
#[doc(hidden)]
pub struct CucumberGivenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,

#[doc(hidden)]
pub regex: ::cucumber::codegen::Regex,

#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}

#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberGivenWorld
{
fn new (
loc: ::cucumber::step::Location,
regex: ::cucumber::codegen::Regex,
func: ::cucumber::Step<World>,
) -> Self {
Self { loc, regex, func }
}

fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::Regex,
::cucumber::Step<World>,
) {
(
self.loc.clone(),
self.regex.clone(),
self.func.clone(),
)
}
}

#[automatically_derived]
::cucumber::codegen::collect!(CucumberGivenWorld);

#[automatically_derived]
#[doc(hidden)]
pub struct CucumberWhenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,

#[doc(hidden)]
pub regex: ::cucumber::codegen::Regex,

#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}

#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberWhenWorld
{
fn new (
loc: ::cucumber::step::Location,
regex: ::cucumber::codegen::Regex,
func: ::cucumber::Step<World>,
) -> Self {
Self { loc, regex, func }
}

fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::Regex,
::cucumber::Step<World>,
) {
(
self.loc.clone(),
self.regex.clone(),
self.func.clone(),
)
}
}

#[automatically_derived]
::cucumber::codegen::collect!(CucumberWhenWorld);

#[automatically_derived]
#[doc(hidden)]
pub struct CucumberThenWorld {
#[doc(hidden)]
pub loc: ::cucumber::step::Location,

#[doc(hidden)]
pub regex: ::cucumber::codegen::Regex,

#[doc(hidden)]
pub func: ::cucumber::Step<World>,
}

#[automatically_derived]
impl ::cucumber::codegen::StepConstructor<World> for
CucumberThenWorld
{
fn new (
loc: ::cucumber::step::Location,
regex: ::cucumber::codegen::Regex,
func: ::cucumber::Step<World>,
) -> Self {
Self { loc, regex, func }
}

fn inner(&self) -> (
::cucumber::step::Location,
::cucumber::codegen::Regex,
::cucumber::Step<World>,
) {
(
self.loc.clone(),
self.regex.clone(),
self.func.clone(),
)
}
}

#[automatically_derived]
::cucumber::codegen::collect!(CucumberThenWorld);
};

assert_eq!(
super::world_init(input, &["given", "when", "then"])
.unwrap()
.to_string(),
output.to_string(),
);
}
}
80 changes: 80 additions & 0 deletions codegen/tests/two_worlds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::{convert::Infallible, time::Duration};

use async_trait::async_trait;
use cucumber::{gherkin::Step, given, when, World, WorldInit};
use tokio::time;

#[derive(Debug, WorldInit)]
pub struct FirstWorld {
foo: i32,
}

#[async_trait(?Send)]
impl World for FirstWorld {
type Error = Infallible;

async fn new() -> Result<Self, Self::Error> {
Ok(Self { foo: 0 })
}
}

#[derive(Debug, WorldInit)]
pub struct SecondWorld {
foo: i32,
}

#[async_trait(?Send)]
impl World for SecondWorld {
type Error = Infallible;

async fn new() -> Result<Self, Self::Error> {
Ok(Self { foo: 0 })
}
}

#[given(regex = r"(\S+) is (\d+)")]
#[when(regex = r"(\S+) is (\d+)")]
async fn test_regex_async(
w: &mut FirstWorld,
step: String,
#[step] ctx: &Step,
num: usize,
) {
time::sleep(Duration::new(1, 0)).await;

assert_eq!(step, "foo");
assert_eq!(num, 0);
assert_eq!(ctx.value, "foo is 0");

w.foo += 1;
}

#[given(regex = r"(\S+) is sync (\d+)")]
fn test_regex_sync_slice(w: &mut SecondWorld, step: &Step, matches: &[String]) {
assert_eq!(matches[0], "foo");
assert_eq!(matches[1].parse::<usize>().unwrap(), 0);
assert_eq!(step.value, "foo is sync 0");

w.foo += 1;
}

#[tokio::main]
async fn main() {
let writer = FirstWorld::cucumber()
.max_concurrent_scenarios(None)
.run("./tests/features")
.await;

assert_eq!(writer.steps.passed, 7);
assert_eq!(writer.steps.skipped, 2);
assert_eq!(writer.steps.failed, 0);

let writer = SecondWorld::cucumber()
.max_concurrent_scenarios(None)
.run("./tests/features")
.await;

assert_eq!(writer.steps.passed, 1);
assert_eq!(writer.steps.skipped, 5);
assert_eq!(writer.steps.failed, 0);
}

0 comments on commit 15b5fe6

Please sign in to comment.