Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(report) Add new score value for aggregation of several modules #786

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 @@ -99,14 +99,20 @@ private Map<InstructionLocation, Set<TestInfo>> blocksToMap(
for (final BlockCoverage blockData : coverageData) {
for (int i = blockData.getBlock().getFirstInsnInBlock();
i <= blockData.getBlock().getLastInsnInBlock(); i++) {
blockCoverageMap.put(new InstructionLocation(blockData.getBlock(), i),
new HashSet<>(
FCollection.map(blockData.getTests(), toTestInfo(blockData))));
addBlockCoverage(blockCoverageMap, new InstructionLocation(blockData.getBlock(), i),
FCollection.map(blockData.getTests(), toTestInfo(blockData)));
}
}
return blockCoverageMap;
}

private void addBlockCoverage(Map<InstructionLocation, Set<TestInfo>> blockCoverageMap, InstructionLocation instructionLocation, List<TestInfo> tests) {
if (!blockCoverageMap.containsKey(instructionLocation)) {
blockCoverageMap.put(instructionLocation, new HashSet<>());
}
blockCoverageMap.get(instructionLocation).addAll(tests);
}

private Function<String, TestInfo> toTestInfo(final BlockCoverage blockData) {
return a -> new TestInfo(null, a, 0, Optional.ofNullable(blockData.getBlock().getLocation().getClassName()), blockData.getBlock().getBlock());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ public class AnnotatedLineFactory {
private final Collection<MutationResult> mutations;
private final CoverageDatabase statistics;
private final Collection<ClassInfo> classesInFile;
private final DetectionStatusCalculator statusCalculator;

public AnnotatedLineFactory(
final Collection<MutationResult> mutations,
final CoverageDatabase statistics, final Collection<ClassInfo> classes) {
final CoverageDatabase statistics, final Collection<ClassInfo> classes,
final DetectionStatusCalculator statusCalculator) {
this.mutations = mutations;
this.statistics = statistics;
this.classesInFile = classes;
this.statusCalculator = statusCalculator;
}

public List<Line> convert(final Reader source) throws IOException {
Expand All @@ -59,9 +62,10 @@ private Function<String, Line> stringToAnnotatedLine() {

@Override
public Line apply(final String a) {
List<MutationResult> mutationsForLine = getMutationsForLine(this.lineNumber);
final Line l = new Line(this.lineNumber,
StringUtil.escapeBasicHtmlChars(a), lineCovered(this.lineNumber),
getMutationsForLine(this.lineNumber));
mutationsForLine, statusCalculator.calculate(mutationsForLine));
this.lineNumber++;
return l;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.pitest.mutationtest.report.html;

import java.util.List;

import org.pitest.mutationtest.DetectionStatus;
import org.pitest.mutationtest.MutationResult;

public interface DetectionStatusCalculator {
DetectionStatus calculate(List<MutationResult> mutationsForLine);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package org.pitest.mutationtest.report.html;

import java.util.Collections;
import java.util.List;

import java.util.Optional;
Expand All @@ -25,14 +26,17 @@ public class Line {
private final String text;
private final LineStatus lineCovered;
private final List<MutationResult> mutations;
private final DetectionStatus detectionStatus;

public Line(final long number, final String text,
final LineStatus lineCovered, final List<MutationResult> mutations) {
final LineStatus lineCovered, final List<MutationResult> mutations,
final DetectionStatus detectionStatus) {
this.number = number;
this.text = text;
this.lineCovered = lineCovered;
this.mutations = mutations;
mutations.sort(new ResultComparator());
this.detectionStatus = detectionStatus;
Collections.sort(mutations, new ResultComparator());
}

public long getNumber() {
Expand All @@ -52,10 +56,7 @@ public List<MutationResult> getMutations() {
}

public Optional<DetectionStatus> detectionStatus() {
if (this.mutations.isEmpty()) {
return Optional.empty();
}
return Optional.ofNullable(this.mutations.get(0).getStatus());
return Optional.ofNullable(detectionStatus);
}

public int getNumberOfMutations() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.pitest.mutationtest.report.html;

import static org.pitest.mutationtest.report.html.MutationScoreUtil.groupBySameMutationOnSameLines;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.pitest.mutationtest.DetectionStatus;
import org.pitest.mutationtest.MutationResult;
import org.pitest.mutationtest.engine.MutationIdentifier;
import org.pitest.mutationtest.report.html.ResultComparator.DetectionStatusComparator;

/**
* Compute the {@link DetectionStatus} according to the list of mutations applied on the line.
* This algorithm works with multi-projects. For example:
*
* <strong>Tested class</strong>
* <pre><code>
* 1 class Foo {
* 2 public int compare() {
* 3 if (a > b) {
* 4 return -1;
* 5 }
* 6 if (a < b) {
* 7 return 1;
* 8 }
* 9 return 0;
* 10 }
* 11 }
* </pre></code>
*
* Module A contains Foo class.
* Module B depends on Module A.
*
* <strong>Test results 1</strong>
* <table>
* <caption>mutations</caption>
* <thead><tr><th>Module</th><th>Test</th><th>Mutation results</th></tr></thead>
* <tbody>
* <tr><td>Module A</td><td>UnitTest.identical()</td><td><ul><li>line 9: return replaced by -1 -> SURVIVED</li></ul></td></tr>
* <tr><td>Module A</td><td>UnitTest.identical()</td><td><ul><li>line 9: return replaced by 1 -> SURVIVED</li></ul></td></tr>
* <tr><td>Module B</td><td>IntegrationTest.sort()</td><td><ul><li>line 9: return replaced by -1 -> KILLED</li></ul></td></tr>
* <tr><td>Module B</td><td>IntegrationTest.sort()</td><td><ul><li>line 9: return replaced by 1 -> SURVIVED</li></ul></td></tr>
* </tbody>
* </table>
*
* There are several mutations applied on the same line and one mutation has survived across all tests and one mutation has been killed.
* So the result should be SURVIVED because for the same line, at least one mutation has survived.
*
* <strong>Test results 2</strong>
* <table>
* <caption>mutations</caption>
* <thead><tr><th>Module</th><th>Test</th><th>Mutation results</th></tr></thead>
* <tbody>
* <tr><td>Module A</td><td>UnitTest.identical()</td><td><ul><li>line 9: return replaced by -1 -> SURVIVED</li></ul></td></tr>
* <tr><td>Module A</td><td>UnitTest.identical()</td><td><ul><li>line 9: return replaced by 1 -> KILLED</li></ul></td></tr>
* <tr><td>Module B</td><td>IntegrationTest.sort()</td><td><ul><li>line 9: return replaced by -1 -> KILLED</li></ul></td></tr>
* <tr><td>Module B</td><td>IntegrationTest.sort()</td><td><ul><li>line 9: return replaced by 1 -> SURVIVED</li></ul></td></tr>
* </tbody>
* </table>
*
* In this case, all mutations are killed across several projects. So the result should be KILLED.
*
* @author Aurélien Baudet
*
*/
public class MergeStatusForSameMutationAndLine implements DetectionStatusCalculator {
@Override
public DetectionStatus calculate(List<MutationResult> mutationsForLine) {
if (mutationsForLine.isEmpty()) {
return null;
}
Map<MutationIdentifier, Collection<MutationResult>> grouped = groupBySameMutationOnSameLines(mutationsForLine);
Map<MutationIdentifier, DetectionStatus> statusForSameMutation = new HashMap<>();
// compute the status for each mutation
for (Entry<MutationIdentifier, Collection<MutationResult>> resultsByMutation : grouped.entrySet()) {
statusForSameMutation.put(resultsByMutation.getKey(), computeDetectionStatusForSameLineAndMutation(resultsByMutation.getValue()));
}
// for different mutations, take the worst mutation
List<DetectionStatus> status = new ArrayList<>(statusForSameMutation.values());
Collections.sort(status, new DetectionStatusComparator());
if (status.isEmpty()) {
return null;
}
return status.get(0);
}

private DetectionStatus computeDetectionStatusForSameLineAndMutation(Collection<MutationResult> results) {
List<MutationResult> list = new ArrayList<>(results);
Collections.sort(list, new ResultComparator());
if (list.isEmpty()) {
return null;
}
// use best status to handle several tests across projects
return list.get(list.size() - 1).getStatus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ private List<Line> createAnnotatedSourceCodeLines(final String sourceFile,
sourceFile);
if (reader.isPresent()) {
final AnnotatedLineFactory alf = new AnnotatedLineFactory(
mutationsForThisFile.list(), this.coverage, classes);
mutationsForThisFile.list(), this.coverage, classes,
new MergeStatusForSameMutationAndLine());
return alf.convert(reader.get());
}
return Collections.emptyList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.pitest.mutationtest.report.html;

public class MutationScore {
private long numberOfUniqueMutations;
private long numberOfUniqueMutationsKilled;
private long numberOfUniqueMutationsDetected;

public MutationScore() {
this(0, 0, 0);
}

public MutationScore(long numberOfUniqueMutations,
long numberOfUniqueMutationsKilled,
long numberOfUniqueMutationsDetected) {
super();
this.numberOfUniqueMutations = numberOfUniqueMutations;
this.numberOfUniqueMutationsKilled = numberOfUniqueMutationsKilled;
this.numberOfUniqueMutationsDetected = numberOfUniqueMutationsDetected;
}

public long getNumberOfUniqueMutations() {
return numberOfUniqueMutations;
}

public long getNumberOfUniqueMutationsKilled() {
return numberOfUniqueMutationsKilled;
}

public long getNumberOfUniqueMutationsDetected() {
return numberOfUniqueMutationsDetected;
}

public long getNumberOfUniqueMutationsPossiblyDetected() {
return numberOfUniqueMutationsDetected - numberOfUniqueMutationsKilled;
}

public int getKilledScore() {
return numberOfUniqueMutations == 0 ? 100
: Math.round((100f * numberOfUniqueMutationsKilled)
/ numberOfUniqueMutations);
}

public int getPossiblyDetectedScore() {
return numberOfUniqueMutations == 0 ? 100
: Math.round((100f * getNumberOfUniqueMutationsDetected())
/ numberOfUniqueMutations);
}

public void add(MutationScore score) {
numberOfUniqueMutations += score.numberOfUniqueMutations;
numberOfUniqueMutationsDetected += score.numberOfUniqueMutationsDetected;
numberOfUniqueMutationsKilled += score.numberOfUniqueMutationsKilled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.pitest.mutationtest.report.html;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.pitest.mutationtest.MutationResult;
import org.pitest.mutationtest.engine.MutationIdentifier;

public final class MutationScoreUtil {
private MutationScoreUtil() {
super();
}

public static Map<MutationIdentifier, Collection<MutationResult>> groupBySameMutationOnSameLines(Collection<MutationResult> mutations) {
Map<MutationIdentifier, Collection<MutationResult>> grouped = new HashMap<>();
for (MutationResult each : mutations) {
Collection<MutationResult> results = grouped.get(each.getDetails().getId());
if (results == null) {
results = new ArrayList<>();
grouped.put(each.getDetails().getId(), results);
}
results.add(each);
}
return grouped;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@
*/
package org.pitest.mutationtest.report.html;

import org.pitest.classinfo.ClassInfo;
import org.pitest.coverage.TestInfo;
import org.pitest.functional.FCollection;
import org.pitest.mutationtest.MutationResult;
import static org.pitest.mutationtest.DetectionStatus.KILLED;
import static org.pitest.mutationtest.report.html.MutationScoreUtil.groupBySameMutationOnSameLines;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.pitest.classinfo.ClassInfo;
import org.pitest.coverage.TestInfo;
import org.pitest.functional.FCollection;
import org.pitest.mutationtest.MutationResult;

public class MutationTestSummaryData {

private final String fileName;
Expand Down Expand Up @@ -55,6 +58,7 @@ public MutationTotals getTotals() {
mt.addLines(getNumberOfLines());
mt.addLinesCovered(this.numberOfCoveredLines);
mt.addMutationsWithCoverage(this.getNumberOfMutationsWithCoverage());
mt.addMutationScore(new MutationScore(getNumberOfUniqueMutations(), getNumberOfUniqueMutationsKilled(), getNumberOfUniqueMutationsDetected()));
return mt;
}

Expand Down Expand Up @@ -130,6 +134,36 @@ private long getNumberOfMutationsDetected() {
return count;
}

private long getNumberOfUniqueMutations() {
return groupBySameMutationOnSameLines(mutations).entrySet().size();
}

private long getNumberOfUniqueMutationsDetected() {
int count = 0;
for (final Collection<MutationResult> results : groupBySameMutationOnSameLines(mutations).values()) {
for (MutationResult each : results) {
if (each.getStatus().isDetected()) {
count++;
break;
}
}
}
return count;
}

private long getNumberOfUniqueMutationsKilled() {
int count = 0;
for (final Collection<MutationResult> results : groupBySameMutationOnSameLines(mutations).values()) {
for (MutationResult each : results) {
if (each.getStatus() == KILLED) {
count++;
break;
}
}
}
return count;
}

private Function<MutationResult, Iterable<TestInfo>> mutationToTargettedTests() {
return a -> a.getDetails().getTestsInOrder();
}
Expand Down
Loading