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

History performance #891

Merged
merged 6 commits into from
Apr 29, 2021
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
@@ -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);
}

}
Loading