Skip to content

Commit 52d513b

Browse files
Externalize ruling test project definitions to JSON
- Add ruling-projects.json with project name-to-path mappings - Add RulingProject record for type-safe access - Add ProjectConfigLoader utility with requireProject() method - Update JavaRulingTest to use centralized project definitions - Add unit tests for ProjectConfigLoader
1 parent 77b8bb0 commit 52d513b

5 files changed

Lines changed: 332 additions & 59 deletions

File tree

its/ruling/src/test/java/org/sonar/java/it/JavaRulingTest.java

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.HashSet;
4242
import java.util.List;
4343
import java.util.Set;
44+
import java.util.Map;
4445
import java.util.stream.Collectors;
4546
import javax.annotation.Nullable;
4647
import org.apache.commons.lang3.StringUtils;
@@ -190,42 +191,35 @@ private static void copyFile(Path source, Path targetDir) {
190191
191192
@Test
192193
public void spring_mall() throws Exception {
193-
String projectName = "mall";
194-
MavenBuild build = test_project("com.macro.mall:mall", projectName);
195-
build
196-
.setProperty("docker.skip", "true")
197-
.setProperty("java.version", "21")
198-
.setProperty("maven-bundle-plugin.version", "5.1.4")
199-
.setProperty("maven.javadoc.skip", "true")
200-
.setProperty("sonar.java.experimental.batchModeSizeInKB", "420");
201-
executeBuildWithCommonProperties(build, projectName);
194+
RulingProject project = ProjectConfigLoader.requireProject("mall");
195+
executeMavenBuild(project, Map.of(
196+
"docker.skip", "true",
197+
"java.version", "21",
198+
"maven-bundle-plugin.version", "5.1.4",
199+
"maven.javadoc.skip", "true",
200+
"sonar.java.experimental.batchModeSizeInKB", "420"
201+
));
202202
}
203203
204204
@Test
205205
public void guava() throws Exception {
206-
String projectName = "guava";
207-
MavenBuild build = test_project("com.google.guava:guava", projectName);
208-
build
209-
// Keep compilation and analysis on Java 17 without overriding the Java runtime version seen by the scanner.
210-
.setProperty("sonar.java.source", "17")
211-
.setProperty("maven-bundle-plugin.version", "5.1.4")
212-
.setProperty("maven.javadoc.skip", "true")
213-
.setProperty("animal.sniffer.skip", "true")
214-
// use batch
215-
.setProperty("sonar.java.experimental.batchModeSizeInKB", "8192");
216-
executeBuildWithCommonProperties(build, projectName);
206+
RulingProject project = ProjectConfigLoader.requireProject("guava");
207+
executeMavenBuild(project, Map.of(
208+
"sonar.java.source", "17",
209+
"maven-bundle-plugin.version", "5.1.4",
210+
"maven.javadoc.skip", "true",
211+
"animal.sniffer.skip", "true",
212+
"sonar.java.experimental.batchModeSizeInKB", "8192"
213+
));
217214
}
218215
219216
@Test
220217
public void apache_commons_beanutils() throws Exception {
221-
String projectName = "commons-beanutils";
222-
MavenBuild build = test_project("commons-beanutils:commons-beanutils", projectName);
223-
build
224-
// by default it can not be built with jdk 17 without changing some plugin versions
225-
.setProperty("maven-bundle-plugin.version", "5.1.4")
226-
// use batch
227-
.setProperty("sonar.java.experimental.batchModeSizeInKB", "8192");
228-
executeBuildWithCommonProperties(build, projectName);
218+
RulingProject project = ProjectConfigLoader.requireProject("commons-beanutils");
219+
executeMavenBuild(project, Map.of(
220+
"maven-bundle-plugin.version", "5.1.4",
221+
"sonar.java.experimental.batchModeSizeInKB", "8192"
222+
));
229223
}
230224
231225
@Test
@@ -236,14 +230,15 @@ public void eclipse_jetty_incremental() throws Exception {
236230
237231
List<String> 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/");
238232
239-
String mainBranchSourceCode = "eclipse-jetty";
233+
RulingProject mainProject = ProjectConfigLoader.requireProject("eclipse-jetty");
234+
String mainBranchSourceCode = mainProject.projectName();
240235
String mainBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + mainBranchSourceCode + "/" + dir + "target/classes"))
241236
.map(JavaRulingTest::getFileLocationAbsolutePath)
242237
.collect(Collectors.joining(","));
243238
244239
final var mainBranch = "eclipse-jetty-main";
245240
246-
MavenBuild branchBuild = test_project("org.eclipse.jetty:jetty-project", mainBranchSourceCode)
241+
MavenBuild branchBuild = test_project(mainProject.projectKey(), mainBranchSourceCode)
247242
// re-define binaries from initial maven build
248243
.setProperty("sonar.java.binaries", mainBinaries)
249244
.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 {
265260
var time1 = after1 - before1;
266261
267262
// Huge PR
268-
String prSourceCode = "eclipse-jetty-similar-to-main";
263+
RulingProject prProject = ProjectConfigLoader.requireProject("eclipse-jetty-similar-to-main");
264+
String prSourceCode = prProject.projectName();
269265
String prBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + prSourceCode + "/" + dir + "target/classes"))
270266
.map(JavaRulingTest::getFileLocationAbsolutePath)
271267
.collect(Collectors.joining(","));
272268
273269
final var prBranch = "eclipse-jetty-same-issues-as-main";
274270
275-
MavenBuild prBuild = test_existing_project("org.eclipse.jetty:jetty-project", prSourceCode)
271+
MavenBuild prBuild = test_existing_project(prProject.projectKey(), prSourceCode)
276272
// re-define binaries from initial maven build
277273
.setProperty("sonar.java.binaries", prBinaries)
278274
.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 {
297293
var time2 = after2 - before2;
298294
299295
// Small PR
300-
String smallPrSourceCode = "eclipse-jetty-similar-to-main-small";
296+
RulingProject smallPrProject = ProjectConfigLoader.requireProject("eclipse-jetty-similar-to-main-small");
297+
String smallPrSourceCode = smallPrProject.projectName();
301298
String smallPrBinaries = dirs.stream().map(dir -> FileLocation.of("../sources/" + smallPrSourceCode + "/" + dir + "target/classes"))
302299
.map(JavaRulingTest::getFileLocationAbsolutePath)
303300
.collect(Collectors.joining(","));
304301
305302
final var smallPrBranch = "eclipse-jetty-same-issues-as-main-small";
306303
307-
MavenBuild smallPrBuild = test_existing_project("org.eclipse.jetty:jetty-project", smallPrSourceCode)
304+
MavenBuild smallPrBuild = test_existing_project(smallPrProject.projectKey(), smallPrSourceCode)
308305
// re-define binaries from initial maven build
309306
.setProperty("sonar.java.binaries", smallPrBinaries)
310307
.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 {
343340
public void java_time_example_incremental() throws Exception {
344341
// Main branch: 85% int literals → above 80% threshold → S8694 suppresses all issues
345342
final var mainBranch = "main";
346-
String mainSourceCode = "java-time-example";
343+
RulingProject mainProject = ProjectConfigLoader.requireProject("java-time-example");
344+
String mainSourceCode = mainProject.projectName();
347345
348-
MavenBuild branchBuild = test_project("example:java-time-example", mainSourceCode)
346+
MavenBuild branchBuild = test_project(mainProject.projectKey(), mainSourceCode)
349347
.setProperties(
350348
"sonar.branch.name", mainBranch,
351349
"sonar.scm.provider", "git",
@@ -356,10 +354,11 @@ public void java_time_example_incremental() throws Exception {
356354
executeBuildWithCommonProperties(branchBuild, mainSourceCode);
357355
358356
// PR: 50% int literals → below 80% threshold → S8694 raises issues
359-
String prSourceCode = "java-time-example-less-threshold";
357+
RulingProject prProject = ProjectConfigLoader.requireProject("java-time-example-less-threshold");
358+
String prSourceCode = prProject.projectName();
360359
final var prBranch = "java-time-example-pr";
361360
362-
MavenBuild prBuild = test_existing_project("example:java-time-example", prSourceCode)
361+
MavenBuild prBuild = test_existing_project(prProject.projectKey(), prSourceCode)
363362
.setProperties(
364363
"sonar.pullrequest.key", prBranch,
365364
"sonar.pullrequest.branch", prBranch,
@@ -382,51 +381,51 @@ private static String getFileLocationAbsolutePath(FileLocation location) {
382381
383382
@Test
384383
public void sonarqube_server() throws Exception {
385-
// sonarqube-6.5/server/sonar-server (v.6.5)
386-
String projectName = "sonar-server";
387-
MavenBuild build = test_project("org.sonarsource.sonarqube:sonar-server", "sonarqube-6.5/server", projectName)
388-
.setProperty("sonar.java.fileByFile", "true");
389-
executeBuildWithCommonProperties(build, projectName);
384+
RulingProject project = ProjectConfigLoader.requireProject("sonar-server");
385+
executeMavenBuild(project, Map.of("sonar.java.fileByFile", "true"));
390386
}
391387
392388
@Test
393389
public void jboss_ejb3_tutorial() throws Exception {
394-
// https://github.com/jbossejb3/jboss-ejb3-tutorial (18/01/2015)
395-
String projectName = "jboss-ejb3-tutorial";
396-
prepareProject(projectName, projectName);
397-
SonarScanner build = SonarScanner.create(FileLocation.of("../sources/jboss-ejb3-tutorial").getFile())
390+
RulingProject project = ProjectConfigLoader.requireProject("jboss-ejb3-tutorial");
391+
prepareProject(project.projectKey(), project.projectName());
392+
SonarScanner build = SonarScanner.create(FileLocation.of("../sources/" + project.path()).getFile())
398393
.setProperty("sonar.java.fileByFile", "true")
399-
.setProjectKey(projectName)
400-
.setProjectName(projectName)
394+
.setProjectKey(project.projectKey())
395+
.setProjectName(project.projectName())
401396
.setProjectVersion("0.1.0-SNAPSHOT")
402397
.setSourceEncoding("UTF-8")
403398
.setSourceDirs(".")
404399
.setDebugLogs(true)
405400
// Dummy sonar.java.binaries to pass validation
406401
.setProperty("sonar.java.binaries", "asynch")
407402
.setProperty("sonar.java.source", "1.5");
408-
executeDebugBuildWithCommonProperties(build, projectName);
403+
executeDebugBuildWithCommonProperties(build, project.projectName());
409404
}
410405
411406
@Test
412407
public void regex_examples() throws IOException {
413-
String projectName = "regex-examples";
414-
MavenBuild build = test_project("org.regex-examples:regex-examples", projectName)
415-
.setProperty("sonar.java.fileByFile", "true");
416-
executeBuildWithCommonProperties(build, projectName);
408+
RulingProject project = ProjectConfigLoader.requireProject("regex-examples");
409+
executeMavenBuild(project, Map.of("sonar.java.fileByFile", "true"));
417410
}
418411
419412
/**
420413
* Vibe-bot runs this test when testing samples; so it's excluded from the GitHub Actions workflow.
421414
*/
422415
@Test
423416
public void vibebot() throws IOException {
424-
String projectName = "vibebot";
425-
File pomFile = FileLocation.of("../vibebot/pom.xml").getFile().getCanonicalFile();
426-
prepareProject("org.vibebot:vibebot", projectName);
417+
RulingProject project = ProjectConfigLoader.requireProject("vibebot");
418+
File pomFile = FileLocation.of("../" + project.path() + "/pom.xml").getFile().getCanonicalFile();
419+
prepareProject(project.projectKey(), project.projectName());
427420
MavenBuild build = MavenBuild.create().setPom(pomFile).setCleanPackageSonarGoals().addArgument("-DskipTests");
428-
build.setProperty("sonar.projectKey", "org.vibebot:vibebot");
429-
executeBuildWithCommonProperties(build, projectName);
421+
build.setProperty("sonar.projectKey", project.projectKey());
422+
executeBuildWithCommonProperties(build, project.projectName());
423+
}
424+
425+
private static void executeMavenBuild(RulingProject project, Map<String, String> extraProperties) throws IOException {
426+
MavenBuild build = test_project(project.projectKey(), project.projectName());
427+
extraProperties.forEach(build::setProperty);
428+
executeBuildWithCommonProperties(build, project.projectName());
430429
}
431430

432431
private static MavenBuild test_project(String projectKey, String projectName) throws IOException {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* You can redistribute and/or modify this program under the terms of
7+
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.it;
18+
19+
import com.google.gson.Gson;
20+
import com.google.gson.reflect.TypeToken;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.InputStreamReader;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Collections;
26+
import java.util.List;
27+
import java.util.Objects;
28+
29+
import org.assertj.core.api.Assertions;
30+
31+
/**
32+
* Loads ruling project configurations from the ruling-projects.json resource file.
33+
* This allows external tools to discover available projects for ruling tests.
34+
*/
35+
public final class ProjectConfigLoader {
36+
37+
private static final String CONFIG_RESOURCE = "ruling-projects.json";
38+
private static final Gson GSON = new Gson();
39+
40+
private ProjectConfigLoader() {
41+
// Utility class
42+
}
43+
44+
/**
45+
* Loads all ruling project configurations from the classpath resource.
46+
*
47+
* @return unmodifiable list of project configurations, never null
48+
* @throws IllegalStateException if the resource cannot be found or parsed
49+
*/
50+
public static List<RulingProject> loadProjects() {
51+
try (InputStream is = ProjectConfigLoader.class.getClassLoader().getResourceAsStream(CONFIG_RESOURCE)) {
52+
Objects.requireNonNull(is, "Resource '" + CONFIG_RESOURCE + "' not found on classpath");
53+
List<RulingProject> projects = GSON.fromJson(
54+
new InputStreamReader(is, StandardCharsets.UTF_8),
55+
new TypeToken<List<RulingProject>>() {}.getType()
56+
);
57+
return projects != null ? Collections.unmodifiableList(projects) : Collections.emptyList();
58+
} catch (IOException e) {
59+
throw new IllegalStateException("Failed to load ruling project configurations", e);
60+
}
61+
}
62+
63+
/**
64+
* Finds a project configuration by its project name.
65+
*
66+
* @param projectName the project name to find
67+
* @return the project configuration, or null if not found
68+
*/
69+
public static RulingProject findByProjectName(String projectName) {
70+
return loadProjects().stream()
71+
.filter(p -> projectName.equals(p.projectName()))
72+
.findFirst()
73+
.orElse(null);
74+
}
75+
76+
/**
77+
* Finds a project configuration by name and asserts it exists.
78+
*
79+
* @param projectName the project name to find
80+
* @return the project configuration, never null
81+
* @throws AssertionError if the project is not found
82+
*/
83+
public static RulingProject requireProject(String projectName) {
84+
RulingProject project = findByProjectName(projectName);
85+
Assertions.assertThat(project)
86+
.as("Project '%s' should be defined in ruling-projects.json", projectName)
87+
.isNotNull();
88+
return project;
89+
}
90+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* You can redistribute and/or modify this program under the terms of
7+
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.it;
18+
19+
import java.util.List;
20+
import org.junit.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
public class ProjectConfigLoaderTest {
25+
26+
@Test
27+
public void loadProjectsReturnsAllProjects() {
28+
List<RulingProject> projects = ProjectConfigLoader.loadProjects();
29+
assertThat(projects).isNotEmpty();
30+
assertThat(projects).hasSize(12);
31+
}
32+
33+
@Test
34+
public void findByProjectNameReturnsCorrectProject() {
35+
RulingProject project = ProjectConfigLoader.findByProjectName("guava");
36+
assertThat(project).isNotNull();
37+
assertThat(project.projectKey()).isEqualTo("com.google.guava:guava");
38+
assertThat(project.path()).isEqualTo("guava");
39+
assertThat(project.buildType()).isEqualTo(RulingProject.BuildType.MAVEN);
40+
}
41+
42+
@Test
43+
public void findByProjectNameReturnsNullForUnknownProject() {
44+
RulingProject project = ProjectConfigLoader.findByProjectName("non-existent");
45+
assertThat(project).isNull();
46+
}
47+
48+
@Test
49+
public void mavenExistingProjectsAreMarkedCorrectly() {
50+
RulingProject project = ProjectConfigLoader.findByProjectName("eclipse-jetty-similar-to-main");
51+
assertThat(project).isNotNull();
52+
assertThat(project.isExistingProject()).isTrue();
53+
assertThat(project.isMavenBuild()).isTrue();
54+
}
55+
56+
@Test
57+
public void sonarScannerProjectIsMarkedCorrectly() {
58+
RulingProject project = ProjectConfigLoader.findByProjectName("jboss-ejb3-tutorial");
59+
assertThat(project).isNotNull();
60+
assertThat(project.buildType()).isEqualTo(RulingProject.BuildType.SONAR_SCANNER);
61+
assertThat(project.isMavenBuild()).isFalse();
62+
}
63+
}

0 commit comments

Comments
 (0)