Repository: quarkiverse/quarkus-cucumber
Pull request #213: Fix @CucumberOptions ignored on proxied classes
Author: login: mlotko
Created: 2025-02-25T21:30:28Z | Merged: 2025-03-10T15:01:13Z

I have a project with two separate sets of features I wanted to run separately.
After upgrading from 1.1.0 to 1.2.0 is stopped working.

Debug shos that this line in `CucucmberQuarcusTest.getTests()`
```
        if (optionsProvider.hasOptions(this.getClass())) {
```
resolves to false in 1.2.0, because the test class is being proxied by something (probably related to `@ScenarioScope` feature).


Diff:
diff --git a/integration-tests/src/test/java/io/quarkiverse/cucumber/it/Steps.java b/integration-tests/src/test/java/io/quarkiverse/cucumber/it/Steps.java
index 95c44ad..db7ef07 100644
--- a/integration-tests/src/test/java/io/quarkiverse/cucumber/it/Steps.java
+++ b/integration-tests/src/test/java/io/quarkiverse/cucumber/it/Steps.java
@@ -6,11 +6,13 @@
 
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 
+import io.cucumber.java.en.And;
 import io.cucumber.java.en.Given;
 import io.cucumber.java.en.Then;
 import io.restassured.response.ValidatableResponse;
 
 public class Steps {
+    private static boolean fail = false;
 
     @Inject
     @ConfigProperty(name = "testPath", defaultValue = "/")
@@ -35,4 +37,18 @@ public void response_is_ok() throws Exception {
         result.statusCode(200);
     }
 
+    @And("controlled failure")
+    public void controlled_failure() throws Exception {
+        if (fail) {
+            throw new AssertionError("Unexpected failure");
+        }
+    }
+
+    public static void enableControlledFailure() {
+        fail = true;
+    }
+
+    public static void disableControlledFailure() {
+        fail = false;
+    }
 }
diff --git a/integration-tests/src/test/java/io/quarkiverse/cucumber/it/options/CucumberOptionsTest.java b/integration-tests/src/test/java/io/quarkiverse/cucumber/it/options/CucumberOptionsTest.java
index a177a39..08a99f1 100644
--- a/integration-tests/src/test/java/io/quarkiverse/cucumber/it/options/CucumberOptionsTest.java
+++ b/integration-tests/src/test/java/io/quarkiverse/cucumber/it/options/CucumberOptionsTest.java
@@ -1,10 +1,24 @@
 package io.quarkiverse.cucumber.it.options;
 
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
 import io.quarkiverse.cucumber.CucumberOptions;
 import io.quarkiverse.cucumber.CucumberQuarkusTest;
+import io.quarkiverse.cucumber.it.Steps;
 
 @CucumberOptions(glue = { "io.quarkiverse.cucumber.it" }, tags = "@important", plugin = { "json" })
 public class CucumberOptionsTest extends CucumberQuarkusTest {
+    @BeforeEach
+    public void before() {
+        Steps.enableControlledFailure();
+    }
+
+    @AfterEach
+    public void after() {
+        Steps.disableControlledFailure();
+    }
+
     public static void main(String[] args) {
         runMain(CucumberOptionsTest.class, args);
     }
diff --git a/integration-tests/src/test/resources/out_of_defined_scope.feature b/integration-tests/src/test/resources/out_of_defined_scope.feature
new file mode 100644
index 0000000..2893960
--- /dev/null
+++ b/integration-tests/src/test/resources/out_of_defined_scope.feature
@@ -0,0 +1,4 @@
+Feature: That should not be run if subpackage is declared
+  @important
+  Scenario: That does nothing unless it's run from CucumberOptionsTest
+    And controlled failure
diff --git a/runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java b/runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
index 47d3a97..e047414 100644
--- a/runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
+++ b/runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
@@ -82,11 +82,12 @@ List<DynamicNode> getTests() {
                 .addDefaultSummaryPrinterIfNotDisabled();
 
         QuarkusCucumberOptionsProvider optionsProvider = new QuarkusCucumberOptionsProvider();
-        if (optionsProvider.hasOptions(this.getClass())) {
+        final Class<? extends CucumberQuarkusTest> testClassWithCucumberOptions = findCucumberOptionAnnotatedClassSkippingProxies();
+        if (testClassWithCucumberOptions != null) {
             CucumberOptionsAnnotationParser annotationParser = new CucumberOptionsAnnotationParser()
                     .withOptionsProvider(optionsProvider);
             RuntimeOptions annotationOptions = annotationParser
-                    .parse(this.getClass())
+                    .parse(testClassWithCucumberOptions)
                     .build(systemOptions);
             runtimeOptions = runtimeOptionsBuilder.build(annotationOptions);
         } else {
@@ -173,6 +174,18 @@ List<DynamicNode> getTests() {
         return features;
     }
 
+    private Class<? extends CucumberQuarkusTest> findCucumberOptionAnnotatedClassSkippingProxies() {
+        for (Class<?> candidate = this.getClass(); candidate != CucumberQuarkusTest.class; candidate = candidate
+                .getSuperclass()) {
+            if (candidate.getAnnotation(CucumberOptions.class) != null) {
+                @SuppressWarnings("unchecked")
+                final var castCheckedInALoop = (Class<? extends CucumberQuarkusTest>) candidate;
+                return castCheckedInALoop;
+            }
+        }
+        return null;
+    }
+
     private static CucumberExecutionContext cucumberExecutionContext(EventBus eventBus, RuntimeOptions runtimeOptions,
             ExitStatus exitStatus) {
         ObjectFactory objectFactory = new CdiObjectFactory();


Reviews:
user: login: gsmet
body: Thanks for the PR. I think the patch looks generally fine but could you please drop the code you commented out, amend your commit and force push?

Thanks!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T13:04:41Z
user: login: mlotko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T14:35:45Z
user: login: mlotko
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T14:45:15Z
user: login: gsmet
body: Thanks, let's get this in!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-10T15:01:05Z

Review comments:
diff_hunk: @@ -173,6 +174,20 @@ List<DynamicNode> getTests() {
         return features;
     }
 
+    private Class<? extends CucumberQuarkusTest> findCucumberOptionAnnotatedClassSkippingProxies() {
+        // This is the equivalent of previous implementation - may be removed
+        // return this.getClass().getAnnotation(CucumberOptions.class) != null ? this.getClass() : null;
path: runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
user: login: gsmet
body: Could you drop this?
created_at: 2025-03-10T13:01:56Z
author_association: MEMBER
diff_hunk: @@ -173,6 +174,20 @@ List<DynamicNode> getTests() {
         return features;
     }
 
+    private Class<? extends CucumberQuarkusTest> findCucumberOptionAnnotatedClassSkippingProxies() {
+        // This is the equivalent of previous implementation - may be removed
+        // return this.getClass().getAnnotation(CucumberOptions.class) != null ? this.getClass() : null;
path: runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
user: login: mlotko
body: Sure, give me some time.
I just left it so you can see how does that change situation.
created_at: 2025-03-10T14:35:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,6 +174,20 @@ List<DynamicNode> getTests() {
         return features;
     }
 
+    private Class<? extends CucumberQuarkusTest> findCucumberOptionAnnotatedClassSkippingProxies() {
+        // This is the equivalent of previous implementation - may be removed
+        // return this.getClass().getAnnotation(CucumberOptions.class) != null ? this.getClass() : null;
path: runtime/src/main/java/io/quarkiverse/cucumber/CucumberQuarkusTest.java
user: login: mlotko
body: I fixed that, but there's some problem with obsolete github actions API.
One more thing I've adjusted is changing `isAssignable` call to simple `!= CucumberQuarkusTest` which should be alyways the root of the object calling the method.
created_at: 2025-03-10T14:45:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: mlotko
created_at: 2025-02-26T08:07:56Z
body: Fixes issue https://github.com/quarkiverse/quarkus-cucumber/issues/211 and https://github.com/quarkiverse/quarkus-cucumber/issues/60
author_association: CONTRIBUTOR
user: login: AchrafBelkahla
created_at: 2025-03-10T12:55:40Z
body: hello @bmscomp @christophd @stuartwdouglas @tisoft Any near plans on having this fixed/merged please ? 
author_association: NONE
user: login: gsmet
created_at: 2025-03-10T14:57:49Z
body: I'm having a look at the CI problem. Will rebase once fixed.
author_association: MEMBER