Skip to content

Commit c2d1910

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-4050 Fix Cobertura source path resolving (#1061)
GitOrigin-RevId: c7c8ff06ab0e3f046cf936c4656b4185c1012161
1 parent 0c9e7f7 commit c2d1910

10 files changed

Lines changed: 155 additions & 16 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" ?>
2+
<!DOCTYPE coverage
3+
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
4+
<coverage branch-rate="0" line-rate="1" timestamp="1422365174523" version="3.7.1">
5+
<sources>
6+
<source>src</source>
7+
</sources>
8+
<packages>
9+
<package branch-rate="0" complexity="0" line-rate="1" name="">
10+
<classes>
11+
<class branch-rate="0" complexity="0" filename="prod.py" line-rate="1" name="src/prod">
12+
<methods/>
13+
<lines>
14+
<line hits="1" number="1"/>
15+
</lines>
16+
</class>
17+
</classes>
18+
</package>
19+
</packages>
20+
</coverage>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x = 1

its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/CoverageTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@
3333
public class CoverageTest {
3434

3535
private static final String COVERAGE_PROJECT = "projects/coverage_project";
36+
private static final String COVERAGE_PROJECT_PROJECT_BASEDIR = "projects/coverage_project_projectbasedir";
3637
@RegisterExtension
3738
public static final ConcurrentOrchestratorExtension ORCHESTRATOR = TestsUtils.dynamicOrchestrator;
3839

3940
private static final String PROJECT_KEY = "coverage_project";
41+
private static final String PROJECT_BASEDIR_KEY = "coverage_project_projectbasedir";
4042
private static final String LINES_TO_COVER = "lines_to_cover";
4143
private static final String COVERAGE = "coverage";
4244
private static final String LINE_COVERAGE = "line_coverage";
@@ -123,4 +125,28 @@ void empty_coverage_report() {
123125
assertThat(TestsUtils.getMeasureAsDouble(PROJECT_KEY, COVERAGE)).isZero();
124126
}
125127

128+
@Test
129+
void relative_source_paths_are_resolved_from_project_base_dir() {
130+
File projectDir = new File(COVERAGE_PROJECT_PROJECT_BASEDIR);
131+
File projectBaseDir = new File(projectDir, "app");
132+
SonarScanner build = ORCHESTRATOR.createSonarScanner()
133+
.setProjectDir(projectDir)
134+
.setProperty("sonar.projectKey", PROJECT_BASEDIR_KEY)
135+
.setProperty("sonar.projectName", PROJECT_BASEDIR_KEY)
136+
.setProperty("sonar.projectVersion", "1")
137+
.setProperty("sonar.sourceEncoding", "UTF8")
138+
.setProperty("sonar.projectBaseDir", projectBaseDir.getAbsolutePath())
139+
.setProperty("sonar.sources", "src")
140+
.setProperty(COVERAGE_REPORT_PATHS, "coverage.xml");
141+
142+
ORCHESTRATOR.executeBuild(build);
143+
144+
Map<String, Integer> expected = new HashMap<>();
145+
expected.put(LINES_TO_COVER, 1);
146+
expected.put(COVERAGE, 100);
147+
expected.put(LINE_COVERAGE, 100);
148+
expected.put(BRANCH_COVERAGE, null);
149+
TestsUtils.assertProjectMeasures(PROJECT_BASEDIR_KEY, expected);
150+
}
151+
126152
}

python-commons/src/main/java/org/sonar/plugins/python/coverage/CoberturaParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ private List<File> extractBaseDirectories(SMInputCursor sources, File defaultBas
8080
while (source.getNext() != null) {
8181
String path = FilenameUtils.normalize(source.collectDescendantText());
8282
if (!StringUtils.isBlank(path)) {
83-
File baseDirectory = new File(path);
83+
File sourcePath = new File(path);
84+
File baseDirectory = sourcePath.isAbsolute() ? sourcePath : new File(defaultBaseDirectory, path);
8485
if (baseDirectory.isDirectory()) {
8586
baseDirectories.add(baseDirectory);
8687
} else {

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

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,105 @@ void should_warn_on_invalid_basedir() {
316316
}
317317
}
318318

319+
@Test
320+
void should_resolve_relative_source_against_project_base_dir() throws Exception {
321+
Path projectBaseDir = tmpDir.resolve(".tests/resources/python-app");
322+
Path sourceDir = Files.createDirectories(projectBaseDir.resolve("my_app"));
323+
Path sourceFile = sourceDir.resolve("__init__.py");
324+
Files.writeString(sourceFile, "print('hello')\n", UTF_8);
325+
326+
Path report = projectBaseDir.resolve("coverage.xml");
327+
Files.writeString(report, """
328+
<?xml version="1.0" ?>
329+
<coverage branch-rate="0.0" line-rate="1.0" timestamp="1" version="4.4.2">
330+
<sources>
331+
<source>my_app</source>
332+
</sources>
333+
<packages>
334+
<package branch-rate="0.0" complexity="0.0" line-rate="1.0" name="my_app">
335+
<classes>
336+
<class branch-rate="0.0" complexity="0.0" filename="__init__.py" line-rate="1.0" name="__init__.py">
337+
<lines>
338+
<line branch="false" hits="1" number="1"/>
339+
</lines>
340+
</class>
341+
</classes>
342+
</package>
343+
</packages>
344+
</coverage>
345+
""", UTF_8);
346+
347+
SensorContextTester sensorContext = SensorContextTester.create(projectBaseDir.toFile());
348+
MapSettings sensorSettings = new MapSettings();
349+
sensorSettings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "coverage.xml");
350+
sensorContext.setSettings(sensorSettings);
351+
352+
DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", "my_app/__init__.py")
353+
.setModuleBaseDir(projectBaseDir)
354+
.setLanguage("py")
355+
.setType(Type.MAIN)
356+
.initMetadata(TestUtils.fileContent(sourceFile.toFile(), StandardCharsets.UTF_8))
357+
.build();
358+
sensorContext.fileSystem().add(inputFile);
359+
360+
coverageSensor.execute(sensorContext);
361+
362+
assertThat(sensorContext.lineHits("moduleKey:my_app/__init__.py", 1)).isOne();
363+
}
364+
365+
@Test
366+
void should_resolve_absolute_source_path_independently_of_project_base_dir() throws Exception {
367+
Path projectBaseDir = tmpDir.resolve(".tests/resources/python-app-abs");
368+
Path sourceDir = Files.createDirectories(projectBaseDir.resolve("my_app"));
369+
Path sourceFile = sourceDir.resolve("__init__.py");
370+
Files.writeString(sourceFile, "print('hello')\n", UTF_8);
371+
372+
String absoluteSourceDir = sourceDir.toAbsolutePath().toString();
373+
Path report = projectBaseDir.resolve("coverage.xml");
374+
Files.writeString(report, """
375+
<?xml version="1.0" ?>
376+
<coverage branch-rate="0.0" line-rate="1.0" timestamp="1" version="4.4.2">
377+
<sources>
378+
<source>%s</source>
379+
</sources>
380+
<packages>
381+
<package branch-rate="0.0" complexity="0.0" line-rate="1.0" name="my_app">
382+
<classes>
383+
<class branch-rate="0.0" complexity="0.0" filename="__init__.py" line-rate="1.0" name="__init__.py">
384+
<lines>
385+
<line branch="false" hits="1" number="1"/>
386+
</lines>
387+
</class>
388+
</classes>
389+
</package>
390+
</packages>
391+
</coverage>
392+
""".formatted(absoluteSourceDir), UTF_8);
393+
394+
SensorContextTester sensorContext = SensorContextTester.create(projectBaseDir.toFile());
395+
MapSettings sensorSettings = new MapSettings();
396+
sensorSettings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "coverage.xml");
397+
sensorContext.setSettings(sensorSettings);
398+
399+
DefaultInputFile inputFile = TestInputFileBuilder.create("moduleKey", "my_app/__init__.py")
400+
.setModuleBaseDir(projectBaseDir)
401+
.setLanguage("py")
402+
.setType(Type.MAIN)
403+
.initMetadata(TestUtils.fileContent(sourceFile.toFile(), StandardCharsets.UTF_8))
404+
.build();
405+
sensorContext.fileSystem().add(inputFile);
406+
407+
coverageSensor.execute(sensorContext);
408+
409+
assertThat(sensorContext.lineHits("moduleKey:my_app/__init__.py", 1)).isOne();
410+
}
411+
319412
@Test
320413
void should_warn_if_source_is_not_directory() {
321414
settings.setProperty(PythonCoverageSensor.REPORT_PATHS_KEY, "coverage_source_invalid_directory.xml");
322415
coverageSensor.execute(context);
323-
File file1 = new File("src/test/resources/org/sonar/plugins/python/coverage-reports/sources/file1.py");
324-
File file2 = new File("src/test/resources/org/sonar/plugins/python/coverage-reports/sources/file2.py");
325-
String message1 = "Invalid directory path in 'source' element: " + file1.getPath();
326-
String message2 = "Invalid directory path in 'source' element: " + file2.getPath();
416+
String message1 = "Invalid directory path in 'source' element: sources" + File.separator + "file1.py";
417+
String message2 = "Invalid directory path in 'source' element: sources" + File.separator + "file2.py";
327418
assertThat(logTester.logs(Level.WARN)).contains(message1);
328419
assertThat(logTester.logs(Level.WARN)).contains(message2);
329420
verify(analysisWarnings, times(1)).addUnique("The following error(s) occurred while trying to import coverage report:" + System.lineSeparator() + message1 + System.lineSeparator() + message2);

python-commons/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage.4.4.2-multi-sources.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
44
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
55
<sources>
6-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder2</source>
7-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1</source>
6+
<source>sources/folder2</source>
7+
<source>sources/folder1</source>
88
</sources>
99
<packages>
1010
<package name=".">

python-commons/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage.4.4.2.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
44
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
55
<sources>
6-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports/sources</source>
6+
<source>sources</source>
77
</sources>
88
<packages>
99
<package branch-rate="0.5" complexity="0" line-rate="0.75" name=".">

python-commons/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage_source_invalid_directory.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
44
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
55
<sources>
6-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports/sources/file1.py</source>
7-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports/sources/file2.py</source>
6+
<source>sources/file1.py</source>
7+
<source>sources/file2.py</source>
88
</sources>
99
<packages></packages>
1010
</coverage>

python-commons/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage_with_unresolved_absolute_path.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
44
<coverage branch-rate="0.5" line-rate="0.27397260274" timestamp="1335184370" version="gcovr 2.5-prerelease (r2774)">
55
<sources>
6-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports</source>
6+
<source>sources</source>
77
</sources>
88

99
<packages>
@@ -21,7 +21,7 @@
2121

2222
<package branch-rate="0.0" complexity="0.0" line-rate="0.0" name="sources.utils">
2323
<classes>
24-
<class branch-rate="0.0" complexity="0.0" filename="sources/not_exist.py" line-rate="0.0" name="code_chunks_cpp">
24+
<class branch-rate="0.0" complexity="0.0" filename="not_exist.py" line-rate="0.0" name="code_chunks_cpp">
2525
<lines>
2626
<line branch="false" hits="1" number="1"/>
2727
<line branch="false" hits="0" number="4"/>
@@ -33,7 +33,7 @@
3333

3434
<package branch-rate="0.0" complexity="0.0" line-rate="0.0" name="sources.utils">
3535
<classes>
36-
<class branch-rate="0.0" complexity="0.0" filename="sources/file1.py" line-rate="0.0" name="code_chunks_cpp">
36+
<class branch-rate="0.0" complexity="0.0" filename="file1.py" line-rate="0.0" name="code_chunks_cpp">
3737
<lines>
3838
<line branch="false" hits="1" number="1"/>
3939
<line branch="false" hits="0" number="4"/>

python-commons/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage_with_unresolved_path.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
44
<coverage branch-rate="0.5" line-rate="0.27397260274" timestamp="1335184370" version="gcovr 2.5-prerelease (r2774)">
55
<sources>
6-
<source>src/test/resources/org/sonar/plugins/python/coverage-reports</source>
6+
<source>sources</source>
77
</sources>
88
<packages>
99
<package branch-rate="0.0" complexity="0.0" line-rate="0.0" name="sources.utils">
1010
<classes>
11-
<class branch-rate="0.0" complexity="0.0" filename="sources/not_exist.py" line-rate="0.0" name="code_chunks_cpp">
11+
<class branch-rate="0.0" complexity="0.0" filename="not_exist.py" line-rate="0.0" name="code_chunks_cpp">
1212
<lines>
1313
<line branch="false" hits="1" number="1"/>
1414
<line branch="false" hits="0" number="4"/>
@@ -20,7 +20,7 @@
2020

2121
<package branch-rate="0.0" complexity="0.0" line-rate="0.0" name="sources.utils">
2222
<classes>
23-
<class branch-rate="0.0" complexity="0.0" filename="sources/file1.py" line-rate="0.0" name="code_chunks_cpp">
23+
<class branch-rate="0.0" complexity="0.0" filename="file1.py" line-rate="0.0" name="code_chunks_cpp">
2424
<lines>
2525
<line branch="false" hits="1" number="1"/>
2626
<line branch="false" hits="0" number="4"/>

0 commit comments

Comments
 (0)