Skip to content

Commit 15b5fe6

Browse files
authored
Fix multiple WorldInit derivers conflicting implementations in a single module (#150, #148)
1 parent 17718ca commit 15b5fe6

File tree

4 files changed

+254
-6
lines changed

4 files changed

+254
-6
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ All user visible changes to `cucumber` crate will be documented in this file. Th
66

77

88

9+
## [0.10.2] · ???
10+
[0.10.2]: /../../tree/v0.10.2
11+
12+
[Diff](/../../compare/v0.10.1...v0.10.2) | [Milestone](/../../milestone/5)
13+
14+
### Fixed
15+
16+
- Multiple `WorldInit` derivers conflicting implementations in a single module. ([#150])
17+
18+
[#150]: /../../pull/150
19+
20+
21+
22+
923
## [0.10.1] · 2021-10-29
1024
[0.10.1]: /../../tree/v0.10.1
1125

codegen/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ tokio = { version = "1.12", features = ["macros", "rt-multi-thread", "time"] }
3737
name = "example"
3838
path = "tests/example.rs"
3939
harness = false
40+
41+
[[test]]
42+
name = "two_worlds"
43+
path = "tests/two_worlds.rs"
44+
harness = false

codegen/src/derive.rs

Lines changed: 155 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ pub(crate) fn world_init(
2121
) -> syn::Result<TokenStream> {
2222
let input = syn::parse2::<syn::DeriveInput>(input)?;
2323

24-
let step_types = step_types(steps);
25-
let step_structs = generate_step_structs(steps, &input);
26-
2724
let world = &input.ident;
2825

26+
let step_types = step_types(steps, world);
27+
let step_structs = generate_step_structs(steps, &input);
28+
2929
Ok(quote! {
3030
impl ::cucumber::codegen::WorldInventory<
3131
#( #step_types, )*
@@ -38,12 +38,12 @@ pub(crate) fn world_init(
3838
/// Generates [`syn::Ident`]s of generic types for private trait impl.
3939
///
4040
/// [`syn::Ident`]: struct@syn::Ident
41-
fn step_types(steps: &[&str]) -> Vec<syn::Ident> {
41+
fn step_types(steps: &[&str], world: &syn::Ident) -> Vec<syn::Ident> {
4242
steps
4343
.iter()
4444
.map(|step| {
4545
let step = to_pascal_case(step);
46-
format_ident!("Cucumber{}", step)
46+
format_ident!("Cucumber{}{}", step, world)
4747
})
4848
.collect()
4949
}
@@ -55,7 +55,7 @@ fn generate_step_structs(
5555
) -> Vec<TokenStream> {
5656
let (world, world_vis) = (&world.ident, &world.vis);
5757

58-
step_types(steps)
58+
step_types(steps, world)
5959
.iter()
6060
.map(|ty| {
6161
quote! {
@@ -101,3 +101,152 @@ fn generate_step_structs(
101101
})
102102
.collect()
103103
}
104+
105+
#[cfg(test)]
106+
mod spec {
107+
use quote::quote;
108+
use syn::parse_quote;
109+
110+
#[test]
111+
fn expand() {
112+
let input = parse_quote! {
113+
pub struct World;
114+
};
115+
116+
let output = quote! {
117+
impl ::cucumber::codegen::WorldInventory<
118+
CucumberGivenWorld, CucumberWhenWorld, CucumberThenWorld,
119+
> for World {}
120+
121+
#[automatically_derived]
122+
#[doc(hidden)]
123+
pub struct CucumberGivenWorld {
124+
#[doc(hidden)]
125+
pub loc: ::cucumber::step::Location,
126+
127+
#[doc(hidden)]
128+
pub regex: ::cucumber::codegen::Regex,
129+
130+
#[doc(hidden)]
131+
pub func: ::cucumber::Step<World>,
132+
}
133+
134+
#[automatically_derived]
135+
impl ::cucumber::codegen::StepConstructor<World> for
136+
CucumberGivenWorld
137+
{
138+
fn new (
139+
loc: ::cucumber::step::Location,
140+
regex: ::cucumber::codegen::Regex,
141+
func: ::cucumber::Step<World>,
142+
) -> Self {
143+
Self { loc, regex, func }
144+
}
145+
146+
fn inner(&self) -> (
147+
::cucumber::step::Location,
148+
::cucumber::codegen::Regex,
149+
::cucumber::Step<World>,
150+
) {
151+
(
152+
self.loc.clone(),
153+
self.regex.clone(),
154+
self.func.clone(),
155+
)
156+
}
157+
}
158+
159+
#[automatically_derived]
160+
::cucumber::codegen::collect!(CucumberGivenWorld);
161+
162+
#[automatically_derived]
163+
#[doc(hidden)]
164+
pub struct CucumberWhenWorld {
165+
#[doc(hidden)]
166+
pub loc: ::cucumber::step::Location,
167+
168+
#[doc(hidden)]
169+
pub regex: ::cucumber::codegen::Regex,
170+
171+
#[doc(hidden)]
172+
pub func: ::cucumber::Step<World>,
173+
}
174+
175+
#[automatically_derived]
176+
impl ::cucumber::codegen::StepConstructor<World> for
177+
CucumberWhenWorld
178+
{
179+
fn new (
180+
loc: ::cucumber::step::Location,
181+
regex: ::cucumber::codegen::Regex,
182+
func: ::cucumber::Step<World>,
183+
) -> Self {
184+
Self { loc, regex, func }
185+
}
186+
187+
fn inner(&self) -> (
188+
::cucumber::step::Location,
189+
::cucumber::codegen::Regex,
190+
::cucumber::Step<World>,
191+
) {
192+
(
193+
self.loc.clone(),
194+
self.regex.clone(),
195+
self.func.clone(),
196+
)
197+
}
198+
}
199+
200+
#[automatically_derived]
201+
::cucumber::codegen::collect!(CucumberWhenWorld);
202+
203+
#[automatically_derived]
204+
#[doc(hidden)]
205+
pub struct CucumberThenWorld {
206+
#[doc(hidden)]
207+
pub loc: ::cucumber::step::Location,
208+
209+
#[doc(hidden)]
210+
pub regex: ::cucumber::codegen::Regex,
211+
212+
#[doc(hidden)]
213+
pub func: ::cucumber::Step<World>,
214+
}
215+
216+
#[automatically_derived]
217+
impl ::cucumber::codegen::StepConstructor<World> for
218+
CucumberThenWorld
219+
{
220+
fn new (
221+
loc: ::cucumber::step::Location,
222+
regex: ::cucumber::codegen::Regex,
223+
func: ::cucumber::Step<World>,
224+
) -> Self {
225+
Self { loc, regex, func }
226+
}
227+
228+
fn inner(&self) -> (
229+
::cucumber::step::Location,
230+
::cucumber::codegen::Regex,
231+
::cucumber::Step<World>,
232+
) {
233+
(
234+
self.loc.clone(),
235+
self.regex.clone(),
236+
self.func.clone(),
237+
)
238+
}
239+
}
240+
241+
#[automatically_derived]
242+
::cucumber::codegen::collect!(CucumberThenWorld);
243+
};
244+
245+
assert_eq!(
246+
super::world_init(input, &["given", "when", "then"])
247+
.unwrap()
248+
.to_string(),
249+
output.to_string(),
250+
);
251+
}
252+
}

codegen/tests/two_worlds.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use std::{convert::Infallible, time::Duration};
2+
3+
use async_trait::async_trait;
4+
use cucumber::{gherkin::Step, given, when, World, WorldInit};
5+
use tokio::time;
6+
7+
#[derive(Debug, WorldInit)]
8+
pub struct FirstWorld {
9+
foo: i32,
10+
}
11+
12+
#[async_trait(?Send)]
13+
impl World for FirstWorld {
14+
type Error = Infallible;
15+
16+
async fn new() -> Result<Self, Self::Error> {
17+
Ok(Self { foo: 0 })
18+
}
19+
}
20+
21+
#[derive(Debug, WorldInit)]
22+
pub struct SecondWorld {
23+
foo: i32,
24+
}
25+
26+
#[async_trait(?Send)]
27+
impl World for SecondWorld {
28+
type Error = Infallible;
29+
30+
async fn new() -> Result<Self, Self::Error> {
31+
Ok(Self { foo: 0 })
32+
}
33+
}
34+
35+
#[given(regex = r"(\S+) is (\d+)")]
36+
#[when(regex = r"(\S+) is (\d+)")]
37+
async fn test_regex_async(
38+
w: &mut FirstWorld,
39+
step: String,
40+
#[step] ctx: &Step,
41+
num: usize,
42+
) {
43+
time::sleep(Duration::new(1, 0)).await;
44+
45+
assert_eq!(step, "foo");
46+
assert_eq!(num, 0);
47+
assert_eq!(ctx.value, "foo is 0");
48+
49+
w.foo += 1;
50+
}
51+
52+
#[given(regex = r"(\S+) is sync (\d+)")]
53+
fn test_regex_sync_slice(w: &mut SecondWorld, step: &Step, matches: &[String]) {
54+
assert_eq!(matches[0], "foo");
55+
assert_eq!(matches[1].parse::<usize>().unwrap(), 0);
56+
assert_eq!(step.value, "foo is sync 0");
57+
58+
w.foo += 1;
59+
}
60+
61+
#[tokio::main]
62+
async fn main() {
63+
let writer = FirstWorld::cucumber()
64+
.max_concurrent_scenarios(None)
65+
.run("./tests/features")
66+
.await;
67+
68+
assert_eq!(writer.steps.passed, 7);
69+
assert_eq!(writer.steps.skipped, 2);
70+
assert_eq!(writer.steps.failed, 0);
71+
72+
let writer = SecondWorld::cucumber()
73+
.max_concurrent_scenarios(None)
74+
.run("./tests/features")
75+
.await;
76+
77+
assert_eq!(writer.steps.passed, 1);
78+
assert_eq!(writer.steps.skipped, 5);
79+
assert_eq!(writer.steps.failed, 0);
80+
}

0 commit comments

Comments
 (0)