diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml
index 4f5c42669c..d84d6e2d7c 100644
--- a/java-checks-test-sources/default/pom.xml
+++ b/java-checks-test-sources/default/pom.xml
@@ -998,6 +998,23 @@
jspecify
1.0.0
+
+ io.cucumber
+ cucumber-java
+ 7.20.1
+ provided
+
+
+ io.cucumber
+ cucumber-junit-platform-engine
+ 7.20.1
+ provided
+
+
+ org.junit.platform
+ junit-platform-suite
+ provided
+
diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberTest.java b/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberTest.java
new file mode 100644
index 0000000000..fe51544d8c
--- /dev/null
+++ b/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberTest.java
@@ -0,0 +1,23 @@
+package checks.tests;
+
+import static io.cucumber.junit.platform.engine.Constants.*;
+
+import org.junit.platform.suite.api.*;
+
+@Suite
+@IncludeEngines("cucumber")
+@SelectClasspathResource("com.project.class.path")
+@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value="com.project.class.path")
+public class NoTestInTestClassCheckCucumberTest {}
+
+@Suite
+@org.junit.platform.suite.api.IncludeEngines("cucumber")
+@SelectClasspathResource("com.project.class.path")
+@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value="com.project.class.path")
+class NoTestInTestClassCheckCucumberFQTest {}
+
+@Suite
+@IncludeEngines("bellpepper")
+@SelectClasspathResource("com.project.class.path")
+@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value="com.project.class.path")
+class NoTestInTestClassCheckBellPepperTest {} // Noncompliant
diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java
index b91e394256..2bd83f28aa 100644
--- a/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java
+++ b/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java
@@ -152,8 +152,8 @@ private static boolean containsJUnit3Tests(List members) {
private void checkJunit4AndAboveTestClass(IdentifierTree className, Symbol.TypeSymbol symbol, List members) {
addUsedAnnotations(symbol);
- if (!runWithCucumberOrSuiteOrTheoriesRunner(symbol)
- && members.stream().noneMatch(this::isTestFieldOrMethod)) {
+
+ if (!runWithCucumberOrSuiteOrTheoriesRunner(symbol) && members.stream().noneMatch(this::isTestFieldOrMethod)) {
reportClass(className);
}
}
@@ -170,7 +170,8 @@ private void addUsedAnnotations(Symbol.TypeSymbol classSymbol) {
}
private static boolean runWithCucumberOrSuiteOrTheoriesRunner(Symbol.TypeSymbol symbol) {
- return checkRunWith(symbol, "Cucumber", "Suite", "Theories");
+ return annotatedIncludeEnginesCucumber(symbol)
+ || checkRunWith(symbol, "Cucumber", "Suite", "Theories");
}
private static boolean runWithZohhak(Symbol.TypeSymbol symbol) {
@@ -199,6 +200,32 @@ private static boolean checkRunWithType(Symbol.TypeSymbol value, String... runne
return false;
}
+ /**
+ * True is the symbol is annotated {@code @IncludeEngines("cucumber")}.
+ */
+ private static boolean annotatedIncludeEnginesCucumber(Symbol.TypeSymbol symbol) {
+ SymbolMetadata metadata = symbol.metadata();
+
+ List annotations = metadata.annotations();
+ for (SymbolMetadata.AnnotationInstance annotation: annotations) {
+ if (annotation.symbol().type().fullyQualifiedName().endsWith("IncludeEngines")) {
+ // values are not available in automatic analysis, so assume "cucumber" is there
+ if (annotation.values().isEmpty()) {
+ return true;
+ }
+ // otherwise check the list
+ boolean containsCucumber = annotation.values().stream().anyMatch(annotationValue ->
+ annotationValue.value() instanceof Object[] vals
+ && vals.length == 1
+ && "cucumber".equals(vals[0]));
+ if (containsCucumber) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private boolean isTestFieldOrMethod(Symbol member) {
return member.metadata().annotations().stream().anyMatch(input -> {
Type type = input.symbol().type();
diff --git a/java-checks/src/test/java/org/sonar/java/checks/tests/NoTestInTestClassCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/tests/NoTestInTestClassCheckTest.java
index 78f236f6a8..b9a88adbb6 100644
--- a/java-checks/src/test/java/org/sonar/java/checks/tests/NoTestInTestClassCheckTest.java
+++ b/java-checks/src/test/java/org/sonar/java/checks/tests/NoTestInTestClassCheckTest.java
@@ -93,4 +93,22 @@ void testNg() {
.withCheck(new NoTestInTestClassCheck())
.verifyIssues();
}
+
+ @Test
+ void testCucumber() {
+ CheckVerifier.newVerifier()
+ .onFile(testCodeSourcesPath("checks/tests/NoTestInTestClassCheckCucumberTest.java"))
+ .withCheck(new NoTestInTestClassCheck())
+ .verifyIssues();
+ }
+
+ @Test
+ void testCucumberWithoutSemantic() {
+ CheckVerifier.newVerifier()
+ .onFile(testCodeSourcesPath("checks/tests/NoTestInTestClassCheckCucumberTest.java"))
+ .withCheck(new NoTestInTestClassCheck())
+ .withoutSemantic()
+ // Note, that the sample file contains a noncompliant test, but we are fine with FN in automatic analysis
+ .verifyNoIssues();
+ }
}