Skip to content

Commit

Permalink
Merge pull request #882 from hcoles/pre_coverage_check
Browse files Browse the repository at this point in the history
check for mutations before running coverage
  • Loading branch information
hcoles authored Apr 19, 2021
2 parents 5962ff1 + e24ff8b commit 19b5029
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 71 deletions.
14 changes: 2 additions & 12 deletions pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private static Function<Entry<InstructionLocation, Set<TestInfo>>, BlockCoverage
@Override
public Collection<ClassInfo> getClassesForFile(final String sourceFile,
String packageName) {
final Collection<ClassInfo> value = this.getClassesForFileCache().get(
final Collection<ClassInfo> value = classesForFile.get(
keyFromSourceAndPackage(sourceFile, packageName));
if (value == null) {
return Collections.emptyList();
Expand All @@ -176,20 +176,11 @@ public Collection<ClassInfo> getClassesForFile(final String sourceFile,
}
}

private Map<String, Collection<ClassInfo>> getClassesForFileCache() {
return this.classesForFile;
}

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

@Override
public Map<InstructionLocation, Set<TestInfo>> getInstructionCoverage() {
return Collections.unmodifiableMap(this.instructionCoverage);
}

private BigInteger generateCoverageNumber(
final Map<ClassLine, Set<TestInfo>> coverage) {
BigInteger coverageNumber = BigInteger.ZERO;
Expand Down Expand Up @@ -323,8 +314,7 @@ private Function<Entry<InstructionLocation, Set<TestInfo>>, Stream<TestInfo>> to
return a -> a.getValue().stream();
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

import java.math.BigInteger;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

public interface CoverageDatabase {

Expand All @@ -26,7 +24,4 @@ public interface CoverageDatabase {

CoverageSummary createSummary();

Map<InstructionLocation, Set<TestInfo>> getInstructionCoverage();


}
51 changes: 51 additions & 0 deletions pitest-entry/src/main/java/org/pitest/coverage/NoCoverage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.pitest.coverage;

import org.pitest.classinfo.ClassInfo;
import org.pitest.classinfo.ClassName;

import java.math.BigInteger;
import java.util.Collection;
import java.util.Collections;

public class NoCoverage implements CoverageDatabase {
@Override
public Collection<ClassInfo> getClassInfo(Collection<ClassName> classes) {
return Collections.emptyList();
}

@Override
public int getNumberOfCoveredLines(Collection<ClassName> clazz) {
return 0;
}

@Override
public Collection<TestInfo> getTestsForClass(ClassName clazz) {
return Collections.emptyList();
}

@Override
public Collection<TestInfo> getTestsForInstructionLocation(InstructionLocation location) {
return Collections.emptyList();
}

@Override
public Collection<TestInfo> getTestsForClassLine(ClassLine classLine) {
return Collections.emptyList();
}

@Override
public BigInteger getCoverageIdForClass(ClassName clazz) {
return BigInteger.ZERO;
}

@Override
public Collection<ClassInfo> getClassesForFile(String sourceFile, String packageName) {
return Collections.emptyList();
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@
*/
package org.pitest.mutationtest.build;

import static java.util.Comparator.comparing;
import org.pitest.classinfo.ClassName;
import org.pitest.coverage.TestInfo;
import org.pitest.functional.FCollection;
import org.pitest.functional.prelude.Prelude;
import org.pitest.mutationtest.DetectionStatus;
import org.pitest.mutationtest.MutationAnalyser;
import org.pitest.mutationtest.MutationResult;
import org.pitest.mutationtest.engine.MutationDetails;

import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -25,14 +32,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.pitest.classinfo.ClassName;
import org.pitest.coverage.TestInfo;
import org.pitest.functional.FCollection;
import org.pitest.functional.prelude.Prelude;
import org.pitest.mutationtest.DetectionStatus;
import org.pitest.mutationtest.MutationAnalyser;
import org.pitest.mutationtest.MutationResult;
import org.pitest.mutationtest.engine.MutationDetails;
import static java.util.Comparator.comparing;

public class MutationTestBuilder {

Expand All @@ -42,8 +42,9 @@ public class MutationTestBuilder {
private final MutationGrouper grouper;

public MutationTestBuilder(final WorkerFactory workerFactory,
final MutationAnalyser analyser, final MutationSource mutationSource,
final MutationGrouper grouper) {
final MutationAnalyser analyser,
final MutationSource mutationSource,
final MutationGrouper grouper) {

this.mutationSource = mutationSource;
this.analyser = analyser;
Expand All @@ -55,8 +56,7 @@ public List<MutationAnalysisUnit> createMutationTestUnits(
final Collection<ClassName> codeClasses) {
final List<MutationAnalysisUnit> tus = new ArrayList<>();

final List<MutationDetails> mutations = FCollection.flatMap(codeClasses,
classToMutations());
final List<MutationDetails> mutations = FCollection.flatMap(codeClasses, mutationSource::createMutations);

mutations.sort(comparing(MutationDetails::getId));

Expand All @@ -65,7 +65,7 @@ public List<MutationAnalysisUnit> createMutationTestUnits(

final Collection<MutationDetails> needAnalysis = analysedMutations.stream()
.filter(statusNotKnown())
.map(resultToDetails())
.map(MutationResult::getDetails)
.collect(Collectors.toList());

final List<MutationResult> analysed = FCollection.filter(analysedMutations,
Expand All @@ -86,9 +86,6 @@ public List<MutationAnalysisUnit> createMutationTestUnits(
return tus;
}

private Function<ClassName, Iterable<MutationDetails>> classToMutations() {
return a -> MutationTestBuilder.this.mutationSource.createMutations(a);
}

private MutationAnalysisUnit makePreAnalysedUnit(
final List<MutationResult> analysed) {
Expand All @@ -105,10 +102,6 @@ private MutationAnalysisUnit makeUnanalysedUnit(
this.workerFactory);
}

private static Function<MutationResult, MutationDetails> resultToDetails() {
return a -> a.getDetails();
}

private static Predicate<MutationResult> statusNotKnown() {
return a -> a.getStatus() == DetectionStatus.NOT_STARTED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.pitest.classpath.CodeSource;
import org.pitest.coverage.CoverageDatabase;
import org.pitest.coverage.CoverageGenerator;
import org.pitest.coverage.CoverageSummary;
import org.pitest.coverage.NoCoverage;
import org.pitest.coverage.TestInfo;
import org.pitest.functional.FCollection;
import org.pitest.help.Help;
Expand All @@ -48,6 +50,8 @@
import org.pitest.mutationtest.incremental.DefaultCodeHistory;
import org.pitest.mutationtest.incremental.HistoryListener;
import org.pitest.mutationtest.incremental.IncrementalAnalyser;
import org.pitest.mutationtest.incremental.NullHistoryStore;
import org.pitest.mutationtest.statistics.MutationStatistics;
import org.pitest.mutationtest.statistics.MutationStatisticsListener;
import org.pitest.mutationtest.statistics.Score;
import org.pitest.util.Log;
Expand All @@ -66,6 +70,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.Collections.emptyList;

public class MutationCoverage {

private static final int MB = 1024 * 1024;
Expand Down Expand Up @@ -112,7 +118,34 @@ public CombinedStatistics runReport() throws IOException {

checkExcludedRunners();

final CoverageDatabase coverageData = coverage().calculateCoverage();
final EngineArguments args = EngineArguments.arguments()
.withExcludedMethods(this.data.getExcludedMethods())
.withMutators(this.data.getMutators());
final MutationEngine engine = this.strategies.factory().createEngine(args);

List<MutationAnalysisUnit> preScanMutations = findMutations(engine, args);
LOG.info("Created " + preScanMutations.size() + " mutation test units in pre scan");

// throw error if configured to do so
checkMutationsFound(preScanMutations);

if (preScanMutations.isEmpty()) {
LOG.info("Skipping coverage and analysis as no mutations found" );
return emptyStatistics();
}

return runAnalysis(runtime, t0, args, engine);

}

private CombinedStatistics emptyStatistics() {
MutationStatistics mutationStatistics = new MutationStatistics(emptyList(),0,0,0,0);
return new CombinedStatistics(mutationStatistics, new CoverageSummary(0,0));
}

private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments args, MutationEngine engine) {
CoverageDatabase coverageData = coverage().calculateCoverage();
HistoryStore history = this.strategies.history();

LOG.fine("Used memory after coverage calculation "
+ ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " mb");
Expand All @@ -121,31 +154,24 @@ public CombinedStatistics runReport() throws IOException {

final MutationStatisticsListener stats = new MutationStatisticsListener();

final EngineArguments args = EngineArguments.arguments()
.withExcludedMethods(this.data.getExcludedMethods())
.withMutators(this.data.getMutators());
final MutationEngine engine = this.strategies.factory().createEngine(args);

final List<MutationResultListener> config = createConfig(t0, coverageData,
stats, engine);

history().initialize();
history.initialize();

this.timings.registerStart(Timings.Stage.BUILD_MUTATION_TESTS);
final List<MutationAnalysisUnit> tus = buildMutationTests(coverageData,
engine, args);
final List<MutationAnalysisUnit> tus = buildMutationTests(coverageData, history,
engine, args);
this.timings.registerEnd(Timings.Stage.BUILD_MUTATION_TESTS);

LOG.info("Created " + tus.size() + " mutation test units");
checkMutationsFound(tus);

recordClassPath(coverageData);
recordClassPath(history, coverageData);

LOG.fine("Used memory before analysis start "
+ ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " mb");
LOG.fine("Free Memory before analysis start " + (runtime.freeMemory() / MB)
+ " mb");

final List<MutationResultListener> config = createConfig(t0, coverageData, history,
stats, engine);
final MutationAnalysisExecutor mae = new MutationAnalysisExecutor(
numberOfThreads(), config);
this.timings.registerStart(Timings.Stage.RUN_MUTATION_TESTS);
Expand All @@ -158,9 +184,22 @@ public CombinedStatistics runReport() throws IOException {

return new CombinedStatistics(stats.getStatistics(),
coverageData.createSummary());
}

private List<MutationAnalysisUnit> findMutations(MutationEngine engine, EngineArguments args) {
// Run mutant discovery without coverage data or history.
// Ideally we'd ony discover mutants once, but the process is currently tightly
// entangled with coverage data. Generating coverage data is expensive for
// some projects, while discovery usually takes less than 1 second. By doing
// an initial run here we are able to skip coverage generation when no mutants
// are found, e.g if pitest is being run against diffs.
this.timings.registerStart(Timings.Stage.MUTATION_PRE_SCAN);
List<MutationAnalysisUnit> mutants = buildMutationTests(new NoCoverage(), new NullHistoryStore(), engine, args);
this.timings.registerEnd(Timings.Stage.MUTATION_PRE_SCAN);
return mutants;
}


private void checkExcludedRunners() {
final Collection<String> excludedRunners = this.data.getExcludedRunners();
if (!excludedRunners.isEmpty()) {
Expand All @@ -178,9 +217,11 @@ private int numberOfThreads() {
return Math.max(1, this.data.getNumberOfThreads());
}

private List<MutationResultListener> createConfig(final long t0,
final CoverageDatabase coverageData,
final MutationStatisticsListener stats, final MutationEngine engine) {
private List<MutationResultListener> createConfig(long t0,
CoverageDatabase coverageData,
HistoryStore history,
MutationStatisticsListener stats,
MutationEngine engine) {
final List<MutationResultListener> ls = new ArrayList<>();

ls.add(stats);
Expand All @@ -193,19 +234,19 @@ private List<MutationResultListener> createConfig(final long t0,
.listenerFactory().getListener(this.data.getFreeFormProperties(), args);

ls.add(mutationReportListener);
ls.add(new HistoryListener(history()));
ls.add(new HistoryListener(history));

if (!this.data.isVerbose()) {
ls.add(new SpinnerListener(System.out));
}
return ls;
}

private void recordClassPath(final CoverageDatabase coverageData) {
private void recordClassPath(HistoryStore history, CoverageDatabase coverageData) {
final Set<ClassName> allClassNames = getAllClassesAndTests(coverageData);
final Collection<HierarchicalClassId> ids = FCollection.map(
this.code.getClassInfo(allClassNames), ClassInfo.toFullClassId());
history().recordClassPath(ids, coverageData);
history.recordClassPath(ids, coverageData);
}

private Set<ClassName> getAllClassesAndTests(
Expand Down Expand Up @@ -245,8 +286,10 @@ private void printStats(final MutationStatisticsListener stats) {
stats.getStatistics().report(ps);
}

private List<MutationAnalysisUnit> buildMutationTests(
final CoverageDatabase coverageData, final MutationEngine engine, EngineArguments args) {
private List<MutationAnalysisUnit> buildMutationTests(CoverageDatabase coverageData,
HistoryStore history,
MutationEngine engine,
EngineArguments args) {

final MutationConfig mutationConfig = new MutationConfig(engine, coverage()
.getLaunchOptions());
Expand All @@ -264,7 +307,7 @@ private List<MutationAnalysisUnit> buildMutationTests(
final MutationSource source = new MutationSource(mutationConfig, testPrioritiser, bas, interceptor);

final MutationAnalyser analyser = new IncrementalAnalyser(
new DefaultCodeHistory(this.code, history()), coverageData);
new DefaultCodeHistory(this.code, history), coverageData);

final WorkerFactory wf = new WorkerFactory(this.baseDir, coverage()
.getConfiguration(), mutationConfig, args,
Expand Down Expand Up @@ -299,10 +342,6 @@ private CoverageGenerator coverage() {
return this.strategies.coverage();
}

private HistoryStore history() {
return this.strategies.history();
}

// For reasons not yet understood classes from rt.jar are not resolved for some
// projects during static analysis phase. For now fall back to the classloader when
// a class not provided by project classpath
Expand Down
Loading

0 comments on commit 19b5029

Please sign in to comment.