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(); + } }