Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import edu.hm.hafner.util.FilteredLog;

import one.util.streamex.StreamEx;

import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
Expand All @@ -37,6 +39,10 @@ public class CodeDeltaCalculator {
"Failed to map SCM paths from the reference with coverage report paths from the reference "
+ "due to ambiguous fully qualified names";

static final String CODE_DELTA_TO_COVERAGE_DATA_MISMATCH_ERROR_TEMPLATE =
"Unexpected behavior detected when comparing coverage data with the code delta "
+ "- there are ambiguous paths when comparing new with former file paths: ";

static final String EMPTY_OLD_PATHS_WARNING = "File renamings have been detected for files which have not been "
+ "part of the reference coverage report. These files are skipped when calculating the coverage deltas:";

Expand Down Expand Up @@ -134,7 +140,7 @@ public Map<String, FileChanges> mapScmChangesToReportPaths(
/**
* Creates a mapping between the currently used coverage report paths and the corresponding paths that has been used
* for the same coverage nodes before the modifications. This affects only renamed and untouched / modified files
* without a rename, since added files did not exist before and deleted files do not exist anymore.
* without a renaming, since added files did not exist before and deleted files do not exist anymore.
*
* @param root
* The root of the coverage tree
Expand Down Expand Up @@ -164,15 +170,14 @@ public Map<String, String> createOldPathMapping(final CoverageNode root, final C
// the SCM paths and the coverage report paths from the reference
Map<String, String> oldScmToOldReportPathMapping =
getScmToReportPathMapping(oldPathMapping.values(), oldReportPaths);
verifyScmToReportPathMapping(oldPathMapping, log);

// replacing the old SCM paths with the old report paths
Set<String> oldReportPathsWithRename = oldPathMapping.keySet();
Set<String> newReportPathsWithRename = oldPathMapping.keySet();
oldPathMapping.forEach((reportPath, oldScmPath) -> {
String oldReportPath = oldScmToOldReportPathMapping.get(oldScmPath);
oldPathMapping.replace(reportPath, oldReportPath);
});
if (!oldReportPathsWithRename.equals(oldPathMapping.keySet())) {
if (!newReportPathsWithRename.equals(oldPathMapping.keySet())) {
throw new CodeDeltaException(AMBIGUOUS_OLD_PATHS_ERROR);
}

Expand All @@ -183,6 +188,7 @@ public Map<String, String> createOldPathMapping(final CoverageNode root, final C
.forEach(node -> oldPathMapping.put(node.getPath(), node.getPath()));

removeMissingReferences(oldPathMapping, log);
verifyOldPathMapping(oldPathMapping, log);

return oldPathMapping;
}
Expand Down Expand Up @@ -214,7 +220,7 @@ private Map<String, String> getScmToReportPathMapping(
}

/**
* Verifies the the passed mapping between SCM and coverage report paths.
* Verifies the passed mapping between SCM and coverage report paths.
*
* @param pathMapping
* The path mapping
Expand Down Expand Up @@ -257,4 +263,39 @@ private void removeMissingReferences(final Map<String, String> oldPathMapping, f
log.logInfo(EMPTY_OLD_PATHS_WARNING + System.lineSeparator() + skippedFiles);
}
}

/**
* Verifies that the mapping between the file paths of the current build and the former file paths of the reference
* builds are clearly assigned to each other. This is done to prevent an unexpected behavior triggered by a third
* party library in case that the code delta does not match with the coverage data.
*
* @param oldPathMapping
* The file path mapping
* @param log
* The log
*
* @throws CodeDeltaException
* when the mapping is ambiguous
*/
static void verifyOldPathMapping(final Map<String, String> oldPathMapping, final FilteredLog log)
throws CodeDeltaException {
Set<String> duplicates = StreamEx.of(oldPathMapping.values())
.distinct(2)
.collect(Collectors.toSet());

Map<String, String> duplicateEntries = oldPathMapping.entrySet().stream()
.filter(entry -> duplicates.contains(entry.getValue()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));

if (!duplicates.isEmpty()) {
String mismatches = duplicateEntries.entrySet().stream()
.limit(20) // prevent log overflows
.map(entry -> String.format("new: '%s' - former: '%s'", entry.getKey(), entry.getValue()))
.collect(Collectors.joining("," + System.lineSeparator()));
String errorMessage =
CODE_DELTA_TO_COVERAGE_DATA_MISMATCH_ERROR_TEMPLATE + System.lineSeparator() + mismatches;
throw new CodeDeltaException(errorMessage);
}
log.logInfo("Successfully verified that the coverage data matches with the code delta");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,27 @@ void shouldNotCreateOldPathMappingWithMissingReferenceNodes() throws CodeDeltaEx
);
}

// checks the functionality to prevent exceptions in case of false calculated code deltas
@Test
void shouldNotCreateOldPathMappingWithCodeDeltaMismatches() {
CodeDeltaCalculator codeDeltaCalculator = createCodeDeltaCalculator();
FilteredLog log = createFilteredLog();
CoverageNode tree = createMockedCoverageTree();
CoverageNode referenceTree = createMockedReferenceCoverageTree();

// two changes with the same former path
Map<String, FileChanges> changes = new HashMap<>();
changes.put(REPORT_PATH_RENAME, createFileChanges(SCM_PATH_RENAME, OLD_SCM_PATH_RENAME, FileEditType.RENAME));
changes.put(REPORT_PATH_MODIFY, createFileChanges(REPORT_PATH_MODIFY, OLD_SCM_PATH_RENAME, FileEditType.RENAME));

assertThatThrownBy(() -> codeDeltaCalculator.createOldPathMapping(tree, referenceTree, changes, log))
.isInstanceOf(CodeDeltaException.class)
.hasMessageStartingWith(CODE_DELTA_TO_COVERAGE_DATA_MISMATCH_ERROR_TEMPLATE)
.hasMessageContainingAll(
String.format("new: '%s' - former: '%s',", REPORT_PATH_RENAME, OLD_REPORT_PATH_RENAME),
String.format("new: '%s' - former: '%s'", REPORT_PATH_MODIFY, OLD_REPORT_PATH_RENAME));
}

/**
* Creates an instance of {@link CodeDeltaCalculator}.
*
Expand Down