Skip to content

Commit 0e3ec87

Browse files
authored
SONARPY-2120: Ensure coverage report sensor fails gracefully when reports can't be read (#2054)
1 parent bc06ab6 commit 0e3ec87

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed

sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/PythonCoverageSensor.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,19 @@
2929
import java.util.Set;
3030
import java.util.stream.Collectors;
3131
import javax.xml.stream.XMLStreamException;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
3234
import org.sonar.api.batch.fs.InputFile;
3335
import org.sonar.api.batch.sensor.Sensor;
3436
import org.sonar.api.batch.sensor.SensorContext;
3537
import org.sonar.api.batch.sensor.SensorDescriptor;
3638
import org.sonar.api.batch.sensor.coverage.NewCoverage;
3739
import org.sonar.api.config.Configuration;
38-
import org.slf4j.Logger;
39-
import org.slf4j.LoggerFactory;
4040
import org.sonar.plugins.python.EmptyReportException;
4141
import org.sonar.plugins.python.Python;
42+
import org.sonar.plugins.python.PythonReportSensor;
4243
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
4344

44-
import static org.sonar.plugins.python.PythonReportSensor.getReports;
45-
4645
public class PythonCoverageSensor implements Sensor {
4746

4847
private static final Logger LOG = LoggerFactory.getLogger(PythonCoverageSensor.class);
@@ -73,24 +72,38 @@ public void execute(SensorContext context) {
7372

7473
warnDeprecatedPropertyUsage(config);
7574

76-
HashSet<InputFile> filesCovered = new HashSet<>();
77-
List<File> reports = getCoverageReports(baseDir, config);
78-
if (!reports.isEmpty()) {
79-
LOG.info("Python test coverage");
80-
for (File report : uniqueAbsolutePaths(reports)) {
81-
Map<InputFile, NewCoverage> coverageMeasures = parseReport(report, context);
82-
saveMeasures(coverageMeasures, filesCovered);
75+
try {
76+
HashSet<InputFile> filesCovered = new HashSet<>();
77+
List<File> reports = getCoverageReports(baseDir, config);
78+
if (!reports.isEmpty()) {
79+
LOG.info("Python test coverage");
80+
for (File report : uniqueAbsolutePaths(reports)) {
81+
importReport(context, report, filesCovered);
82+
}
8383
}
84+
} catch (Exception e) {
85+
LOG.warn("Cannot read coverage report, the following exception occurred: '{}'", e.getMessage());
86+
analysisWarnings.addUnique("An error occurred while trying to import coverage report(s)");
87+
}
88+
}
89+
90+
private void importReport(SensorContext context, File report, HashSet<InputFile> filesCovered) {
91+
try {
92+
Map<InputFile, NewCoverage> coverageMeasures = parseReport(report, context);
93+
saveMeasures(coverageMeasures, filesCovered);
94+
} catch (Exception e) {
95+
LOG.warn("Cannot read coverage report '{}', the following exception occurred: '{}'", report, e.getMessage());
96+
analysisWarnings.addUnique(String.format("An error occurred while trying to import the coverage report: '%s'", report));
8497
}
8598
}
8699

87100
private List<File> getCoverageReports(String baseDir, Configuration config) {
88101
if (!config.hasKey(REPORT_PATHS_KEY)) {
89-
return getReports(config, baseDir, REPORT_PATHS_KEY, DEFAULT_REPORT_PATH, analysisWarnings);
102+
return PythonReportSensor.getReports(config, baseDir, REPORT_PATHS_KEY, DEFAULT_REPORT_PATH, analysisWarnings);
90103
}
91104

92105
return Arrays.stream(config.getStringArray(REPORT_PATHS_KEY))
93-
.flatMap(path -> getReports(config, baseDir, REPORT_PATHS_KEY, path, analysisWarnings).stream())
106+
.flatMap(path -> PythonReportSensor.getReports(config, baseDir, REPORT_PATHS_KEY, path, analysisWarnings).stream())
94107
.toList();
95108
}
96109

@@ -115,7 +128,8 @@ private Map<InputFile, NewCoverage> parseReport(File report, SensorContext conte
115128
parser.parseReport(report, context, coverageMeasures);
116129
if (!parser.errors().isEmpty()) {
117130
String parseErrors = String.format(String.join("%n", parser.errors()));
118-
analysisWarnings.addUnique(String.format("The following error(s) occurred while trying to import coverage report:%n%s", parseErrors));
131+
analysisWarnings.addUnique(String.format("The following error(s) occurred while trying to import coverage report:%n%s",
132+
parseErrors));
119133
}
120134
} catch (EmptyReportException e) {
121135
analysisWarnings.addUnique(String.format("The coverage report '%s' has been ignored because it seems to be empty.", report));

sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
import java.nio.file.Path;
2626
import java.util.Arrays;
2727
import java.util.List;
28-
import java.util.stream.Collectors;
2928
import java.util.stream.IntStream;
3029
import org.junit.jupiter.api.BeforeEach;
3130
import org.junit.jupiter.api.Test;
3231
import org.junit.jupiter.api.extension.RegisterExtension;
3332
import org.junit.jupiter.api.io.TempDir;
33+
import org.mockito.MockedStatic;
34+
import org.mockito.Mockito;
3435
import org.slf4j.event.Level;
3536
import org.sonar.api.batch.fs.InputFile;
3637
import org.sonar.api.batch.fs.InputFile.Type;
@@ -40,12 +41,14 @@
4041
import org.sonar.api.batch.sensor.internal.SensorContextTester;
4142
import org.sonar.api.config.internal.MapSettings;
4243
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
44+
import org.sonar.plugins.python.PythonReportSensor;
4345
import org.sonar.plugins.python.TestUtils;
4446
import org.sonar.plugins.python.warnings.AnalysisWarningsWrapper;
4547

4648
import static java.nio.charset.StandardCharsets.UTF_8;
4749
import static org.assertj.core.api.Assertions.assertThat;
48-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
50+
import static org.mockito.ArgumentMatchers.any;
51+
import static org.mockito.ArgumentMatchers.contains;
4952
import static org.mockito.ArgumentMatchers.eq;
5053
import static org.mockito.Mockito.spy;
5154
import static org.mockito.Mockito.times;
@@ -284,13 +287,17 @@ void test_comma_separated_paths_with_deprecated_property() {
284287
@Test
285288
void should_fail_on_invalid_report() {
286289
settings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "invalid-coverage-result.xml");
287-
assertThatThrownBy(() -> coverageSensor.execute(context)).isInstanceOf(IllegalStateException.class);
290+
coverageSensor.execute(context);
291+
verify(analysisWarnings).addUnique(contains("An error occurred while trying to import the coverage report: '"));
292+
verify(analysisWarnings).addUnique(contains("invalid-coverage-result.xml"));
288293
}
289294

290295
@Test
291296
void should_fail_on_unexpected_eof() {
292297
settings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "coverage_with_eof_error.xml");
293-
assertThatThrownBy(() -> coverageSensor.execute(context)).isInstanceOf(IllegalStateException.class);
298+
coverageSensor.execute(context);
299+
verify(analysisWarnings).addUnique(contains("An error occurred while trying to import the coverage report: '"));
300+
verify(analysisWarnings).addUnique(contains("coverage_with_eof_error.xml"));
294301
}
295302

296303
@Test
@@ -301,6 +308,18 @@ void should_do_nothing_on_empty_report() {
301308
assertThat(context.lineHits(FILE1_KEY, 1)).isNull();
302309
}
303310

311+
@Test
312+
void should_warn_on_invalid_basedir() {
313+
try(MockedStatic<PythonReportSensor> pythonReportSensorMock = Mockito.mockStatic(PythonReportSensor.class)) {
314+
pythonReportSensorMock
315+
.when(() -> PythonReportSensor.getReports(any(), any(), any(), any(), any()))
316+
.thenThrow(RuntimeException.class);
317+
coverageSensor.execute(context);
318+
319+
verify(analysisWarnings).addUnique(contains("An error occurred while trying to import coverage report(s)"));
320+
}
321+
}
322+
304323
@Test
305324
void should_warn_if_source_is_not_directory() {
306325
settings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "coverage_source_invalid_directory.xml");

0 commit comments

Comments
 (0)