diff --git a/its/ruling/src/test/java/org/sonar/java/it/JavaRulingTest.java b/its/ruling/src/test/java/org/sonar/java/it/JavaRulingTest.java index aed5d6b0cd1..f239445e6c3 100644 --- a/its/ruling/src/test/java/org/sonar/java/it/JavaRulingTest.java +++ b/its/ruling/src/test/java/org/sonar/java/it/JavaRulingTest.java @@ -41,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; @@ -190,42 +191,35 @@ private static void copyFile(Path source, Path targetDir) { @Test public void spring_mall() throws Exception { - String projectName = "mall"; - MavenBuild build = test_project("com.macro.mall:mall", projectName); - build - .setProperty("docker.skip", "true") - .setProperty("java.version", "21") - .setProperty("maven-bundle-plugin.version", "5.1.4") - .setProperty("maven.javadoc.skip", "true") - .setProperty("sonar.java.experimental.batchModeSizeInKB", "420"); - executeBuildWithCommonProperties(build, projectName); + RulingProject project = ProjectConfigLoader.requireProject("mall"); + executeMavenBuild(project, Map.of( + "docker.skip", "true", + "java.version", "21", + "maven-bundle-plugin.version", "5.1.4", + "maven.javadoc.skip", "true", + "sonar.java.experimental.batchModeSizeInKB", "420" + )); } @Test public void guava() throws Exception { - String projectName = "guava"; - MavenBuild build = test_project("com.google.guava:guava", projectName); - build - // Keep compilation and analysis on Java 17 without overriding the Java runtime version seen by the scanner. - .setProperty("sonar.java.source", "17") - .setProperty("maven-bundle-plugin.version", "5.1.4") - .setProperty("maven.javadoc.skip", "true") - .setProperty("animal.sniffer.skip", "true") - // use batch - .setProperty("sonar.java.experimental.batchModeSizeInKB", "8192"); - executeBuildWithCommonProperties(build, projectName); + RulingProject project = ProjectConfigLoader.requireProject("guava"); + executeMavenBuild(project, Map.of( + "sonar.java.source", "17", + "maven-bundle-plugin.version", "5.1.4", + "maven.javadoc.skip", "true", + "animal.sniffer.skip", "true", + "sonar.java.experimental.batchModeSizeInKB", "8192" + )); } @Test public void apache_commons_beanutils() throws Exception { - String projectName = "commons-beanutils"; - MavenBuild build = test_project("commons-beanutils:commons-beanutils", projectName); - build - // by default it can not be built with jdk 17 without changing some plugin versions - .setProperty("maven-bundle-plugin.version", "5.1.4") - // use batch - .setProperty("sonar.java.experimental.batchModeSizeInKB", "8192"); - executeBuildWithCommonProperties(build, projectName); + RulingProject project = ProjectConfigLoader.requireProject("commons-beanutils"); + executeMavenBuild(project, Map.of( + "maven-bundle-plugin.version", "5.1.4", + "sonar.java.experimental.batchModeSizeInKB", "8192" + )); } @Test @@ -236,14 +230,15 @@ public void eclipse_jetty_incremental() throws Exception { List dirs = Arrays.asList("jetty-http/", "jetty-io/", "jetty-jmx/", "jetty-server/", "jetty-slf4j-impl/", "jetty-util/", "jetty-util-ajax/", "jetty-xml/", "tests/jetty-http-tools/"); - String mainBranchSourceCode = "eclipse-jetty"; + RulingProject mainProject = ProjectConfigLoader.requireProject("eclipse-jetty"); + String mainBranchSourceCode = mainProject.projectName(); String mainBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + mainBranchSourceCode + "/" + dir + "target/classes")) .map(JavaRulingTest::getFileLocationAbsolutePath) .collect(Collectors.joining(",")); final var mainBranch = "eclipse-jetty-main"; - MavenBuild branchBuild = test_project("org.eclipse.jetty:jetty-project", mainBranchSourceCode) + MavenBuild branchBuild = test_project(mainProject.projectKey(), mainBranchSourceCode) // re-define binaries from initial maven build .setProperty("sonar.java.binaries", mainBinaries) .setProperty("sonar.exclusions", "jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java," + @@ -265,14 +260,15 @@ public void eclipse_jetty_incremental() throws Exception { var time1 = after1 - before1; // Huge PR - String prSourceCode = "eclipse-jetty-similar-to-main"; + RulingProject prProject = ProjectConfigLoader.requireProject("eclipse-jetty-similar-to-main"); + String prSourceCode = prProject.projectName(); String prBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + prSourceCode + "/" + dir + "target/classes")) .map(JavaRulingTest::getFileLocationAbsolutePath) .collect(Collectors.joining(",")); final var prBranch = "eclipse-jetty-same-issues-as-main"; - MavenBuild prBuild = test_existing_project("org.eclipse.jetty:jetty-project", prSourceCode) + MavenBuild prBuild = test_existing_project(prProject.projectKey(), prSourceCode) // re-define binaries from initial maven build .setProperty("sonar.java.binaries", prBinaries) .setProperty("sonar.exclusions", "jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java," + @@ -297,14 +293,15 @@ public void eclipse_jetty_incremental() throws Exception { var time2 = after2 - before2; // Small PR - String smallPrSourceCode = "eclipse-jetty-similar-to-main-small"; + RulingProject smallPrProject = ProjectConfigLoader.requireProject("eclipse-jetty-similar-to-main-small"); + String smallPrSourceCode = smallPrProject.projectName(); String smallPrBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + smallPrSourceCode + "/" + dir + "target/classes")) .map(JavaRulingTest::getFileLocationAbsolutePath) .collect(Collectors.joining(",")); final var smallPrBranch = "eclipse-jetty-same-issues-as-main-small"; - MavenBuild smallPrBuild = test_existing_project("org.eclipse.jetty:jetty-project", smallPrSourceCode) + MavenBuild smallPrBuild = test_existing_project(smallPrProject.projectKey(), smallPrSourceCode) // re-define binaries from initial maven build .setProperty("sonar.java.binaries", smallPrBinaries) .setProperty("sonar.exclusions", "jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java," + @@ -343,9 +340,10 @@ public void eclipse_jetty_incremental() throws Exception { public void java_time_example_incremental() throws Exception { // Main branch: 85% int literals → above 80% threshold → S8694 suppresses all issues final var mainBranch = "main"; - String mainSourceCode = "java-time-example"; + RulingProject mainProject = ProjectConfigLoader.requireProject("java-time-example"); + String mainSourceCode = mainProject.projectName(); - MavenBuild branchBuild = test_project("example:java-time-example", mainSourceCode) + MavenBuild branchBuild = test_project(mainProject.projectKey(), mainSourceCode) .setProperties( "sonar.branch.name", mainBranch, "sonar.scm.provider", "git", @@ -356,10 +354,11 @@ public void java_time_example_incremental() throws Exception { executeBuildWithCommonProperties(branchBuild, mainSourceCode); // PR: 50% int literals → below 80% threshold → S8694 raises issues - String prSourceCode = "java-time-example-less-threshold"; + RulingProject prProject = ProjectConfigLoader.requireProject("java-time-example-less-threshold"); + String prSourceCode = prProject.projectName(); final var prBranch = "java-time-example-pr"; - MavenBuild prBuild = test_existing_project("example:java-time-example", prSourceCode) + MavenBuild prBuild = test_existing_project(prProject.projectKey(), prSourceCode) .setProperties( "sonar.pullrequest.key", prBranch, "sonar.pullrequest.branch", prBranch, @@ -382,22 +381,18 @@ private static String getFileLocationAbsolutePath(FileLocation location) { @Test public void sonarqube_server() throws Exception { - // sonarqube-6.5/server/sonar-server (v.6.5) - String projectName = "sonar-server"; - MavenBuild build = test_project("org.sonarsource.sonarqube:sonar-server", "sonarqube-6.5/server", projectName) - .setProperty("sonar.java.fileByFile", "true"); - executeBuildWithCommonProperties(build, projectName); + RulingProject project = ProjectConfigLoader.requireProject("sonar-server"); + executeMavenBuild(project, Map.of("sonar.java.fileByFile", "true")); } @Test public void jboss_ejb3_tutorial() throws Exception { - // https://github.com/jbossejb3/jboss-ejb3-tutorial (18/01/2015) - String projectName = "jboss-ejb3-tutorial"; - prepareProject(projectName, projectName); - SonarScanner build = SonarScanner.create(FileLocation.of("../sources/jboss-ejb3-tutorial").getFile()) + RulingProject project = ProjectConfigLoader.requireProject("jboss-ejb3-tutorial"); + prepareProject(project.projectKey(), project.projectName()); + SonarScanner build = SonarScanner.create(FileLocation.of("../sources/" + project.path()).getFile()) .setProperty("sonar.java.fileByFile", "true") - .setProjectKey(projectName) - .setProjectName(projectName) + .setProjectKey(project.projectKey()) + .setProjectName(project.projectName()) .setProjectVersion("0.1.0-SNAPSHOT") .setSourceEncoding("UTF-8") .setSourceDirs(".") @@ -405,15 +400,13 @@ public void jboss_ejb3_tutorial() throws Exception { // Dummy sonar.java.binaries to pass validation .setProperty("sonar.java.binaries", "asynch") .setProperty("sonar.java.source", "1.5"); - executeDebugBuildWithCommonProperties(build, projectName); + executeDebugBuildWithCommonProperties(build, project.projectName()); } @Test public void regex_examples() throws IOException { - String projectName = "regex-examples"; - MavenBuild build = test_project("org.regex-examples:regex-examples", projectName) - .setProperty("sonar.java.fileByFile", "true"); - executeBuildWithCommonProperties(build, projectName); + RulingProject project = ProjectConfigLoader.requireProject("regex-examples"); + executeMavenBuild(project, Map.of("sonar.java.fileByFile", "true")); } /** @@ -421,12 +414,18 @@ public void regex_examples() throws IOException { */ @Test public void vibebot() throws IOException { - String projectName = "vibebot"; - File pomFile = FileLocation.of("../vibebot/pom.xml").getFile().getCanonicalFile(); - prepareProject("org.vibebot:vibebot", projectName); + RulingProject project = ProjectConfigLoader.requireProject("vibebot"); + File pomFile = FileLocation.of("../" + project.path() + "/pom.xml").getFile().getCanonicalFile(); + prepareProject(project.projectKey(), project.projectName()); MavenBuild build = MavenBuild.create().setPom(pomFile).setCleanPackageSonarGoals().addArgument("-DskipTests"); - build.setProperty("sonar.projectKey", "org.vibebot:vibebot"); - executeBuildWithCommonProperties(build, projectName); + build.setProperty("sonar.projectKey", project.projectKey()); + executeBuildWithCommonProperties(build, project.projectName()); + } + + private static void executeMavenBuild(RulingProject project, Map extraProperties) throws IOException { + MavenBuild build = test_project(project.projectKey(), project.path(), project.projectName()); + extraProperties.forEach(build::setProperty); + executeBuildWithCommonProperties(build, project.projectName()); } private static MavenBuild test_project(String projectKey, String projectName) throws IOException { @@ -434,7 +433,7 @@ private static MavenBuild test_project(String projectKey, String projectName) th } private static MavenBuild test_project(String projectKey, @Nullable String path, String projectName) throws IOException { - String pomLocation = "../sources/" + (path != null ? path + "/" : "") + projectName + "/pom.xml"; + String pomLocation = "../sources/" + (path != null ? path : projectName) + "/pom.xml"; File pomFile = FileLocation.of(pomLocation).getFile().getCanonicalFile(); prepareProject(projectKey, projectName); MavenBuild mavenBuild = MavenBuild.create().setPom(pomFile).setCleanPackageSonarGoals().addArgument("-DskipTests"); diff --git a/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoader.java b/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoader.java new file mode 100644 index 00000000000..9a291aec600 --- /dev/null +++ b/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoader.java @@ -0,0 +1,92 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.it; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +import org.assertj.core.api.Assertions; + +/** + * Loads ruling project configurations from the ruling-projects.json resource file. + * This allows external tools to discover available projects for ruling tests. + */ +public final class ProjectConfigLoader { + + private static final String CONFIG_RESOURCE = "ruling-projects.json"; + private static final Gson GSON = new Gson(); + + private ProjectConfigLoader() { + // Utility class + } + + /** + * Loads all ruling project configurations from the classpath resource. + * + * @return unmodifiable list of project configurations, never null + * @throws IllegalStateException if the resource cannot be found or parsed + */ + public static List loadProjects() { + try (InputStream is = ProjectConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_RESOURCE)) { + if (is == null) { + throw new IllegalStateException("Resource '" + CONFIG_RESOURCE + "' not found on classpath"); + } + List projects = GSON.fromJson( + new InputStreamReader(is, StandardCharsets.UTF_8), + new TypeToken>() {}.getType() + ); + return projects != null ? Collections.unmodifiableList(projects) : Collections.emptyList(); + } catch (IOException | JsonParseException e) { + throw new IllegalStateException("Failed to load ruling project configurations", e); + } + } + + /** + * Finds a project configuration by its project name. + * + * @param projectName the project name to find + * @return the project configuration, or null if not found + */ + public static RulingProject findByProjectName(String projectName) { + return loadProjects().stream() + .filter(p -> projectName.equals(p.projectName())) + .findFirst() + .orElse(null); + } + + /** + * Finds a project configuration by name and asserts it exists. + * + * @param projectName the project name to find + * @return the project configuration, never null + * @throws AssertionError if the project is not found + */ + public static RulingProject requireProject(String projectName) { + RulingProject project = findByProjectName(projectName); + Assertions.assertThat(project) + .as("Project '%s' should be defined in ruling-projects.json", projectName) + .isNotNull(); + return project; + } +} diff --git a/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoaderTest.java b/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoaderTest.java new file mode 100644 index 00000000000..9d7d3361198 --- /dev/null +++ b/its/ruling/src/test/java/org/sonar/java/it/ProjectConfigLoaderTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.it; + +import java.util.List; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectConfigLoaderTest { + + @Test + public void loadProjectsReturnsAllProjects() { + List projects = ProjectConfigLoader.loadProjects(); + assertThat(projects).hasSize(12); + } + + @Test + public void findByProjectNameReturnsCorrectProject() { + RulingProject project = ProjectConfigLoader.findByProjectName("guava"); + assertThat(project).isNotNull(); + assertThat(project.projectKey()).isEqualTo("com.google.guava:guava"); + assertThat(project.path()).isEqualTo("guava"); + assertThat(project.buildType()).isEqualTo(RulingProject.BuildType.MAVEN); + } + + @Test + public void findByProjectNameReturnsNullForUnknownProject() { + RulingProject project = ProjectConfigLoader.findByProjectName("non-existent"); + assertThat(project).isNull(); + } + + @Test + public void mavenExistingProjectsAreMarkedCorrectly() { + RulingProject project = ProjectConfigLoader.findByProjectName("eclipse-jetty-similar-to-main"); + assertThat(project).isNotNull(); + assertThat(project.isExistingProject()).isTrue(); + assertThat(project.isMavenBuild()).isTrue(); + } + + @Test + public void sonarScannerProjectIsMarkedCorrectly() { + RulingProject project = ProjectConfigLoader.findByProjectName("jboss-ejb3-tutorial"); + assertThat(project).isNotNull(); + assertThat(project.buildType()).isEqualTo(RulingProject.BuildType.SONAR_SCANNER); + assertThat(project.isMavenBuild()).isFalse(); + } +} diff --git a/its/ruling/src/test/java/org/sonar/java/it/RulingProject.java b/its/ruling/src/test/java/org/sonar/java/it/RulingProject.java new file mode 100644 index 00000000000..9b0f1ead5e6 --- /dev/null +++ b/its/ruling/src/test/java/org/sonar/java/it/RulingProject.java @@ -0,0 +1,47 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.it; + +import com.google.gson.annotations.SerializedName; + +/** + * Represents a ruling test project configuration loaded from ruling-projects.json. + */ +public record RulingProject( + String projectName, + String projectKey, + String path, + BuildType buildType +) { + + public enum BuildType { + @SerializedName("maven") + MAVEN, + @SerializedName("maven-existing") + MAVEN_EXISTING, + @SerializedName("sonar-scanner") + SONAR_SCANNER + } + + public boolean isMavenBuild() { + return buildType == BuildType.MAVEN || buildType == BuildType.MAVEN_EXISTING; + } + + public boolean isExistingProject() { + return buildType == BuildType.MAVEN_EXISTING; + } +} diff --git a/its/ruling/src/test/resources/ruling-projects.json b/its/ruling/src/test/resources/ruling-projects.json new file mode 100644 index 00000000000..7892ced2cba --- /dev/null +++ b/its/ruling/src/test/resources/ruling-projects.json @@ -0,0 +1,74 @@ +[ + { + "projectName": "mall", + "projectKey": "com.macro.mall:mall", + "path": "mall", + "buildType": "maven" + }, + { + "projectName": "guava", + "projectKey": "com.google.guava:guava", + "path": "guava", + "buildType": "maven" + }, + { + "projectName": "commons-beanutils", + "projectKey": "commons-beanutils:commons-beanutils", + "path": "commons-beanutils", + "buildType": "maven" + }, + { + "projectName": "eclipse-jetty", + "projectKey": "org.eclipse.jetty:jetty-project", + "path": "eclipse-jetty", + "buildType": "maven" + }, + { + "projectName": "eclipse-jetty-similar-to-main", + "projectKey": "org.eclipse.jetty:jetty-project", + "path": "eclipse-jetty-similar-to-main", + "buildType": "maven-existing" + }, + { + "projectName": "eclipse-jetty-similar-to-main-small", + "projectKey": "org.eclipse.jetty:jetty-project", + "path": "eclipse-jetty-similar-to-main-small", + "buildType": "maven-existing" + }, + { + "projectName": "java-time-example", + "projectKey": "example:java-time-example", + "path": "java-time-example", + "buildType": "maven" + }, + { + "projectName": "java-time-example-less-threshold", + "projectKey": "example:java-time-example", + "path": "java-time-example-less-threshold", + "buildType": "maven-existing" + }, + { + "projectName": "sonar-server", + "projectKey": "org.sonarsource.sonarqube:sonar-server", + "path": "sonarqube-6.5/server/sonar-server", + "buildType": "maven" + }, + { + "projectName": "jboss-ejb3-tutorial", + "projectKey": "jboss-ejb3-tutorial", + "path": "jboss-ejb3-tutorial", + "buildType": "sonar-scanner" + }, + { + "projectName": "regex-examples", + "projectKey": "org.regex-examples:regex-examples", + "path": "regex-examples", + "buildType": "maven" + }, + { + "projectName": "vibebot", + "projectKey": "org.vibebot:vibebot", + "path": "vibebot", + "buildType": "maven" + } +]