From c63783bed9ca70c4a4fdc33c94363eb7d8c803f2 Mon Sep 17 00:00:00 2001 From: michael Date: Sun, 23 Oct 2022 23:04:14 +0200 Subject: [PATCH 1/3] #2491 Support @CucumberContextConfiguration as a meta-annotation Using @CucumberContextConfiguration as a meta-annotation caused a CucumberBackendException because SpringFactory only realized raw use of the class. Method hasCucumberContextConfiguration does now recognize the use of @CucumberContextConfiguration as meta-annotation or with inheritance. Also SpringBackend#loadGlue filters out abstract classes and interfaces to not try to instantiate what cannot be instantiated. --- .../io/cucumber/spring/SpringBackend.java | 7 ++++- .../io/cucumber/spring/SpringFactory.java | 7 +++-- .../io/cucumber/spring/SpringBackendTest.java | 14 +++++++-- .../io/cucumber/spring/SpringFactoryTest.java | 12 ++++++++ .../AbstractWithComponentAnnotation.java | 9 ++++++ .../cucontextconfig/WithMetaAnnotation.java | 29 +++++++++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java create mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java diff --git a/cucumber-spring/src/main/java/io/cucumber/spring/SpringBackend.java b/cucumber-spring/src/main/java/io/cucumber/spring/SpringBackend.java index 3449052c0c..1e3a55e976 100644 --- a/cucumber-spring/src/main/java/io/cucumber/spring/SpringBackend.java +++ b/cucumber-spring/src/main/java/io/cucumber/spring/SpringBackend.java @@ -7,6 +7,7 @@ import io.cucumber.core.resource.ClasspathScanner; import io.cucumber.core.resource.ClasspathSupport; +import java.lang.reflect.Modifier; import java.net.URI; import java.util.Collection; import java.util.List; @@ -31,7 +32,8 @@ public void loadGlue(Glue glue, List gluePaths) { .map(ClasspathSupport::packageName) .map(classFinder::scanForClassesInPackage) .flatMap(Collection::stream) - .filter((Class clazz) -> clazz.getAnnotation(CucumberContextConfiguration.class) != null) + .filter(SpringFactory::hasCucumberContextConfiguration) + .filter(this::checkIfOfClassTypeAndNotAbstract) .distinct() .forEach(container::addClass); } @@ -51,4 +53,7 @@ public Snippet getSnippet() { return null; } + private boolean checkIfOfClassTypeAndNotAbstract(Class clazz) { + return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()); + } } diff --git a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java index 40ae720b24..e5127e5933 100644 --- a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java +++ b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java @@ -5,6 +5,7 @@ import io.cucumber.core.resource.ClasspathSupport; import org.apiguardian.api.API; import org.springframework.beans.BeansException; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.stereotype.Component; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.BootstrapWith; @@ -82,8 +83,10 @@ private static void checkNoComponentAnnotations(Class type) { } } - private static boolean hasCucumberContextConfiguration(Class stepClass) { - return stepClass.getAnnotation(CucumberContextConfiguration.class) != null; + public static boolean hasCucumberContextConfiguration(Class stepClass) { + return stepClass.getAnnotation(CucumberContextConfiguration.class) != null + || AnnotatedElementUtils.hasMetaAnnotationTypes(stepClass, CucumberContextConfiguration.class) + || AnnotatedElementUtils.getMergedAnnotation(stepClass, CucumberContextConfiguration.class) != null; } private void checkOnlyOneClassHasCucumberContextConfiguration(Class stepClass) { diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java index c902c54a58..53e72a5ea0 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java @@ -2,13 +2,12 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.ObjectFactory; -import io.cucumber.core.backend.StepDefinition; import io.cucumber.spring.annotationconfig.AnnotationContextConfiguration; +import io.cucumber.spring.cucontextconfig.AbstractWithComponentAnnotation; +import io.cucumber.spring.cucontextconfig.WithMetaAnnotation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -52,4 +51,13 @@ void finds_annotaiton_context_configuration_once_by_classpath_url() { verify(factory, times(1)).addClass(AnnotationContextConfiguration.class); } + @Test + void ignoresAbstractClassWithCucumberContextConfiguration() { + backend.loadGlue(glue, singletonList( + URI.create("classpath:io/cucumber/spring/cucontextconfig"))); + backend.buildWorld(); + verify(factory, times(0)).addClass(AbstractWithComponentAnnotation.class); + verify(factory, times(1)).addClass(WithMetaAnnotation.class); + } + } diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java index 5287400cbc..6515cc6474 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java @@ -13,6 +13,7 @@ import io.cucumber.spring.componentannotation.WithControllerAnnotation; import io.cucumber.spring.contextconfig.BellyStepDefinitions; import io.cucumber.spring.contexthierarchyconfig.WithContextHierarchyAnnotation; +import io.cucumber.spring.cucontextconfig.WithMetaAnnotation; import io.cucumber.spring.dirtiescontextconfig.DirtiesContextBellyStepDefinitions; import io.cucumber.spring.metaconfig.dirties.DirtiesContextBellyMetaStepDefinitions; import io.cucumber.spring.metaconfig.general.BellyMetaStepDefinitions; @@ -359,6 +360,17 @@ void shouldBeStoppableWhenFacedWithFailedApplicationContext(Class contextConf assertDoesNotThrow(factory::stop); } + @ParameterizedTest + @ValueSource(classes = { + WithMetaAnnotation.class, + }) + void shouldNotFailWithCucumberContextConfigurationMetaAnnotation(Class contextConfiguration) { + final ObjectFactory factory = new SpringFactory(); + factory.addClass(contextConfiguration); + + assertDoesNotThrow(factory::start); + } + @CucumberContextConfiguration @ContextConfiguration("classpath:cucumber.xml") public static class WithSpringAnnotations { diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java new file mode 100644 index 0000000000..c98e14a196 --- /dev/null +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java @@ -0,0 +1,9 @@ +package io.cucumber.spring.cucontextconfig; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.test.context.ContextConfiguration; + +@CucumberContextConfiguration +@ContextConfiguration("classpath:cucumber.xml") +public abstract class AbstractWithComponentAnnotation { +} diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java new file mode 100644 index 0000000000..9bfb5129bb --- /dev/null +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java @@ -0,0 +1,29 @@ +package io.cucumber.spring.cucontextconfig; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import java.lang.annotation.*; + +@MyTestAnnotation +public class WithMetaAnnotation { +} + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited + +@CucumberContextConfiguration // Required for Cucumber + Spring +@ComponentScan("some.test.stuff") +@ContextConfiguration(classes = SomeGeneralPurposeTestContext.class) +@TestPropertySource(properties = "testprop=value_for_testing") +@ActiveProfiles("test") +@interface MyTestAnnotation { + +} + +class SomeGeneralPurposeTestContext { +} From 6461925406d4c5754f17076b0d49094078a5eaac Mon Sep 17 00:00:00 2001 From: michael Date: Sun, 30 Oct 2022 20:10:00 +0100 Subject: [PATCH 2/3] #2491 Support @CucumberContextConfiguration as a meta-annotation Adds suggestions from review: renames test package, more focused testing and updates the changelog. Method hasCucumberContextConfiguration now uses AnnotatedElementUtils isAnnotated with considers all merged annotations (including inherited and meta-annotation). --- CHANGELOG.md | 3 ++ .../io/cucumber/spring/SpringFactory.java | 6 ++-- .../io/cucumber/spring/SpringBackendTest.java | 22 ++++++++++++-- .../io/cucumber/spring/SpringFactoryTest.java | 20 ++++++++----- .../cucontextconfig/WithMetaAnnotation.java | 29 ------------------- .../AbstractWithComponentAnnotation.java | 2 +- .../AnnotatedInterface.java | 7 +++++ .../WithInheritedAnnotation.java | 21 ++++++++++++++ .../WithMetaAnnotation.java | 17 +++++++++++ 9 files changed, 83 insertions(+), 44 deletions(-) delete mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java rename cucumber-spring/src/test/java/io/cucumber/spring/{cucontextconfig => cucumbercontextconfigannotation}/AbstractWithComponentAnnotation.java (81%) create mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AnnotatedInterface.java create mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithInheritedAnnotation.java create mode 100644 cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithMetaAnnotation.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 80346c4c1e..b5c380697f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- [Spring] Support @CucumberContextConfiguration as a meta-annotation ([2491](https://github.com/cucumber/cucumber-jvm/issues/2491) Michael Schlatt) + ### Changed - [Core] Update dependency io.cucumber:gherkin to v24.1 - [Core] Delegate encoding and BOM handling to gherkin ([2624](https://github.com/cucumber/cucumber-jvm/issues/2624) M.P. Korstanje) diff --git a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java index e5127e5933..0baef77ee8 100644 --- a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java +++ b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java @@ -83,10 +83,8 @@ private static void checkNoComponentAnnotations(Class type) { } } - public static boolean hasCucumberContextConfiguration(Class stepClass) { - return stepClass.getAnnotation(CucumberContextConfiguration.class) != null - || AnnotatedElementUtils.hasMetaAnnotationTypes(stepClass, CucumberContextConfiguration.class) - || AnnotatedElementUtils.getMergedAnnotation(stepClass, CucumberContextConfiguration.class) != null; + static boolean hasCucumberContextConfiguration(Class stepClass) { + return AnnotatedElementUtils.isAnnotated(stepClass, CucumberContextConfiguration.class); } private void checkOnlyOneClassHasCucumberContextConfiguration(Class stepClass) { diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java index 53e72a5ea0..b510415792 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java @@ -3,8 +3,9 @@ import io.cucumber.core.backend.Glue; import io.cucumber.core.backend.ObjectFactory; import io.cucumber.spring.annotationconfig.AnnotationContextConfiguration; -import io.cucumber.spring.cucontextconfig.AbstractWithComponentAnnotation; -import io.cucumber.spring.cucontextconfig.WithMetaAnnotation; +import io.cucumber.spring.cucumbercontextconfigannotation.AbstractWithComponentAnnotation; +import io.cucumber.spring.cucumbercontextconfigannotation.AnnotatedInterface; +import io.cucumber.spring.cucumbercontextconfigannotation.WithMetaAnnotation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -54,9 +55,24 @@ void finds_annotaiton_context_configuration_once_by_classpath_url() { @Test void ignoresAbstractClassWithCucumberContextConfiguration() { backend.loadGlue(glue, singletonList( - URI.create("classpath:io/cucumber/spring/cucontextconfig"))); + URI.create("classpath:io/cucumber/spring/cucumbercontextconfigannotation"))); backend.buildWorld(); verify(factory, times(0)).addClass(AbstractWithComponentAnnotation.class); + } + + @Test + void ignoresInterfaceWithCucumberContextConfiguration() { + backend.loadGlue(glue, singletonList( + URI.create("classpath:io/cucumber/spring/cucumbercontextconfigannotation"))); + backend.buildWorld(); + verify(factory, times(0)).addClass(AnnotatedInterface.class); + } + + @Test + void considersClassWithCucumberContextConfigurationMetaAnnotation() { + backend.loadGlue(glue, singletonList( + URI.create("classpath:io/cucumber/spring/cucumbercontextconfigannotation"))); + backend.buildWorld(); verify(factory, times(1)).addClass(WithMetaAnnotation.class); } diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java index 6515cc6474..1d0e05df11 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java @@ -13,7 +13,8 @@ import io.cucumber.spring.componentannotation.WithControllerAnnotation; import io.cucumber.spring.contextconfig.BellyStepDefinitions; import io.cucumber.spring.contexthierarchyconfig.WithContextHierarchyAnnotation; -import io.cucumber.spring.cucontextconfig.WithMetaAnnotation; +import io.cucumber.spring.cucumbercontextconfigannotation.WithInheritedAnnotation; +import io.cucumber.spring.cucumbercontextconfigannotation.WithMetaAnnotation; import io.cucumber.spring.dirtiescontextconfig.DirtiesContextBellyStepDefinitions; import io.cucumber.spring.metaconfig.dirties.DirtiesContextBellyMetaStepDefinitions; import io.cucumber.spring.metaconfig.general.BellyMetaStepDefinitions; @@ -360,13 +361,18 @@ void shouldBeStoppableWhenFacedWithFailedApplicationContext(Class contextConf assertDoesNotThrow(factory::stop); } - @ParameterizedTest - @ValueSource(classes = { - WithMetaAnnotation.class, - }) - void shouldNotFailWithCucumberContextConfigurationMetaAnnotation(Class contextConfiguration) { + @Test + void shouldNotFailWithCucumberContextConfigurationMetaAnnotation() { final ObjectFactory factory = new SpringFactory(); - factory.addClass(contextConfiguration); + factory.addClass(WithMetaAnnotation.class); + + assertDoesNotThrow(factory::start); + } + + @Test + void shouldNotFailWithCucumberContextConfigurationInheritedAnnotation() { + final ObjectFactory factory = new SpringFactory(); + factory.addClass(WithInheritedAnnotation.class); assertDoesNotThrow(factory::start); } diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java deleted file mode 100644 index 9bfb5129bb..0000000000 --- a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/WithMetaAnnotation.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.cucumber.spring.cucontextconfig; - -import io.cucumber.spring.CucumberContextConfiguration; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; - -import java.lang.annotation.*; - -@MyTestAnnotation -public class WithMetaAnnotation { -} - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Inherited - -@CucumberContextConfiguration // Required for Cucumber + Spring -@ComponentScan("some.test.stuff") -@ContextConfiguration(classes = SomeGeneralPurposeTestContext.class) -@TestPropertySource(properties = "testprop=value_for_testing") -@ActiveProfiles("test") -@interface MyTestAnnotation { - -} - -class SomeGeneralPurposeTestContext { -} diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AbstractWithComponentAnnotation.java similarity index 81% rename from cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java rename to cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AbstractWithComponentAnnotation.java index c98e14a196..ef90dbcec7 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/cucontextconfig/AbstractWithComponentAnnotation.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AbstractWithComponentAnnotation.java @@ -1,4 +1,4 @@ -package io.cucumber.spring.cucontextconfig; +package io.cucumber.spring.cucumbercontextconfigannotation; import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.test.context.ContextConfiguration; diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AnnotatedInterface.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AnnotatedInterface.java new file mode 100644 index 0000000000..b24756c5f2 --- /dev/null +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/AnnotatedInterface.java @@ -0,0 +1,7 @@ +package io.cucumber.spring.cucumbercontextconfigannotation; + +import io.cucumber.spring.CucumberContextConfiguration; + +@CucumberContextConfiguration +public interface AnnotatedInterface { +} diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithInheritedAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithInheritedAnnotation.java new file mode 100644 index 0000000000..d267494eae --- /dev/null +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithInheritedAnnotation.java @@ -0,0 +1,21 @@ +package io.cucumber.spring.cucumbercontextconfigannotation; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.test.context.ContextConfiguration; + +import java.lang.annotation.*; + +public class WithInheritedAnnotation extends ParentClass { +} + +@InheritableCumberContextConfiguration +class ParentClass { +} + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@CucumberContextConfiguration +@ContextConfiguration("classpath:cucumber.xml") +@Inherited +@interface InheritableCumberContextConfiguration { +} diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithMetaAnnotation.java b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithMetaAnnotation.java new file mode 100644 index 0000000000..b6f7467bf1 --- /dev/null +++ b/cucumber-spring/src/test/java/io/cucumber/spring/cucumbercontextconfigannotation/WithMetaAnnotation.java @@ -0,0 +1,17 @@ +package io.cucumber.spring.cucumbercontextconfigannotation; + +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.test.context.ContextConfiguration; + +import java.lang.annotation.*; + +@MyTestAnnotation +public class WithMetaAnnotation { +} + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@CucumberContextConfiguration +@ContextConfiguration("classpath:cucumber.xml") +@interface MyTestAnnotation { +} From bfb1e2bf53f240988e40581e89ee0046e91c9ff0 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Tue, 1 Nov 2022 12:41:50 +0100 Subject: [PATCH 3/3] Replace manual annotation checking with AnnotatedElementUtils.isAnnotated --- .../io/cucumber/spring/SpringFactory.java | 51 ++++--------------- .../io/cucumber/spring/SpringBackendTest.java | 2 +- .../io/cucumber/spring/SpringFactoryTest.java | 6 +-- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java index 0baef77ee8..f25cc0417f 100644 --- a/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java +++ b/cucumber-spring/src/main/java/io/cucumber/spring/SpringFactory.java @@ -14,13 +14,8 @@ import org.springframework.test.context.TestContextManager; import org.springframework.test.context.web.WebAppConfiguration; -import java.lang.annotation.Annotation; -import java.util.ArrayDeque; import java.util.Collection; -import java.util.Collections; -import java.util.Deque; import java.util.HashSet; -import java.util.Set; /** * Spring based implementation of ObjectFactory. @@ -70,16 +65,14 @@ public boolean addClass(final Class stepClass) { } private static void checkNoComponentAnnotations(Class type) { - for (Annotation annotation : type.getAnnotations()) { - if (hasComponentAnnotation(annotation)) { - throw new CucumberBackendException(String.format("" + - "Glue class %1$s was annotated with @%2$s; marking it as a candidate for auto-detection by " + - "Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by " - + - "spring may lead to duplicate bean definitions. Please remove the @%2$s annotation", - type.getName(), - annotation.annotationType().getSimpleName())); - } + if (AnnotatedElementUtils.isAnnotated(type, Component.class)) { + throw new CucumberBackendException(String.format("" + + "Glue class %1$s was (meta-)annotated with @Component; marking it as a candidate for auto-detection by " + + + "Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by " + + + "spring may lead to duplicate bean definitions. Please remove the @Component (meta-)annotation", + type.getName())); } } @@ -90,7 +83,7 @@ static boolean hasCucumberContextConfiguration(Class stepClass) { private void checkOnlyOneClassHasCucumberContextConfiguration(Class stepClass) { if (withCucumberContextConfiguration != null) { throw new CucumberBackendException(String.format("" + - "Glue class %1$s and %2$s are both annotated with @CucumberContextConfiguration.\n" + + "Glue class %1$s and %2$s are both (meta-)annotated with @CucumberContextConfiguration.\n" + "Please ensure only one class configures the spring context\n" + "\n" + "By default Cucumber scans the entire classpath for context configuration.\n" + @@ -101,32 +94,6 @@ private void checkOnlyOneClassHasCucumberContextConfiguration(Class stepClass } } - private static boolean hasComponentAnnotation(Annotation annotation) { - return hasAnnotation(annotation, Collections.singleton(Component.class)); - } - - private static boolean hasAnnotation(Annotation annotation, Collection> desired) { - Set> seen = new HashSet<>(); - Deque> toCheck = new ArrayDeque<>(); - toCheck.add(annotation.annotationType()); - - while (!toCheck.isEmpty()) { - Class annotationType = toCheck.pop(); - if (desired.contains(annotationType)) { - return true; - } - - seen.add(annotationType); - for (Annotation annotationTypesAnnotations : annotationType.getAnnotations()) { - if (!seen.contains(annotationTypesAnnotations.annotationType())) { - toCheck.add(annotationTypesAnnotations.annotationType()); - } - } - - } - return false; - } - @Override public void start() { if (withCucumberContextConfiguration == null) { diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java index b510415792..f3f8a1b035 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringBackendTest.java @@ -63,7 +63,7 @@ void ignoresAbstractClassWithCucumberContextConfiguration() { @Test void ignoresInterfaceWithCucumberContextConfiguration() { backend.loadGlue(glue, singletonList( - URI.create("classpath:io/cucumber/spring/cucumbercontextconfigannotation"))); + URI.create("classpath:io/cucumber/spring/cucumbercontextconfigannotation"))); backend.buildWorld(); verify(factory, times(0)).addClass(AnnotatedInterface.class); } diff --git a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java index 1d0e05df11..81926d7086 100644 --- a/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java +++ b/cucumber-spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java @@ -263,7 +263,7 @@ void shouldFailIfMultipleClassesWithSpringAnnotationsAreFound() { Executable testMethod = () -> factory.addClass(BellyStepDefinitions.class); CucumberBackendException actualThrown = assertThrows(CucumberBackendException.class, testMethod); assertThat(actualThrown.getMessage(), startsWith( - "Glue class class io.cucumber.spring.contextconfig.BellyStepDefinitions and class io.cucumber.spring.SpringFactoryTest$WithSpringAnnotations are both annotated with @CucumberContextConfiguration.\n" + "Glue class class io.cucumber.spring.contextconfig.BellyStepDefinitions and class io.cucumber.spring.SpringFactoryTest$WithSpringAnnotations are both (meta-)annotated with @CucumberContextConfiguration.\n" + "Please ensure only one class configures the spring context")); } @@ -275,7 +275,7 @@ void shouldFailIfClassWithSpringComponentAnnotationsIsFound() { Executable testMethod = () -> factory.addClass(WithComponentAnnotation.class); CucumberBackendException actualThrown = assertThrows(CucumberBackendException.class, testMethod); assertThat(actualThrown.getMessage(), is(equalTo( - "Glue class io.cucumber.spring.componentannotation.WithComponentAnnotation was annotated with @Component; marking it as a candidate for auto-detection by Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by spring may lead to duplicate bean definitions. Please remove the @Component annotation"))); + "Glue class io.cucumber.spring.componentannotation.WithComponentAnnotation was (meta-)annotated with @Component; marking it as a candidate for auto-detection by Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by spring may lead to duplicate bean definitions. Please remove the @Component (meta-)annotation"))); } @Test @@ -285,7 +285,7 @@ void shouldFailIfClassWithAnnotationAnnotatedWithSpringComponentAnnotationsIsFou Executable testMethod = () -> factory.addClass(WithControllerAnnotation.class); CucumberBackendException actualThrown = assertThrows(CucumberBackendException.class, testMethod); assertThat(actualThrown.getMessage(), is(equalTo( - "Glue class io.cucumber.spring.componentannotation.WithControllerAnnotation was annotated with @Controller; marking it as a candidate for auto-detection by Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by spring may lead to duplicate bean definitions. Please remove the @Controller annotation"))); + "Glue class io.cucumber.spring.componentannotation.WithControllerAnnotation was (meta-)annotated with @Component; marking it as a candidate for auto-detection by Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by spring may lead to duplicate bean definitions. Please remove the @Component (meta-)annotation"))); } @Test