Skip to content

Commit

Permalink
Merge pull request #891 from hcoles/history_performance
Browse files Browse the repository at this point in the history
Reduce cost of history recording on larger projects
  • Loading branch information
hcoles committed Apr 29, 2021
2 parents fe4c0ca + 7807743 commit 32ea625
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 240 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
package org.pitest.aggregate;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;

import org.pitest.classpath.CodeSource;
import org.pitest.coverage.BlockCoverage;
import org.pitest.coverage.BlockLocation;
import org.pitest.coverage.CoverageData;
import org.pitest.coverage.CoverageDatabase;
import org.pitest.coverage.InstructionLocation;
import org.pitest.coverage.ReportCoverage;
import org.pitest.coverage.TestInfo;
import org.pitest.coverage.analysis.LineMapper;
import org.pitest.functional.FCollection;
Expand All @@ -31,6 +18,20 @@
import org.pitest.util.Log;
import org.pitest.util.ResultOutputStrategy;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public final class ReportAggregator {
private final ResultOutputStrategy resultOutputStrategy;
private final DataLoader<BlockCoverage> blockCoverageLoader;
Expand Down Expand Up @@ -65,7 +66,7 @@ private MutationResultListener createResultListener(final MutationMetaData mutat
final SourceLocator sourceLocator = new SmartSourceLocator(this.sourceCodeDirectories);

final CodeSource codeSource = this.codeSourceAggregator.createCodeSource();
final CoverageDatabase coverageDatabase = calculateCoverage(codeSource, mutationMetaData);
final ReportCoverage coverageDatabase = calculateCoverage(codeSource);
final Collection<String> mutatorNames = new HashSet<>(FCollection.flatMap(mutationMetaData.getMutations(), resultToMutatorName()));

return new MutationHtmlReportListener(coverageDatabase, this.resultOutputStrategy, mutatorNames, sourceLocator);
Expand All @@ -82,27 +83,34 @@ private static Function<MutationResult, List<String>> resultToMutatorName() {
};
}

private CoverageData calculateCoverage(final CodeSource codeSource, final MutationMetaData metadata) throws ReportAggregationException {
final Collection<BlockCoverage> coverageData = this.blockCoverageLoader.loadData();
private ReportCoverage calculateCoverage(final CodeSource codeSource) throws ReportAggregationException {
try {
final Map<InstructionLocation, Set<TestInfo>> blockCoverageMap = blocksToMap(coverageData);
return new CoverageData(codeSource, new LineMapper(codeSource),blockCoverageMap);
Collection<BlockLocation> coverageData = this.blockCoverageLoader.loadData().stream()
.map(BlockCoverage::getBlock)
.collect(Collectors.toList());
CoverageData cd = new CoverageData(codeSource, new LineMapper(codeSource));
cd.loadBlockDataOnly(coverageData);
return cd;
} catch (final Exception e) {
throw new ReportAggregationException(e.getMessage(), e);
}
}

private Map<InstructionLocation, Set<TestInfo>> blocksToMap(
private Map<TestInfo, Collection<BlockLocation>> blocksToMap(
final Collection<BlockCoverage> coverageData) {
final Map<InstructionLocation, Set<TestInfo>> blockCoverageMap = new HashMap<>();

Map<TestInfo, Collection<BlockLocation>> blockCoverageMap = new HashMap<>();

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))));
List<TestInfo> tests = blockData.getTests().stream()
.map(toTestInfo(blockData))
.collect(Collectors.toList());

for (TestInfo each : tests) {
Collection<BlockLocation> collection = blockCoverageMap.computeIfAbsent(each, k -> new ArrayList<>());
collection.add(blockData.getBlock());
}

}
return blockCoverageMap;
}
Expand Down
186 changes: 37 additions & 149 deletions pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -36,58 +35,59 @@
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toCollection;
import java.util.stream.Collectors;

public class CoverageData implements CoverageDatabase {

private static final Logger LOG = Log
.getLogger();
private static final Logger LOG = Log.getLogger();

// We calculate block coverage, but everything currently runs on line
// coverage. Ugly mess of maps below should go when
// api changed to work via blocks
private final Map<InstructionLocation, Set<TestInfo>> instructionCoverage;
private final Map<BlockLocation, Set<Integer>> blocksToLines = new LinkedHashMap<>();
private final Map<ClassName, Map<ClassLine, Set<TestInfo>>> lineCoverage = new LinkedHashMap<>();
private final Map<String, Collection<ClassInfo>> classesForFile;

private final CodeSource code;
private final Map<InstructionLocation, Set<TestInfo>> instructionCoverage = new LinkedHashMap<>();
private final LegacyClassCoverage legacyClassCoverage;

private final LineMap lm;
private final CodeSource code;

private final List<Description> failingTestDescriptions = new ArrayList<>();
private final List<Description> failingTestDescriptions = new ArrayList<>();

public CoverageData(final CodeSource code, final LineMap lm) {
this(code, lm, new LinkedHashMap<>());
this.code = code;
this.legacyClassCoverage = new LegacyClassCoverage(code, lm);
}

public void calculateClassCoverage(final CoverageResult cr) {

public CoverageData(final CodeSource code, final LineMap lm, Map<InstructionLocation, Set<TestInfo>> instructionCoverage) {
this.instructionCoverage = instructionCoverage;
this.code = code;
this.lm = lm;
this.classesForFile = FCollection.bucket(this.code.getCode(),
keyFromClassInfo());
checkForFailedTest(cr);
final TestInfo ti = this.createTestInfo(cr.getTestUnitDescription(),
cr.getExecutionTime(), cr.getNumberOfCoveredBlocks());

legacyClassCoverage.addTestToClasses(ti,cr.getCoverage());

for (final BlockLocation each : cr.getCoverage()) {
for (int i = each.getFirstInsnInBlock();
i <= each.getLastInsnInBlock(); i++) {
addTestsToBlockMap(ti, new InstructionLocation(each, i));
}
}
}


// populates class with class level data only, without block level data
public void loadBlockDataOnly(Collection<BlockLocation> coverageData) {
legacyClassCoverage.loadBlockDataOnly(coverageData);
}


@Override
public Collection<TestInfo> getTestsForInstructionLocation(InstructionLocation location) {
return this.instructionCoverage.getOrDefault(location, Collections.emptySet());
}

@Override
public Collection<TestInfo> getTestsForClassLine(final ClassLine classLine) {
final Collection<TestInfo> result = getLineCoverageForClassName(
classLine.getClassName()).get(classLine);
if (result == null) {
return Collections.emptyList();
} else {
return result;
}
return legacyClassCoverage.getTestsForClassLine(classLine);
}

public boolean allTestsGreen() {
Expand All @@ -109,31 +109,12 @@ public Collection<ClassInfo> getClassInfo(final Collection<ClassName> classes) {

@Override
public int getNumberOfCoveredLines(final Collection<ClassName> mutatedClass) {
return mutatedClass.stream()
.map(this::getLineCoverageForClassName)
.mapToInt(Map::size)
.sum();
return legacyClassCoverage.getNumberOfCoveredLines(mutatedClass);
}

@Override
public Collection<TestInfo> getTestsForClass(final ClassName clazz) {
return this.instructionCoverage.entrySet().stream()
.filter(isFor(clazz))
.flatMap(toTests())
.collect(toCollection(() -> new TreeSet<>(new TestInfoNameComparator())));
}

public void calculateClassCoverage(final CoverageResult cr) {

checkForFailedTest(cr);
final TestInfo ti = this.createTestInfo(cr.getTestUnitDescription(),
cr.getExecutionTime(), cr.getNumberOfCoveredBlocks());
for (final BlockLocation each : cr.getCoverage()) {
for (int i = each.getFirstInsnInBlock();
i <= each.getLastInsnInBlock(); i++) {
addTestsToBlockMap(ti, new InstructionLocation(each, i));
}
}
return legacyClassCoverage.getTestsForClass(clazz);
}

private void addTestsToBlockMap(final TestInfo ti, InstructionLocation each) {
Expand All @@ -147,7 +128,7 @@ private void addTestsToBlockMap(final TestInfo ti, InstructionLocation each) {

@Override
public BigInteger getCoverageIdForClass(final ClassName clazz) {
final Map<ClassLine, Set<TestInfo>> coverage = getLineCoverageForClassName(clazz);
final Collection<TestInfo> coverage = getTestsForClass(clazz);
if (coverage.isEmpty()) {
return BigInteger.ZERO;
}
Expand All @@ -167,25 +148,19 @@ private static Function<Entry<InstructionLocation, Set<TestInfo>>, BlockCoverage
@Override
public Collection<ClassInfo> getClassesForFile(final String sourceFile,
String packageName) {
final Collection<ClassInfo> value = classesForFile.get(
keyFromSourceAndPackage(sourceFile, packageName));
if (value == null) {
return Collections.emptyList();
} else {
return value;
}
return legacyClassCoverage.getClassesForFile(sourceFile, packageName);
}

@Override
public CoverageSummary createSummary() {
return new CoverageSummary(numberOfLines(), coveredLines());
}

private BigInteger generateCoverageNumber(
final Map<ClassLine, Set<TestInfo>> coverage) {
private BigInteger generateCoverageNumber(Collection<TestInfo> coverage) {
BigInteger coverageNumber = BigInteger.ZERO;
final Set<ClassName> testClasses = new HashSet<>();
FCollection.flatMapTo(coverage.values(), testsToClassName(), testClasses);
Set<ClassName> testClasses = coverage.stream()
.map(TestInfo.toDefiningClassName())
.collect(Collectors.toSet());

for (final ClassInfo each : this.code.getClassInfo(testClasses)) {
coverageNumber = coverageNumber.add(each.getDeepHash());
Expand All @@ -194,22 +169,6 @@ private BigInteger generateCoverageNumber(
return coverageNumber;
}

private Function<Set<TestInfo>, Iterable<ClassName>> testsToClassName() {
return a -> FCollection.map(a, TestInfo.toDefiningClassName());
}

private static Function<ClassInfo, String> keyFromClassInfo() {

return c -> keyFromSourceAndPackage(c.getSourceFileName(), c.getName()
.getPackage().asJavaName());
}

private static String keyFromSourceAndPackage(final String sourceFile,
final String packageName) {

return packageName + " " + sourceFile;
}

private Collection<ClassName> allClasses() {
return this.code.getCodeUnderTestNames();
}
Expand Down Expand Up @@ -243,79 +202,8 @@ private TestInfo createTestInfo(final Description description,
description.getQualifiedName(), executionTime, testee, linesCovered);
}

private Map<ClassLine, Set<TestInfo>> getLineCoverageForClassName(final ClassName clazz) {
// Use any test that provided some coverage of the class
// This fails to consider tests that only accessed a static variable
// of the class in question as this does not register as coverage.
final Map<ClassLine, Set<TestInfo>> map = this.lineCoverage.get(clazz);
if (map != null) {
return map;
}

return convertInstructionCoverageToLineCoverageForClass(clazz);

}

private Map<ClassLine, Set<TestInfo>> convertInstructionCoverageToLineCoverageForClass(
ClassName clazz) {
final List<Entry<InstructionLocation, Set<TestInfo>>> tests = FCollection.filter(
this.instructionCoverage.entrySet(), isFor(clazz));

final Map<ClassLine, Set<TestInfo>> linesToTests = new LinkedHashMap<>(
0);

for (final Entry<InstructionLocation, Set<TestInfo>> each : tests) {
for (final int line : getLinesForBlock(each.getKey().getBlockLocation())) {
final Set<TestInfo> tis = getLineTestSet(clazz, linesToTests, each, line);
tis.addAll(each.getValue());
}
}

this.lineCoverage.put(clazz, linesToTests);
return linesToTests;
}

private static Set<TestInfo> getLineTestSet(ClassName clazz,
Map<ClassLine, Set<TestInfo>> linesToTests,
Entry<InstructionLocation, Set<TestInfo>> each, int line) {
final ClassLine cl = new ClassLine(clazz, line);
Set<TestInfo> tis = linesToTests.get(cl);
if (tis == null) {
tis = new TreeSet<>(new TestInfoNameComparator());
tis.addAll(each.getValue());
linesToTests.put(new ClassLine(clazz, line), tis);
}
return tis;
}

private Set<Integer> getLinesForBlock(BlockLocation bl) {
Set<Integer> lines = this.blocksToLines.get(bl);
if (lines == null) {
calculateLinesForBlocks(bl.getLocation().getClassName());
lines = this.blocksToLines.get(bl);
if (lines == null) {
lines = Collections.emptySet();
}
}

return lines;
}

private void calculateLinesForBlocks(ClassName className) {
final Map<BlockLocation, Set<Integer>> lines = this.lm.mapLines(className);
this.blocksToLines.putAll(lines);
}

private void recordTestFailure(final Description testDescription) {
this.failingTestDescriptions.add(testDescription);
}

private Function<Entry<InstructionLocation, Set<TestInfo>>, Stream<TestInfo>> toTests() {
return a -> a.getValue().stream();
}

private Predicate<Entry<InstructionLocation, Set<TestInfo>>> isFor(ClassName clazz) {
return a -> a.getKey().isFor(clazz);
}

}

0 comments on commit 32ea625

Please sign in to comment.