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