Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Parsing errors on attributes preceding visibility sections.
- Parsing errors on attributes preceding `var` sections in type bodies.
- Errors when parsing coverage reports if duplicate file names were present in the project.

## [1.21.0] - 2026-09-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
import au.com.integradev.delphi.msbuild.DelphiProjectHelper;
import com.google.common.base.Splitter;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import java.io.File;
import java.util.Map;
import java.util.List;
import java.util.function.Supplier;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
Expand All @@ -46,19 +47,24 @@
public class DelphiCodeCoverageParser implements DelphiCoverageParser {
private static final Logger LOG = LoggerFactory.getLogger(DelphiCodeCoverageParser.class);
private final DelphiProjectHelper delphiProjectHelper;
private final Supplier<Map<String, InputFile>> fileNameToInputFile;
private final Supplier<ListMultimap<String, InputFile>> fileNameToInputFiles;

public DelphiCodeCoverageParser(DelphiProjectHelper delphiProjectHelper) {
this.delphiProjectHelper = delphiProjectHelper;
this.fileNameToInputFile = Suppliers.memoize(this::indexInputFiles);
this.fileNameToInputFiles = Suppliers.memoize(this::indexInputFiles);
}

private Map<String, InputFile> indexInputFiles() {
var builder = ImmutableSortedMap.<String, InputFile>orderedBy(String.CASE_INSENSITIVE_ORDER);
private ListMultimap<String, InputFile> indexInputFiles() {
ListMultimap<String, InputFile> inputFiles =
MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER).arrayListValues().build();

for (InputFile inputFile : delphiProjectHelper.inputFiles()) {
builder.put(inputFile.filename(), inputFile);
if (inputFile.type() != InputFile.Type.TEST) {
inputFiles.put(inputFile.filename(), inputFile);
}
}
return builder.build();

return inputFiles;
}

@Override
Expand Down Expand Up @@ -103,16 +109,23 @@ private void parseReportFile(SensorContext sensorContext, File reportFile) {

private void parseFileNode(SensorContext sensorContext, Node srcFile) {
String fileName = srcFile.getAttributes().getNamedItem("name").getTextContent();
InputFile sourceFile = fileNameToInputFile.get().get(fileName);
if (sourceFile == null) {
List<InputFile> candidates = fileNameToInputFiles.get().get(fileName);

if (candidates.isEmpty()) {
LOG.debug("File not found in project: {}", fileName);
return;
}

if (sourceFile.type() == InputFile.Type.TEST) {
if (candidates.size() > 1) {
LOG.warn(
"Coverage data skipped for ambiguous file name '{}': {} source files match",
fileName,
candidates.size());
return;
}

InputFile sourceFile = candidates.get(0);

LOG.debug("Parsing line hit information for file: {}", fileName);

NewCoverage newCoverage = sensorContext.newCoverage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,50 @@ void testMismatchedCasingAllowed() {
assertThat(context.lineHits(MAIN_WINDOW_FILE_KEY, 40)).isEqualTo(1);
}

@Test
void testDuplicateFileNamesAreSkipped() {
setup(InputFile.Type.MAIN);

context
.fileSystem()
.add(
TestInputFileBuilder.create("", "duplicate/" + MAIN_WINDOW_FILENAME)
.setLanguage(Delphi.KEY)
.setType(InputFile.Type.MAIN)
.setContents("unit MainWindow;")
.build());

assertThatCode(() -> parser.parse(context, DelphiUtils.getResource(NORMAL_COVERAGE)))
.doesNotThrowAnyException();

// Globals.pas keeps a unique name, so its coverage is still recorded...
assertThat(context.lineHits(GLOBALS_FILE_KEY, 16)).isEqualTo(1);
assertThat(context.lineHits(GLOBALS_FILE_KEY, 17)).isEqualTo(1);
assertThat(context.lineHits(GLOBALS_FILE_KEY, 23)).isZero();

// ...while the ambiguous name is skipped.
assertThat(context.lineHits(MAIN_WINDOW_FILE_KEY, 31)).isNull();
}

@Test
void testDuplicateTestFileNamesDoNotCauseAmbiguity() {
setup(InputFile.Type.MAIN);

context
.fileSystem()
.add(
TestInputFileBuilder.create("", "test/" + MAIN_WINDOW_FILENAME)
.setLanguage(Delphi.KEY)
.setType(InputFile.Type.TEST)
.setContents("unit MainWindow;")
.build());

parser.parse(context, DelphiUtils.getResource(NORMAL_COVERAGE));

assertThat(context.lineHits(GLOBALS_FILE_KEY, 16)).isEqualTo(1);
assertThat(context.lineHits(MAIN_WINDOW_FILE_KEY, 31)).isEqualTo(1);
}

@Test
void testCoverageIsNotReportedForTestInputFiles() {
setup(InputFile.Type.TEST);
Expand Down
Loading