diff --git a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/CoberturaParser.java b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/CoberturaParser.java index 98c663c64a..5d9752d3d0 100644 --- a/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/CoberturaParser.java +++ b/sonar-python-plugin/src/main/java/org/sonar/plugins/python/coverage/CoberturaParser.java @@ -20,7 +20,12 @@ package org.sonar.plugins.python.coverage; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import javax.annotation.Nullable; import javax.xml.stream.XMLStreamException; import org.apache.commons.lang.StringUtils; import org.codehaus.staxmate.in.SMInputCursor; @@ -43,7 +48,8 @@ public void parseReport(File xmlFile, SensorContext context, final Map { - File baseDirectory = context.fileSystem().baseDir(); + File defaultBaseDirectory = context.fileSystem().baseDir(); + List baseDirectories = Collections.singletonList(defaultBaseDirectory); try { rootCursor.advance(); } catch (com.ctc.wstx.exc.WstxEOFException eofExc) { @@ -53,9 +59,9 @@ public void parseReport(File xmlFile, SensorContext context, final Map extractBaseDirectories(SMInputCursor sources, File defaultBaseDirectory) throws XMLStreamException { + List baseDirectories = new ArrayList<>(); SMInputCursor source = sources.childElementCursor("source"); while (source.getNext() != null) { String path = source.collectDescendantText(); if (!StringUtils.isBlank(path)) { File baseDirectory = new File(path); if (baseDirectory.isDirectory()) { - return baseDirectory; + baseDirectories.add(baseDirectory); } else { LOG.warn("Invalid directory path in 'source' element: {}", path); } } } - return defaultBaseDirectory; + if (baseDirectories.isEmpty()) { + return Collections.singletonList(defaultBaseDirectory); + } + return baseDirectories; } - private void collectFileMeasures(SMInputCursor classCursor, SensorContext context, Map coverageData, File baseDirectory) + private void collectFileMeasures(SMInputCursor classCursor, SensorContext context, Map coverageData, List baseDirectories) throws XMLStreamException { while (classCursor.getNext() != null) { - File file = new File(classCursor.getAttrValue("filename")); - if (!file.isAbsolute()) { - file = new File(baseDirectory, file.getPath()); - } - InputFile inputFile = context.fileSystem().inputFile(context.fileSystem().predicates().hasAbsolutePath(file.getAbsolutePath())); + String filename = classCursor.getAttrValue("filename"); + InputFile inputFile = resolve(context, baseDirectories, filename); if (inputFile != null) { NewCoverage coverage = coverageData.computeIfAbsent(inputFile, f -> context.newCoverage().onFile(f)); collectFileData(classCursor, coverage); } else { classCursor.advance(); - unresolvedFilenameCount++; - if (unresolvedFilenameCount == 1) { - LOG.error("Cannot find the file '{}' in the base directory '{}', ignoring coverage measures for this file", file.getPath(), baseDirectory.getPath()); - } } } } + @Nullable + private InputFile resolve(SensorContext context, List baseDirectories, String filename) { + List fileList = baseDirectories.stream() + .map(base -> new File(base, filename)) + .filter(File::exists) + .collect(Collectors.toList()); + if (fileList.isEmpty()) { + logUnresolvedFile("Cannot resolve the file path '{}' of the coverage report, the file does not exist in all .", filename); + return null; + } + if (fileList.size()>1) { + logUnresolvedFile("Cannot resolve the file path '{}' of the coverage report, ambiguity, the file exists in several .", filename); + return null; + } + String absolutePath = fileList.get(0).getAbsolutePath(); + return context.fileSystem().inputFile(context.fileSystem().predicates().hasAbsolutePath(absolutePath)); + } + + private void logUnresolvedFile(String message, String filename) { + unresolvedFilenameCount++; + if (unresolvedFilenameCount == 1) { + LOG.error(message, filename); + } + } + private static void collectFileData(SMInputCursor classCursor, NewCoverage coverage) throws XMLStreamException { SMInputCursor line = classCursor.childElementCursor("lines").advance().childElementCursor("line"); while (line.getNext() != null) { diff --git a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java index 8dbfa59584..8265eb2779 100644 --- a/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java +++ b/sonar-python-plugin/src/test/java/org/sonar/plugins/python/coverage/PythonCoverageSensorTest.java @@ -63,6 +63,9 @@ public void init() { inputFile("sources/file2.py", Type.MAIN); inputFile("sources/file3.py", Type.MAIN); inputFile("sources/file4.py", Type.MAIN); + inputFile("sources/folder1/file1.py", Type.MAIN); + inputFile("sources/folder1/file2.py", Type.MAIN); + inputFile("sources/folder2/file2.py", Type.MAIN); } private InputFile inputFile(String relativePath, Type type) { @@ -146,6 +149,17 @@ public void test_coverage_4_4_2() { assertThat(context.coveredConditions(FILE4_KEY, 10)).isEqualTo(1); } + @Test + public void test_coverage_4_4_2_multi_source() { + settings.setProperty(PythonCoverageSensor.REPORT_PATH_KEY, "coverage.4.4.2-multi-sources.xml"); + coverageSensor.execute(context); + + assertThat(context.lineHits("moduleKey:sources/folder1/file1.py", 1)).isEqualTo(1); + // file2.py ambiguity + assertThat(context.lineHits("moduleKey:sources/folder1/file2.py", 1)).isNull(); + assertThat(context.lineHits("moduleKey:sources/folder2/file2.py", 1)).isNull(); + } + @Test public void test_unique_report() { settings.setProperty(PythonCoverageSensor.REPORT_PATH_KEY, "*coverage.4.4.2*.xml"); diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage.4.4.2-multi-sources.xml b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage.4.4.2-multi-sources.xml new file mode 100644 index 0000000000..b3c177bdb4 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/coverage.4.4.2-multi-sources.xml @@ -0,0 +1,31 @@ + + + + + + src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder2 + src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file1.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file1.py new file mode 100644 index 0000000000..eb28ef4401 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file1.py @@ -0,0 +1 @@ +foo() diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file2.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file2.py new file mode 100644 index 0000000000..eb28ef4401 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder1/file2.py @@ -0,0 +1 @@ +foo() diff --git a/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder2/file2.py b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder2/file2.py new file mode 100644 index 0000000000..eb28ef4401 --- /dev/null +++ b/sonar-python-plugin/src/test/resources/org/sonar/plugins/python/coverage-reports/sources/folder2/file2.py @@ -0,0 +1 @@ +foo()