From 52779de7aa73f9821a98035cb8ceb08673827bf0 Mon Sep 17 00:00:00 2001
From: Guillaume Toison <86775455+gtoison@users.noreply.github.com>
Date: Thu, 9 Feb 2023 21:11:09 +0000
Subject: [PATCH] fix: use the sq 9.8 api to get the binaries (#706)
- JavaResourceLocator.classFilesToAnalyze() is populated as a side
effect of the sonar-java plugin analysis. When a cached analysis is done
this will not return all .class files and this will cause the SpotBugs
analysis to fail
- Instead use the new API added in SonarQube 9.8 to get the binary
folders (main and test)
- In order to remain compatible with pre-9.8 call the new method using
reflection
- Now that we get the test binaries, updated ByteCodeResourceLocator to
also look for sources in the standard maven/java folder so we can report
issues in test sources
- Added a unit test to a sample project so the intergration test can
confirm that we report the issues
---
pom.xml | 4 +
.../findbugs/FindbugsConfiguration.java | 66 +++++++++---
.../findbugs/classpath/ClasspathLocator.java | 38 +++++++
.../classpath/DefaultClasspathLocator.java | 81 ++++++++++++++
.../resource/ByteCodeResourceLocator.java | 11 +-
.../findbugs/FindbugsConfigurationTest.java | 102 +++++++++++++-----
.../sonar/plugins/findbugs/it/FindbugsIT.java | 4 +-
.../plugins/findbugs/it/MultiModuleIT.java | 5 +
.../multi-module-core/build.gradle | 7 ++
.../multi-module/multi-module-core/pom.xml | 7 ++
.../java/multimodule/core/SampleCoreTest.java | 26 +++++
11 files changed, 307 insertions(+), 44 deletions(-)
create mode 100644 src/main/java/org/sonar/plugins/findbugs/classpath/ClasspathLocator.java
create mode 100644 src/main/java/org/sonar/plugins/findbugs/classpath/DefaultClasspathLocator.java
create mode 100644 src/test/resources/projects/multi-module/multi-module-core/src/test/java/multimodule/core/SampleCoreTest.java
diff --git a/pom.xml b/pom.xml
index 0fff7f89..b3b48e11 100644
--- a/pom.xml
+++ b/pom.xml
@@ -344,6 +344,10 @@
${surefireArgLine}
false
false
+
+
+ projects/**
+
diff --git a/src/main/java/org/sonar/plugins/findbugs/FindbugsConfiguration.java b/src/main/java/org/sonar/plugins/findbugs/FindbugsConfiguration.java
index 623e38dd..ea6a1adc 100644
--- a/src/main/java/org/sonar/plugins/findbugs/FindbugsConfiguration.java
+++ b/src/main/java/org/sonar/plugins/findbugs/FindbugsConfiguration.java
@@ -26,6 +26,7 @@
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -53,6 +54,8 @@
import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.plugins.findbugs.classpath.ClasspathLocator;
+import org.sonar.plugins.findbugs.classpath.DefaultClasspathLocator;
import org.sonar.plugins.findbugs.rules.FbContribRulesDefinition;
import org.sonar.plugins.findbugs.rules.FindSecurityBugsRulesDefinition;
import org.sonar.plugins.findbugs.rules.FindbugsRulesDefinition;
@@ -94,12 +97,21 @@ public File getTargetXMLReport() {
}
public void initializeFindbugsProject(Project findbugsProject) throws IOException {
- List classFilesToAnalyze = buildClassFilesToAnalyze();
+ initializeFindbugsProject(findbugsProject, new DefaultClasspathLocator(javaResourceLocator));
+ }
+
+ void initializeFindbugsProject(Project findbugsProject, ClasspathLocator classpathLocator) throws IOException {
+ List classFilesToAnalyze = buildClassFilesToAnalyze(classpathLocator);
- for (File file : javaResourceLocator.classpath()) {
+ for (File file : classpathLocator.classpath()) {
//Auxiliary dependencies
findbugsProject.addAuxClasspathEntry(file.getCanonicalPath());
}
+
+ for (File file : classpathLocator.testClasspath()) {
+ //Auxiliary tests dependencies
+ findbugsProject.addAuxClasspathEntry(file.getCanonicalPath());
+ }
ClassScreener classScreener = getOnlyAnalyzeFilter();
@@ -243,23 +255,47 @@ File saveIncludeConfigXml() throws IOException {
return file;
}
- private List buildClassFilesToAnalyze() throws IOException {
+ private List buildClassFilesToAnalyze(ClasspathLocator classpathLocator) throws IOException {
+ Collection binaryDirs = classpathLocator.binaryDirs();
+
+ if (binaryDirs.isEmpty()) {
+ return buildClassFilesToAnalyzePre98();
+ } else {
+ // It's probably redundant to use javaResourceLocator.classFilesToAnalyze() here, we'll get all the binaries later
+ List classFilesToAnalyze = new ArrayList<>(javaResourceLocator.classFilesToAnalyze());
+
+ addClassFilesFromClasspath(classFilesToAnalyze, binaryDirs);
+
+ boolean hasJspFiles = fileSystem.hasFiles(fileSystem.predicates().hasLanguage("jsp"));
+ if (hasJspFiles) {
+ checkForMissingPrecompiledJsp(classFilesToAnalyze);
+ }
+
+ addClassFilesFromClasspath(classFilesToAnalyze, classpathLocator.testBinaryDirs());
+
+ return classFilesToAnalyze;
+ }
+ }
+
+ private List buildClassFilesToAnalyzePre98() throws IOException {
List classFilesToAnalyze = new ArrayList<>(javaResourceLocator.classFilesToAnalyze());
boolean hasScalaOrKotlinFiles = fileSystem.hasFiles(fileSystem.predicates().hasLanguages("scala", "kotlin"));
boolean hasJspFiles = fileSystem.hasFiles(fileSystem.predicates().hasLanguage("jsp"));
+ Collection classpath = javaResourceLocator.classpath();
+
// javaResourceLocator.classFilesToAnalyze() only contains .class files from Java sources
if (hasScalaOrKotlinFiles) {
// Add all the .class files from the classpath
// For Gradle multi-module projects this will unfortunately include compiled .class files from dependency modules
- addClassFilesFromClasspath(classFilesToAnalyze);
+ addClassFilesFromClasspath(classFilesToAnalyze, classpath);
} else if (hasJspFiles) {
// Add the precompiled JSP .class files
- addPrecompiledJspClasses(classFilesToAnalyze);
+ addPrecompiledJspClasses(classFilesToAnalyze, classpath);
} else if (classFilesToAnalyze.isEmpty()) {
// For some users javaResourceLocator.classFilesToAnalyze() seems to return an empty list, it is unclear why
- addClassFilesFromClasspath(classFilesToAnalyze);
+ addClassFilesFromClasspath(classFilesToAnalyze, classpath);
}
return classFilesToAnalyze;
@@ -272,9 +308,13 @@ private List buildClassFilesToAnalyze() throws IOException {
*
* @throws IOException In case an exception was thrown when building a file canonical path
*/
- public void addPrecompiledJspClasses(List classFilesToAnalyze) throws IOException {
- addClassFilesFromClasspath(classFilesToAnalyze, FindbugsConfiguration::isPrecompiledJspClassFile);
+ public void addPrecompiledJspClasses(List classFilesToAnalyze, Collection classpath) throws IOException {
+ addClassFilesFromClasspath(classFilesToAnalyze, classpath, FindbugsConfiguration::isPrecompiledJspClassFile);
+ checkForMissingPrecompiledJsp(classFilesToAnalyze);
+ }
+
+ public void checkForMissingPrecompiledJsp(List classFilesToAnalyze) {
boolean hasPrecompiledJsp = hasPrecompiledJsp(classFilesToAnalyze);
if (!hasPrecompiledJsp) {
@@ -289,12 +329,12 @@ public void addPrecompiledJspClasses(List classFilesToAnalyze) throws IOEx
*
* @param classFilesToAnalyze The current list of class files to analyze
*/
- private void addClassFilesFromClasspath(List classFilesToAnalyze) {
- addClassFilesFromClasspath(classFilesToAnalyze, f -> f.getName().endsWith(".class"));
+ private void addClassFilesFromClasspath(Collection classFilesToAnalyze, Collection classpath) {
+ addClassFilesFromClasspath(classFilesToAnalyze, classpath, f -> f.getName().endsWith(".class"));
}
-
- private void addClassFilesFromClasspath(List classFilesToAnalyze, Predicate filePredicate) {
- for (File file : javaResourceLocator.classpath()) {
+
+ private void addClassFilesFromClasspath(Collection classFilesToAnalyze, Collection classpath, Predicate filePredicate) {
+ for (File file : classpath) {
//Will capture additional classes including precompiled JSP
if(file.isDirectory()) { // will include "/target/classes" and other non-standard folders
classFilesToAnalyze.addAll(scanForAdditionalClasses(file, filePredicate));
diff --git a/src/main/java/org/sonar/plugins/findbugs/classpath/ClasspathLocator.java b/src/main/java/org/sonar/plugins/findbugs/classpath/ClasspathLocator.java
new file mode 100644
index 00000000..c62a1e6d
--- /dev/null
+++ b/src/main/java/org/sonar/plugins/findbugs/classpath/ClasspathLocator.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube Findbugs Plugin
+ * Copyright (C) 2012 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.findbugs.classpath;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * @author gtoison
+ *
+ */
+public interface ClasspathLocator {
+
+ Collection binaryDirs();
+
+ Collection classpath();
+
+ Collection testBinaryDirs();
+
+ Collection testClasspath();
+}
diff --git a/src/main/java/org/sonar/plugins/findbugs/classpath/DefaultClasspathLocator.java b/src/main/java/org/sonar/plugins/findbugs/classpath/DefaultClasspathLocator.java
new file mode 100644
index 00000000..a8e0b8b4
--- /dev/null
+++ b/src/main/java/org/sonar/plugins/findbugs/classpath/DefaultClasspathLocator.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube Findbugs Plugin
+ * Copyright (C) 2012 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.findbugs.classpath;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.plugins.java.api.JavaResourceLocator;
+
+/**
+ * @author gtoison
+ *
+ */
+public class DefaultClasspathLocator implements ClasspathLocator {
+ @SuppressWarnings("rawtypes")
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ private static final Logger LOG = Loggers.get(DefaultClasspathLocator.class);
+
+ private JavaResourceLocator javaResourceLocator;
+
+ public DefaultClasspathLocator(JavaResourceLocator javaResourceLocator) {
+ this.javaResourceLocator = javaResourceLocator;
+ }
+
+ @Override
+ public Collection binaryDirs() {
+ return callNoArgMethodReturningFilesCollection("binaryDirs");
+ }
+
+ @Override
+ public Collection classpath() {
+ return javaResourceLocator.classpath();
+ }
+
+ @Override
+ public Collection testBinaryDirs() {
+ return callNoArgMethodReturningFilesCollection("testBinaryDirs");
+ }
+
+ @Override
+ public Collection testClasspath() {
+ return callNoArgMethodReturningFilesCollection("testClasspath");
+ }
+
+ @SuppressWarnings("unchecked")
+ private Collection callNoArgMethodReturningFilesCollection(String methodName) {
+ try {
+ Method method = JavaResourceLocator.class.getDeclaredMethod(methodName, EMPTY_CLASS_ARRAY);
+ return (Collection) method.invoke(javaResourceLocator, EMPTY_OBJECT_ARRAY);
+ } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ LOG.info("JavaResourceLocator." + methodName + "() not available before SonarQube 9.8");
+ LOG.debug("Error calling JavaResourceLocator." + methodName + "()", e);
+
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/src/main/java/org/sonar/plugins/findbugs/resource/ByteCodeResourceLocator.java b/src/main/java/org/sonar/plugins/findbugs/resource/ByteCodeResourceLocator.java
index cb0e69ab..e9064fe0 100644
--- a/src/main/java/org/sonar/plugins/findbugs/resource/ByteCodeResourceLocator.java
+++ b/src/main/java/org/sonar/plugins/findbugs/resource/ByteCodeResourceLocator.java
@@ -47,7 +47,16 @@ public class ByteCodeResourceLocator {
private static final Logger LOG = LoggerFactory.getLogger(ByteCodeResourceLocator.class);
- private static final String[] SOURCE_DIRECTORIES = {"src/main/java","src/main/webapp","src/main/resources", "src", "src/java", "app", "src/main/scala"};
+ private static final String[] SOURCE_DIRECTORIES = {
+ "src/main/java",
+ "src/main/webapp",
+ "src/main/resources",
+ "src",
+ "src/java",
+ "app",
+ "src/main/scala",
+ "src/test/java"
+ };
/**
* findSourceFileKeyByClassName() is broken in SonarQube 6.3.1.. This method is fixing it.
diff --git a/src/test/java/org/sonar/plugins/findbugs/FindbugsConfigurationTest.java b/src/test/java/org/sonar/plugins/findbugs/FindbugsConfigurationTest.java
index 69e88cc0..bdbe99fa 100644
--- a/src/test/java/org/sonar/plugins/findbugs/FindbugsConfigurationTest.java
+++ b/src/test/java/org/sonar/plugins/findbugs/FindbugsConfigurationTest.java
@@ -19,10 +19,13 @@
*/
package org.sonar.plugins.findbugs;
-import com.google.common.collect.ImmutableList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import edu.umd.cs.findbugs.ClassScreener;
-import edu.umd.cs.findbugs.Project;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -34,6 +37,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.FilePredicates;
@@ -41,17 +46,16 @@
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.plugins.findbugs.classpath.ClasspathLocator;
import org.sonar.plugins.findbugs.configuration.SimpleConfiguration;
import org.sonar.plugins.findbugs.rule.FakeActiveRules;
import org.sonar.plugins.findbugs.util.JupiterLogTester;
import org.sonar.plugins.java.api.JavaResourceLocator;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import com.google.common.collect.ImmutableList;
+
+import edu.umd.cs.findbugs.ClassScreener;
+import edu.umd.cs.findbugs.Project;
class FindbugsConfigurationTest {
@@ -69,6 +73,7 @@ class FindbugsConfigurationTest {
private ActiveRules activeRules;
private FindbugsConfiguration conf;
private JavaResourceLocator javaResourceLocator;
+ private ClasspathLocator classpathLocator;
@BeforeEach
public void setUp() throws Exception {
@@ -86,6 +91,7 @@ public void setUp() throws Exception {
configuration = new SimpleConfiguration();
javaResourceLocator = mock(JavaResourceLocator.class);
+ classpathLocator = mock(ClasspathLocator.class);
conf = new FindbugsConfiguration(fs, configuration, activeRules, javaResourceLocator);
}
@@ -234,48 +240,67 @@ void scanEmptyFolderForAdditionalClasses() throws IOException {
assertThat(classes).isEmpty();
}
- @Test
- void should_warn_of_missing_precompiled_jsp() throws IOException {
- setupSampleProject(false, true, false);
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void should_warn_of_missing_precompiled_jsp(boolean withSq98Api) throws IOException {
+ setupSampleProject(false, true, false, withSq98Api);
try (Project project = new Project()) {
- conf.initializeFindbugsProject(project);
+ conf.initializeFindbugsProject(project, classpathLocator);
}
- // There should be two warnings:
+ // With the pre SonarQube 9.8 we There should be two warnings:
// - There are JSP but they are not precompiled
// - Findbugs needs sources to be compiled
- assertThat(logTester.getLogs(LoggerLevel.WARN)).hasSize(2);
+ // With the SonarQube 9.8+ API we get the Test.class so only one warning
+ if (withSq98Api) {
+ assertThat(logTester.getLogs(LoggerLevel.WARN)).hasSize(1);
+ } else {
+ assertThat(logTester.getLogs(LoggerLevel.WARN)).hasSize(2);
+ }
}
-
- @Test
- void should_analyze_precompiled_jsp() throws IOException {
- setupSampleProject(true, true, false);
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void should_analyze_precompiled_jsp(boolean withSq98Api) throws IOException {
+ setupSampleProject(true, true, false, withSq98Api);
try (Project project = new Project()) {
- conf.initializeFindbugsProject(project);
+ conf.initializeFindbugsProject(project, classpathLocator);
- assertThat(project.getFileCount()).isEqualTo(3);
+ if (withSq98Api) {
+ // we should also capture the .class that are not from JSP sources and also the unit tests
+ assertThat(project.getFileCount()).isEqualTo(5);
+ } else {
+ assertThat(project.getFileCount()).isEqualTo(3);
+ }
}
assertThat(logTester.getLogs(LoggerLevel.WARN)).isNull();
}
-
- @Test
- void scala_project() throws IOException {
- setupSampleProject(false, false, true);
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void scala_project(boolean withSq98Api) throws IOException {
+ setupSampleProject(false, false, true, withSq98Api);
try (Project project = new Project()) {
- conf.initializeFindbugsProject(project);
+ conf.initializeFindbugsProject(project, classpathLocator);
- assertThat(project.getFileCount()).isEqualTo(1);
- assertThat(project.getFile(0)).endsWith("Test.class");
+ if (withSq98Api) {
+ assertThat(project.getFileCount()).isEqualTo(2);
+ assertThat(project.getFile(0)).endsWith("Test.class");
+ assertThat(project.getFile(1)).endsWith("UnitTest.class");
+ } else {
+ assertThat(project.getFileCount()).isEqualTo(1);
+ assertThat(project.getFile(0)).endsWith("Test.class");
+ }
}
assertThat(logTester.getLogs(LoggerLevel.WARN)).isNull();
}
- private void setupSampleProject(boolean withPrecompiledJsp, boolean withJspFiles, boolean withScalaFiles) throws IOException {
+ private void setupSampleProject(boolean withPrecompiledJsp, boolean withJspFiles, boolean withScalaFiles, boolean withSq98Api) throws IOException {
mockHasLanguagePredicate(withJspFiles, "jsp");
mockHasLanguagesPredicate(withScalaFiles, "scala");
@@ -288,6 +313,9 @@ private void setupSampleProject(boolean withPrecompiledJsp, boolean withJspFiles
// |_ package
// |_ Test.class
// |_ message.txt
+ // |_ test-classes
+ // |_ package
+ // |_ UnitTest.class
File classpath = new File(temp, "classpath");
File jarFile = new File(classpath, "some.jar");
File targetFolder = new File(classpath, "target");
@@ -305,6 +333,14 @@ private void setupSampleProject(boolean withPrecompiledJsp, boolean withJspFiles
Files.createFile(txtFile.toPath());
Files.createFile(moduleInfoFile.toPath());
+ // test binaries
+ File testClassesFolder = new File(targetFolder, "test-classes");
+ File testPackageFolder = new File(testClassesFolder, "package");
+ File unitTestClassFile = new File(testPackageFolder, "UnitTest.class");
+
+ Files.createDirectories(testPackageFolder.toPath());
+ Files.createFile(unitTestClassFile.toPath());
+
if (withPrecompiledJsp) {
File jspClassFile = new File(packageFolder, "page1_jsp.class");
File jspInnerClassFile = new File(packageFolder, "page1_jsp$1.class");
@@ -318,6 +354,14 @@ private void setupSampleProject(boolean withPrecompiledJsp, boolean withJspFiles
List classpathFiles = Arrays.asList(jarFile, classesFolder, jspServletFolder, moduleInfoFile);
when(javaResourceLocator.classpath()).thenReturn(classpathFiles);
+
+ if (withSq98Api) {
+ List binaryDirs = Arrays.asList(classesFolder, jspServletFolder);
+ List testBinaryDirs = Collections.singletonList(testClassesFolder);
+
+ when(classpathLocator.binaryDirs()).thenReturn(binaryDirs);
+ when(classpathLocator.testBinaryDirs()).thenReturn(testBinaryDirs);
+ }
}
private void mockHasLanguagePredicate(boolean predicateReturn, String language) {
diff --git a/src/test/java/org/sonar/plugins/findbugs/it/FindbugsIT.java b/src/test/java/org/sonar/plugins/findbugs/it/FindbugsIT.java
index 2d187041..334498f6 100644
--- a/src/test/java/org/sonar/plugins/findbugs/it/FindbugsIT.java
+++ b/src/test/java/org/sonar/plugins/findbugs/it/FindbugsIT.java
@@ -183,7 +183,9 @@ void only_analyze() throws Exception {
orchestrator.executeBuild(build);
// Check that class was really excluded from Findbugs analysis:
- String findbugsXml = Files.toString(new File(projectDir, ".scannerwork/findbugs-result.xml"), StandardCharsets.UTF_8);
+ // Not sure why but depending on the build the output is either is scannerwork or in target/sonar
+ // For this build the output seems to be in target/sonar
+ String findbugsXml = Files.toString(new File(projectDir, "target/sonar/findbugs-result.xml"), StandardCharsets.UTF_8);
assertThat(findbugsXml).doesNotContain("Findbugs2.class");
diff --git a/src/test/java/org/sonar/plugins/findbugs/it/MultiModuleIT.java b/src/test/java/org/sonar/plugins/findbugs/it/MultiModuleIT.java
index 8ef905c4..2f4202c2 100644
--- a/src/test/java/org/sonar/plugins/findbugs/it/MultiModuleIT.java
+++ b/src/test/java/org/sonar/plugins/findbugs/it/MultiModuleIT.java
@@ -93,6 +93,11 @@ private void checkIssues(String projectKey) {
// There's one native SQ issue for the Hello.scala sample
assertThat(issues.stream().filter(component(projectKey, "multi-module-scala/src/main/scala/Hello.scala"))).hasSize(2);
assertThat(issues.stream().filter(component(projectKey, "multi-module-kotlin/src/main/kotlin/com/bugs/KotlinSample.kt"))).hasSize(2);
+
+ // The API to get the test binaries was added in 9.8
+ if (orchestrator.getServer().version().isGreaterThanOrEquals(9, 8)) {
+ assertThat(issues.stream().filter(component(projectKey, "multi-module-core/src/test/java/multimodule/core/SampleCoreTest.java"))).hasSize(4);
+ }
}
private Predicate component(String projectKey, String fileName) {
diff --git a/src/test/resources/projects/multi-module/multi-module-core/build.gradle b/src/test/resources/projects/multi-module/multi-module-core/build.gradle
index 6d0e498d..45a440e4 100644
--- a/src/test/resources/projects/multi-module/multi-module-core/build.gradle
+++ b/src/test/resources/projects/multi-module/multi-module-core/build.gradle
@@ -1,3 +1,10 @@
dependencies {
implementation 'com.google.code.findbugs:jsr305:3.0.2'
+
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
+}
+
+test {
+ useJUnitPlatform()
}
\ No newline at end of file
diff --git a/src/test/resources/projects/multi-module/multi-module-core/pom.xml b/src/test/resources/projects/multi-module/multi-module-core/pom.xml
index 099aac92..15d929cd 100644
--- a/src/test/resources/projects/multi-module/multi-module-core/pom.xml
+++ b/src/test/resources/projects/multi-module/multi-module-core/pom.xml
@@ -32,5 +32,12 @@
jsr305
3.0.2
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.8.2
+ test
+
\ No newline at end of file
diff --git a/src/test/resources/projects/multi-module/multi-module-core/src/test/java/multimodule/core/SampleCoreTest.java b/src/test/resources/projects/multi-module/multi-module-core/src/test/java/multimodule/core/SampleCoreTest.java
new file mode 100644
index 00000000..43600fab
--- /dev/null
+++ b/src/test/resources/projects/multi-module/multi-module-core/src/test/java/multimodule/core/SampleCoreTest.java
@@ -0,0 +1,26 @@
+/**
+ *
+ */
+package multimodule.core;
+
+import javax.annotation.Nonnegative;
+import org.junit.jupiter.api.Test;
+
+public class SampleCoreTest {
+ private String field;
+
+ @Test
+ public int npe() {
+ System.out.println("test".toString());
+ return field.hashCode();
+ }
+
+ @Test
+ public @Nonnegative int nonnegative() {
+ if (field == null) {
+ return -5;
+ } else {
+ return 42;
+ }
+ }
+}