diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2187.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2187.json index 3e0c2f6a44..67320da791 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2187.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2187.json @@ -1,6 +1,6 @@ { "ruleKey": "S2187", "hasTruePositives": true, - "falseNegatives": 13, + "falseNegatives": 14, "falsePositives": 1 } diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index 4f5c42669c..3a13809867 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -998,6 +998,11 @@ jspecify 1.0.0 + + 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..79589d1014 --- /dev/null +++ b/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberTest.java @@ -0,0 +1,24 @@ +package checks.tests; + +import org.junit.platform.suite.api.IncludeEngines; + +@IncludeEngines("cucumber") +class NoTestInTestClassCheckCucumberStandardTest {} + +@org.junit.platform.suite.api.IncludeEngines("cucumber") +class NoTestInTestClassCheckCucumberFullyQualifiedTest {} + +@IncludeEngines("bellpepper") +class NoTestInTestClassCheckBellPepperTest {} // Noncompliant + +@IncludeEngines({"spring", "cucumber"}) +class NoTestInTestClassCheckTwoEnginesTest{} + +@NoTestInTestClassIncompatibleAnnotations.IncludeEngines(42) +class ClassWithFakeIncludeEnginesAnnotationTest {} // Noncompliant + +class NoTestInTestClassIncompatibleAnnotations { + @interface IncludeEngines { + int value(); + } +} diff --git a/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberWithoutSemanticTest.java b/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberWithoutSemanticTest.java new file mode 100644 index 0000000000..e9016ff134 --- /dev/null +++ b/java-checks-test-sources/default/src/test/java/checks/tests/NoTestInTestClassCheckCucumberWithoutSemanticTest.java @@ -0,0 +1,18 @@ +package checks.tests; + +import org.junit.platform.suite.api.IncludeEngines; + +@IncludeEngines("cucumber") +class NoTestInTestClassCheckCucumberStandardWSTest {} + +@IncludeEngines("cucumber") +class NoTestInTestClassCheckCucumberFullyQualifiedWSTest {} + +@IncludeEngines("bellpepper") +class NoTestInTestClassCheckBellPepperWSTest {} // FN in automatic analysis + +@IncludeEngines({"spring", "cucumber"}) +class NoTestInTestClassCheckTwoEnginesWSTest{} + +@NoTestInTestClassIncompatibleAnnotations.IncludeEngines(42) +class ClassWithFakeIncludeEnginesAnnotationWSTest {} // FN in automatic analysis 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..050df4176d 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 @@ -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,28 @@ private static boolean checkRunWithType(Symbol.TypeSymbol value, String... runne return false; } + /** + * True if the symbol is annotated {@code @IncludeEngines("cucumber")} + * (with some approximation for automatic analysis). + */ + private static boolean annotatedIncludeEnginesCucumber(Symbol.TypeSymbol symbol) { + for (var annotation: symbol.metadata().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 && Arrays.asList(vals).contains("cucumber")); + 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..12d1a9d42c 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,21 @@ 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/NoTestInTestClassCheckCucumberWithoutSemanticTest.java")) + .withCheck(new NoTestInTestClassCheck()) + .withoutSemantic() + .verifyNoIssues(); + } }