Skip to content

Commit 54ed086

Browse files
committed
[Java] Process glue classes distinctly
Users may provide glue paths where one is a strict subset of hte other. For example: ``` com.example com.example.app ``` Cucumber would scan both packages (and sub packages), and for each process the classes it discovered. This would result in the classes from the `app` package being processed twice. Fixes: #2581
1 parent 1c65b80 commit 54ed086

File tree

10 files changed

+100
-3
lines changed

10 files changed

+100
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
### Removed
1717

1818
### Fixed
19+
- [Java] Process glue classes distinctly ([#2582](https://github.com/cucumber/cucumber-jvm/pull/2582) M.P. Korstanje)
1920

2021
## [7.4.1] (2022-06-23)
2122

guice/src/main/java/io/cucumber/guice/GuiceBackend.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public void loadGlue(Glue glue, List<URI> gluePaths) {
3232
.map(classFinder::scanForClassesInPackage)
3333
.flatMap(Collection::stream)
3434
.filter(InjectorSource.class::isAssignableFrom)
35+
.distinct()
3536
.forEach(container::addClass);
3637
}
3738

guice/src/test/java/io/cucumber/guice/GuiceBackendTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
import java.util.function.Supplier;
1414

1515
import static java.lang.Thread.currentThread;
16+
import static java.util.Arrays.asList;
1617
import static java.util.Collections.singletonList;
1718
import static org.hamcrest.MatcherAssert.assertThat;
1819
import static org.hamcrest.Matchers.is;
1920
import static org.hamcrest.Matchers.notNullValue;
2021
import static org.hamcrest.Matchers.nullValue;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
import static org.mockito.Mockito.times;
2224
import static org.mockito.Mockito.verify;
2325

2426
@ExtendWith({ MockitoExtension.class })
@@ -39,6 +41,14 @@ void finds_injector_source_impls_by_classpath_url() {
3941
verify(factory).addClass(YourInjectorSource.class);
4042
}
4143

44+
@Test
45+
void finds_injector_source_impls_once_by_classpath_url() {
46+
GuiceBackend backend = new GuiceBackend(factory, classLoader);
47+
backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/guice/integration"),
48+
URI.create("classpath:io/cucumber/guice/integration")));
49+
verify(factory, times(1)).addClass(YourInjectorSource.class);
50+
}
51+
4252
@Test
4353
void world_and_snippet_methods_do_nothing() {
4454
GuiceBackend backend = new GuiceBackend(factory, classLoader);

java/src/main/java/io/cucumber/java/JavaBackend.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public void loadGlue(Glue glue, List<URI> gluePaths) {
3737
.map(ClasspathSupport::packageName)
3838
.map(classFinder::scanForClassesInPackage)
3939
.flatMap(Collection::stream)
40+
.distinct()
4041
.forEach(aGlueClass -> scan(aGlueClass, (method, annotation) -> {
4142
container.addClass(method.getDeclaringClass());
4243
glueAdaptor.addDefinition(method, annotation);

java/src/test/java/io/cucumber/java/JavaBackendTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.mockito.junit.jupiter.MockitoExtension;
1515

1616
import java.net.URI;
17+
import java.util.Arrays;
1718
import java.util.List;
1819

1920
import static java.lang.Thread.currentThread;
@@ -27,7 +28,7 @@
2728
import static org.mockito.Mockito.times;
2829
import static org.mockito.Mockito.verify;
2930

30-
@ExtendWith({ MockitoExtension.class })
31+
@ExtendWith(MockitoExtension.class)
3132
class JavaBackendTest {
3233

3334
@Captor
@@ -53,6 +54,14 @@ void finds_step_definitions_by_classpath_url() {
5354
verify(factory).addClass(Steps.class);
5455
}
5556

57+
@Test
58+
void finds_step_definitions_once_by_classpath_url() {
59+
backend.loadGlue(glue,
60+
asList(URI.create("classpath:io/cucumber/java/steps"), URI.create("classpath:io/cucumber/java/steps")));
61+
backend.buildWorld();
62+
verify(factory, times(1)).addClass(Steps.class);
63+
}
64+
5665
@Test
5766
void detects_subclassed_glue_and_throws_exception() {
5867
Executable testMethod = () -> backend.loadGlue(glue, asList(URI.create("classpath:io/cucumber/java/steps"),

java8/src/main/java/io/cucumber/java8/Java8Backend.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public void loadGlue(Glue glue, List<URI> gluePaths) {
4848
.flatMap(Collection::stream)
4949
.filter(glueClass -> !glueClass.isInterface())
5050
.filter(glueClass -> glueClass.getConstructors().length > 0)
51+
.distinct()
5152
.forEach(glueClass -> {
5253
container.addClass(glueClass);
5354
lambdaGlueClasses.add(glueClass);

java8/src/test/java/io/cucumber/java8/Java8BackendTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import org.mockito.junit.jupiter.MockitoExtension;
1111

1212
import java.net.URI;
13+
import java.util.Arrays;
1314

1415
import static java.lang.Thread.currentThread;
16+
import static java.util.Arrays.asList;
1517
import static java.util.Collections.singletonList;
18+
import static org.mockito.Mockito.times;
1619
import static org.mockito.Mockito.verify;
1720

18-
@ExtendWith({ MockitoExtension.class })
21+
@ExtendWith(MockitoExtension.class)
1922
class Java8BackendTest {
2023

2124
@Mock
@@ -38,4 +41,12 @@ void finds_step_definitions_by_classpath_url() {
3841
verify(factory).addClass(Steps.class);
3942
}
4043

44+
@Test
45+
void finds_step_definitions_once_by_classpath_url() {
46+
backend.loadGlue(glue,
47+
asList(URI.create("classpath:io/cucumber/java8/steps"), URI.create("classpath:io/cucumber/java8/steps")));
48+
backend.buildWorld();
49+
verify(factory, times(1)).addClass(Steps.class);
50+
}
51+
4152
}

spring/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<junit-jupiter.version>5.8.2</junit-jupiter.version>
1919
<spring.version>5.3.21</spring.version>
2020
<project.Automatic-Module-Name>io.cucumber.spring</project.Automatic-Module-Name>
21+
<mockito.version>4.6.1</mockito.version>
2122
</properties>
2223

2324
<dependencyManagement>
@@ -109,6 +110,12 @@
109110
<version>${hamcrest.version}</version>
110111
<scope>test</scope>
111112
</dependency>
113+
<dependency>
114+
<groupId>org.mockito</groupId>
115+
<artifactId>mockito-junit-jupiter</artifactId>
116+
<version>${mockito.version}</version>
117+
<scope>test</scope>
118+
</dependency>
112119
</dependencies>
113120

114121
</project>

spring/src/main/java/io/cucumber/spring/SpringBackend.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public void loadGlue(Glue glue, List<URI> gluePaths) {
3131
.map(ClasspathSupport::packageName)
3232
.map(classFinder::scanForClassesInPackage)
3333
.flatMap(Collection::stream)
34-
.filter((Class clazz) -> clazz.getAnnotation(CucumberContextConfiguration.class) != null)
34+
.filter((Class<?> clazz) -> clazz.getAnnotation(CucumberContextConfiguration.class) != null)
35+
.distinct()
3536
.forEach(container::addClass);
3637
}
3738

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.cucumber.spring;
2+
3+
import io.cucumber.core.backend.Glue;
4+
import io.cucumber.core.backend.ObjectFactory;
5+
import io.cucumber.core.backend.StepDefinition;
6+
import io.cucumber.spring.annotationconfig.AnnotationContextConfiguration;
7+
import org.junit.jupiter.api.BeforeEach;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.ExtendWith;
10+
import org.mockito.ArgumentCaptor;
11+
import org.mockito.Captor;
12+
import org.mockito.Mock;
13+
import org.mockito.junit.jupiter.MockitoExtension;
14+
15+
import java.net.URI;
16+
17+
import static java.lang.Thread.currentThread;
18+
import static java.util.Arrays.asList;
19+
import static java.util.Collections.singletonList;
20+
import static org.mockito.Mockito.times;
21+
import static org.mockito.Mockito.verify;
22+
23+
@ExtendWith(MockitoExtension.class)
24+
class SpringBackendTest {
25+
26+
@Mock
27+
private Glue glue;
28+
29+
@Mock
30+
private ObjectFactory factory;
31+
32+
private SpringBackend backend;
33+
34+
@BeforeEach
35+
void createBackend() {
36+
this.backend = new SpringBackend(factory, currentThread()::getContextClassLoader);
37+
}
38+
39+
@Test
40+
void finds_annotation_context_configuration_by_classpath_url() {
41+
backend.loadGlue(glue, singletonList(URI.create("classpath:io/cucumber/spring/annotationconfig")));
42+
backend.buildWorld();
43+
verify(factory).addClass(AnnotationContextConfiguration.class);
44+
}
45+
46+
@Test
47+
void finds_annotaiton_context_configuration_once_by_classpath_url() {
48+
backend.loadGlue(glue, asList(
49+
URI.create("classpath:io/cucumber/spring/annotationconfig"),
50+
URI.create("classpath:io/cucumber/spring/annotationconfig")));
51+
backend.buildWorld();
52+
verify(factory, times(1)).addClass(AnnotationContextConfiguration.class);
53+
}
54+
55+
}

0 commit comments

Comments
 (0)