Skip to content

Commit 9eb7447

Browse files
JACOCO-89 Allow multiple report file paths into aggregateXmlReportPaths parameter
1 parent 462ea23 commit 9eb7447

File tree

12 files changed

+161
-100
lines changed

12 files changed

+161
-100
lines changed

its/src/test/java/org/sonar/plugins/jacoco/its/JacocoTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ void aggregate_and_module_based_reports_complement_each_over_to_build_total_cove
209209
.setPom(rootPom.toFile())
210210
.addGoal("clean verify")
211211
.addSonarGoal()
212-
.setProperty("sonar.coverage.jacoco.aggregateXmlReportPath", reportLocation.toAbsolutePath().toString());
212+
.setProperty("sonar.coverage.jacoco.aggregateXmlReportPaths", reportLocation.toAbsolutePath().toString());
213213

214214
orchestrator.executeBuild(build, true);
215215

src/main/java/org/sonar/plugins/jacoco/FileLocator.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ protected FileLocator(Iterable<InputFile> inputFiles, KotlinFileLocator kotlinFi
4141
protected FileLocator(List<InputFile> inputFiles, @Nullable KotlinFileLocator kotlinFileLocator) {
4242
this.kotlinFileLocator = kotlinFileLocator;
4343
for (InputFile inputFile : inputFiles) {
44-
String[] path = inputFile.relativePath().split(SEPARATOR_REGEX);
44+
// InputFile.relativePath() always uses '/' as separator
45+
String[] path = inputFile.relativePath().split("/");
4546
tree.index(inputFile, path);
4647
}
4748
}
@@ -50,7 +51,7 @@ protected FileLocator(List<InputFile> inputFiles, @Nullable KotlinFileLocator ko
5051
public InputFile getInputFile(@Nullable String groupName, String packagePath, String fileName) {
5152
String filePath = packagePath.isEmpty()
5253
? fileName
53-
: (packagePath + '/' + fileName);
54+
: normalizePath(packagePath + "/" + fileName);
5455

5556
InputFile file = lookup(groupName, filePath);
5657

@@ -62,4 +63,9 @@ public InputFile getInputFile(@Nullable String groupName, String packagePath, St
6263

6364
@CheckForNull
6465
protected abstract InputFile lookup(@Nullable String groupName, String filePath);
66+
67+
private static String normalizePath(String path) {
68+
return path.replace("/", File.separator);
69+
}
70+
6571
}

src/main/java/org/sonar/plugins/jacoco/JacocoAggregateSensor.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.FileNotFoundException;
2323
import java.nio.file.Path;
2424
import java.nio.file.Paths;
25+
import java.util.Set;
2526
import java.util.stream.Stream;
2627
import java.util.stream.StreamSupport;
2728
import org.sonar.api.batch.fs.InputFile;
@@ -31,6 +32,8 @@
3132
import org.sonar.api.utils.log.Logger;
3233
import org.sonar.api.utils.log.Loggers;
3334

35+
import static org.sonar.plugins.jacoco.SensorUtils.importReports;
36+
3437
public class JacocoAggregateSensor implements ProjectSensor {
3538
private static final Logger LOG = Loggers.get(JacocoAggregateSensor.class);
3639
private final ProjectCoverageContext projectCoverageContext;
@@ -47,14 +50,8 @@ public void describe(SensorDescriptor descriptor) {
4750
@Override
4851
public void execute(SensorContext context) {
4952
this.projectCoverageContext.setProjectBaseDir(Paths.get(context.config().get("sonar.projectBaseDir").get()));
50-
Path reportPath = null;
51-
try {
52-
reportPath = new ReportPathsProvider(context).getAggregateReportPath();
53-
} catch (FileNotFoundException e) {
54-
LOG.error(String.format("The aggregate JaCoCo sensor will stop: %s", e.getMessage()));
55-
return;
56-
}
57-
if (reportPath == null) {
53+
Set<Path> reportPaths = new ReportPathsProvider(context).getAggregateReportPaths();
54+
if (reportPaths.isEmpty()) {
5855
LOG.debug("No aggregate XML report found. No coverage coverage information will be added at project level.");
5956
return;
6057
}
@@ -63,7 +60,6 @@ public void execute(SensorContext context) {
6360
FileLocator locator = new ProjectFileLocator(inputFiles, new KotlinFileLocator(kotlinInputFileStream), projectCoverageContext);
6461
ReportImporter importer = new ReportImporter(context);
6562

66-
LOG.info("Importing aggregate report {}.", reportPath);
67-
SensorUtils.importReport(new XmlReportParser(reportPath), locator, importer, LOG);
63+
importReports(reportPaths, locator, importer, LOG);
6864
}
6965
}

src/main/java/org/sonar/plugins/jacoco/JacocoPlugin.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ public void define(Context context) {
3838
.build());
3939

4040
context.addExtension(JacocoAggregateSensor.class);
41-
context.addExtension(PropertyDefinition.builder(ReportPathsProvider.AGGREGATE_REPORT_PATH_PROPERTY_KEY)
41+
context.addExtension(PropertyDefinition.builder(ReportPathsProvider.AGGREGATE_REPORT_PATHS_PROPERTY_KEY)
4242
.onQualifiers(Qualifiers.PROJECT)
4343
.type(PropertyType.STRING)
4444
.multiValues(false)
4545
.category("JaCoCo")
46-
.description("Single path to aggregate XML coverage report file.")
46+
.description("Paths to JaCoCo XML aggregate coverage report files. Each path can be either absolute or relative" +
47+
" to the project base directory. Wildcard patterns are accepted (*, ** and ?).")
4748
.build());
4849
}
4950
}

src/main/java/org/sonar/plugins/jacoco/JacocoSensor.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.sonar.api.utils.log.Logger;
3131
import org.sonar.api.utils.log.Loggers;
3232

33+
import static org.sonar.plugins.jacoco.SensorUtils.importReports;
34+
3335
public class JacocoSensor implements Sensor {
3436
private static final Logger LOG = Loggers.get(JacocoSensor.class);
3537

@@ -57,20 +59,7 @@ public void execute(SensorContext context) {
5759
ModuleFileLocator locator = new ModuleFileLocator(inputFiles, new KotlinFileLocator(kotlinInputFileStream));
5860
ReportImporter importer = new ReportImporter(context);
5961

60-
importReports(reportPaths, locator, importer);
61-
}
62-
63-
void importReports(Collection<Path> reportPaths, ModuleFileLocator locator, ReportImporter importer) {
64-
LOG.info("Importing {} report(s). Turn your logs in debug mode in order to see the exhaustive list.", reportPaths.size());
65-
66-
for (Path reportPath : reportPaths) {
67-
LOG.debug("Reading report '{}'", reportPath);
68-
try {
69-
SensorUtils.importReport(new XmlReportParser(reportPath), locator, importer, LOG);
70-
} catch (Exception e) {
71-
LOG.error("Coverage report '{}' could not be read/imported. Error: {}", reportPath, e);
72-
}
73-
}
62+
importReports(reportPaths, locator, importer, LOG);
7463
}
7564

7665
private void recordModuleCoverageContext(SensorContext sensorContext) {

src/main/java/org/sonar/plugins/jacoco/ReportPathsProvider.java

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,22 @@
2626
import java.util.Collection;
2727
import java.util.HashSet;
2828
import java.util.List;
29-
import java.util.Optional;
3029
import java.util.Set;
3130
import java.util.stream.Collectors;
3231
import java.util.stream.Stream;
33-
import javax.annotation.CheckForNull;
3432
import org.sonar.api.batch.sensor.SensorContext;
3533
import org.sonar.api.utils.log.Logger;
3634
import org.sonar.api.utils.log.Loggers;
3735

3836
class ReportPathsProvider {
3937
private static final Logger LOG = Loggers.get(ReportPathsProvider.class);
4038

41-
private static final String[] DEFAULT_PATHS = {"target/site/jacoco/jacoco.xml", "target/site/jacoco-it/jacoco.xml", "build/reports/jacoco/test/jacocoTestReport.xml"};
39+
private static final String[] DEFAULT_PATHS = {
40+
"target/site/jacoco/jacoco.xml",
41+
"target/site/jacoco-it/jacoco.xml",
42+
"build/reports/jacoco/test/jacocoTestReport.xml"};
4243

43-
static final String AGGREGATE_REPORT_PATH_PROPERTY_KEY = "sonar.coverage.jacoco.aggregateXmlReportPath";
44+
static final String AGGREGATE_REPORT_PATHS_PROPERTY_KEY = "sonar.coverage.jacoco.aggregateXmlReportPaths";
4445
static final String REPORT_PATHS_PROPERTY_KEY = "sonar.coverage.jacoco.xmlReportPaths";
4546

4647
private final SensorContext context;
@@ -84,22 +85,23 @@ Collection<Path> getPaths() {
8485
}
8586

8687
/**
87-
* Checks if the aggregate report path property is set, finds the first path matching and returns it.
88+
* Checks if the aggregate report path property is set, finds the paths matching and returns them.
8889
*
89-
* @return Path to the existing aggregate report if the property is set. Null if none specified.
90-
* @throws FileNotFoundException If a path is set but does not match with an existing file.
90+
* @return A Set of Path to the existing aggregate report files if the property is set. An empty set if none specified.
9191
*/
92-
@CheckForNull
93-
Path getAggregateReportPath() throws FileNotFoundException {
94-
Optional<String> property = context.config().get(AGGREGATE_REPORT_PATH_PROPERTY_KEY);
95-
if (!property.isPresent()) {
96-
return null;
97-
}
98-
List<Path> scanned = WildcardPatternFileScanner.scan(context.fileSystem().baseDir().toPath(), property.get());
99-
if (scanned.isEmpty()) {
100-
throw new FileNotFoundException(String.format("Aggregate report %s was not found", property.get()));
92+
Set<Path> getAggregateReportPaths() {
93+
Set<Path> reportPaths = new HashSet<>();
94+
String[] reportPathsParam = context.config().getStringArray(AGGREGATE_REPORT_PATHS_PROPERTY_KEY);
95+
for (String reportPathPattern : reportPathsParam) {
96+
List<Path> scanned = WildcardPatternFileScanner.scan(context.fileSystem().baseDir().toPath(), reportPathPattern);
97+
if (scanned.isEmpty()) {
98+
LOG.warn(String.format("No coverage report found for pattern: '%s'", reportPathPattern));
99+
} else {
100+
reportPaths.addAll(scanned);
101+
}
101102
}
102-
return scanned.get(0);
103+
104+
return reportPaths;
103105
}
104106

105107

src/main/java/org/sonar/plugins/jacoco/SensorUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
package org.sonar.plugins.jacoco;
2121

22+
import java.nio.file.Path;
23+
import java.util.Collection;
2224
import java.util.List;
2325
import org.sonar.api.batch.fs.InputFile;
2426
import org.sonar.api.utils.log.Logger;
@@ -28,6 +30,19 @@ private SensorUtils() {
2830
/* This class should not be instantiated */
2931
}
3032

33+
static void importReports(Collection<Path> reportPaths, FileLocator locator, ReportImporter importer, Logger logger) {
34+
logger.info("Importing {} report(s). Turn your logs in debug mode in order to see the exhaustive list.", reportPaths.size());
35+
36+
for (Path reportPath : reportPaths) {
37+
logger.debug("Reading report '{}'", reportPath);
38+
try {
39+
SensorUtils.importReport(new XmlReportParser(reportPath), locator, importer, logger);
40+
} catch (Exception e) {
41+
logger.error("Coverage report '{}' could not be read/imported. Error: {}", reportPath, e);
42+
}
43+
}
44+
}
45+
3146
static void importReport(XmlReportParser reportParser, FileLocator locator, ReportImporter importer, Logger logger) {
3247
List<XmlReportParser.SourceFile> sourceFiles = reportParser.parse();
3348

src/test/java/org/sonar/plugins/jacoco/JacocoAggregateSensorTest.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,15 @@ void log_missing_report_and_return_early_when_missing_analysis_parameter() {
7070
@Test
7171
void log_missing_report_and_return_early_when_analysis_parameter_points_to_report_that_does_not_exist() {
7272
context.settings()
73-
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATH_PROPERTY_KEY, "non-existing-report.xml");
73+
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATHS_PROPERTY_KEY, "non-existing-report.xml");
7474

7575
var sensor = new JacocoAggregateSensor(new ProjectCoverageContext());
7676
sensor.execute(context);
7777

78-
assertThat(logTester.logs(LoggerLevel.ERROR)).
79-
containsExactly("The aggregate JaCoCo sensor will stop: Aggregate report non-existing-report.xml was not found");
80-
assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
78+
assertThat(logTester.logs(LoggerLevel.DEBUG)).
79+
containsExactly("No aggregate XML report found. No coverage coverage information will be added at project level.");
80+
assertThat(logTester.logs(LoggerLevel.WARN))
81+
.contains("No coverage report found for pattern: 'non-existing-report.xml'");
8182
assertThat(logTester.logs(LoggerLevel.INFO)).isEmpty();
8283
}
8384

@@ -86,13 +87,13 @@ void executes_as_expected() {
8687
Path aggregateReport = Path.of("src", "test", "resources", "jacoco-aggregate.xml");
8788

8889
context.settings()
89-
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATH_PROPERTY_KEY, aggregateReport.toAbsolutePath().toString());
90+
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATHS_PROPERTY_KEY, aggregateReport.toAbsolutePath().toString());
9091

9192
var sensor = new JacocoAggregateSensor(new ProjectCoverageContext());
9293
sensor.execute(context);
9394
assertThat(logTester.logs(LoggerLevel.DEBUG)).doesNotContain(NO_REPORT_TO_IMPORT_LOG_MESSAGE);
9495
assertThat(logTester.logs(LoggerLevel.INFO)).containsOnly(
95-
String.format("Importing aggregate report %s.", aggregateReport.toAbsolutePath())
96+
"Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list."
9697
);
9798
}
9899

@@ -101,15 +102,16 @@ void should_not_fail_when_processing_a_single_module_report() {
101102
Path singModuleReport = Path.of("src", "test", "resources", "jacoco.xml");
102103

103104
context.settings()
104-
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATH_PROPERTY_KEY, singModuleReport.toAbsolutePath().toString());
105+
.setProperty(ReportPathsProvider.AGGREGATE_REPORT_PATHS_PROPERTY_KEY, singModuleReport.toAbsolutePath().toString());
105106

106107
var sensor = new JacocoAggregateSensor(new ProjectCoverageContext());
107108
sensor.execute(context);
108109
assertThat(logTester.logs(LoggerLevel.DEBUG)).doesNotContain(NO_REPORT_TO_IMPORT_LOG_MESSAGE);
110+
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Reading report '" + singModuleReport.toAbsolutePath() + "'");
109111
assertThat(logTester.logs(LoggerLevel.INFO)).containsOnly(
110-
String.format("Importing aggregate report %s.", singModuleReport.toAbsolutePath())
112+
"Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list."
111113
);
112114
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
113115
}
114116

115-
}
117+
}

src/test/java/org/sonar/plugins/jacoco/JacocoPluginTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,18 @@ void should_add_sensors_and_property_definitions() {
5151
assertThat(multiValueReportPaths.key()).isEqualTo("sonar.coverage.jacoco.xmlReportPaths");
5252
assertThat(multiValueReportPaths.multiValues()).isTrue();
5353
assertThat(multiValueReportPaths.category()).isEqualTo("JaCoCo");
54+
assertThat(multiValueReportPaths.description()).isEqualTo("Paths to JaCoCo XML coverage report files. Each path can be either absolute or relative" +
55+
" to the project base directory. Wildcard patterns are accepted (*, ** and ?).");
5456
assertThat(multiValueReportPaths.qualifiers()).containsOnly(Qualifiers.PROJECT);
5557

5658
assertThat(arg.getAllValues().get(3)).isEqualTo(JacocoAggregateSensor.class);
5759
PropertyDefinition aggregateReportPath = (PropertyDefinition) arg.getAllValues().get(4);
58-
assertThat(aggregateReportPath.key()).isEqualTo("sonar.coverage.jacoco.aggregateXmlReportPath");
60+
assertThat(aggregateReportPath.key()).isEqualTo("sonar.coverage.jacoco.aggregateXmlReportPaths");
5961
assertThat(aggregateReportPath.type()).isEqualTo(PropertyType.STRING);
6062
assertThat(aggregateReportPath.multiValues()).isFalse();
6163
assertThat(aggregateReportPath.category()).isEqualTo("JaCoCo");
64+
assertThat(aggregateReportPath.description()).isEqualTo("Paths to JaCoCo XML aggregate coverage report files. Each path can be either absolute or relative" +
65+
" to the project base directory. Wildcard patterns are accepted (*, ** and ?).");
6266
assertThat(aggregateReportPath.qualifiers()).containsOnly(Qualifiers.PROJECT);
6367
}
6468
}

src/test/java/org/sonar/plugins/jacoco/JacocoSensorTest.java

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -93,41 +93,18 @@ void do_not_index_files_when_no_report_was_found() throws IOException {
9393

9494
@Test
9595
void do_nothing_if_report_parse_failure() {
96-
ModuleFileLocator locator = mock(ModuleFileLocator.class);
97-
ReportImporter importer = mock(ReportImporter.class);
98-
99-
sensor.importReports(Collections.singletonList(Paths.get("invalid.xml")), locator, importer);
96+
SensorContextTester tester = SensorContextTester.create(temp.getRoot());
97+
MapSettings settings = new MapSettings()
98+
.setProperty("sonar.moduleKey", "module")
99+
.setProperty("sonar.projectBaseDir", temp.getRoot().getAbsolutePath());
100+
settings.setProperty(ReportPathsProvider.REPORT_PATHS_PROPERTY_KEY, "invalid.xml");
101+
tester.setSettings(settings);
102+
sensor.execute(tester);
100103

101-
assertThat(logTester.logs(LoggerLevel.INFO)).contains("Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list.");
104+
assertThat(logTester.logs(LoggerLevel.INFO)).contains("No report imported, no coverage information will be imported by JaCoCo XML Report Importer");
102105

103-
assertThat(logTester.logs(LoggerLevel.ERROR)).hasSize(1);
106+
assertThat(logTester.logs(LoggerLevel.WARN)).hasSize(1);
104107
assertThat(logTester.logs(LoggerLevel.ERROR)).allMatch(s -> s.startsWith("Coverage report 'invalid.xml' could not be read/imported"));
105-
106-
verifyNoInteractions(locator, importer);
107-
}
108-
109-
@Test
110-
void parse_failure_do_not_fail_analysis() {
111-
ModuleFileLocator locator = mock(ModuleFileLocator.class);
112-
ReportImporter importer = mock(ReportImporter.class);
113-
InputFile inputFile = mock(InputFile.class);
114-
Path baseDir = Paths.get("src", "test", "resources");
115-
Path invalidFile = baseDir.resolve("invalid_ci_in_line.xml");
116-
Path validFile = baseDir.resolve("jacoco.xml");
117-
118-
when(locator.getInputFile(null, "org/sonarlint/cli", "Stats.java")).thenReturn(inputFile);
119-
120-
sensor.importReports(Arrays.asList(invalidFile, validFile), locator, importer);
121-
122-
String expectedErrorMessage = String.format(
123-
"Coverage report '%s' could not be read/imported. Error: java.lang.IllegalStateException: Invalid report: failed to parse integer from the attribute 'ci' for the sourcefile 'File.java' at line 6 column 61",
124-
invalidFile.toString());
125-
126-
assertThat(logTester.logs(LoggerLevel.INFO)).contains("Importing 2 report(s). Turn your logs in debug mode in order to see the exhaustive list.");
127-
128-
assertThat(logTester.logs(LoggerLevel.ERROR)).contains(expectedErrorMessage);
129-
130-
verify(importer, times(1)).importCoverage(any(), eq(inputFile));
131108
}
132109

133110
@Test

0 commit comments

Comments
 (0)