diff --git a/pitest-aggregator/src/test/java/org/pitest/aggregate/CodeSourceAggregatorTest.java b/pitest-aggregator/src/test/java/org/pitest/aggregate/CodeSourceAggregatorTest.java index 36a4fc160..2e038ad54 100644 --- a/pitest-aggregator/src/test/java/org/pitest/aggregate/CodeSourceAggregatorTest.java +++ b/pitest-aggregator/src/test/java/org/pitest/aggregate/CodeSourceAggregatorTest.java @@ -35,8 +35,8 @@ public void testCreateCodeSource() { final CodeSource source = this.underTest.createCodeSource(); assertNotNull(source); - assertTrue(source.fetchClass(ClassName.fromClass(CodeSourceAggregator.class)).isPresent()); - assertFalse(source.fetchClass(ClassName.fromString("com.doesnt.exist.Type")).isPresent()); + assertTrue(source.fetchClassHash(ClassName.fromClass(CodeSourceAggregator.class)).isPresent()); + assertFalse(source.fetchClassHash(ClassName.fromString("com.doesnt.exist.Type")).isPresent()); } } diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/ClassHash.java b/pitest-entry/src/main/java/org/pitest/classinfo/ClassHash.java new file mode 100644 index 000000000..474278429 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/classinfo/ClassHash.java @@ -0,0 +1,15 @@ +package org.pitest.classinfo; + +import java.math.BigInteger; + +public interface ClassHash { + + ClassIdentifier getId(); + + ClassName getName(); + + BigInteger getDeepHash(); + + HierarchicalClassId getHierarchicalId(); + +} diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/ClassHashSource.java b/pitest-entry/src/main/java/org/pitest/classinfo/ClassHashSource.java new file mode 100644 index 000000000..5355c906a --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/classinfo/ClassHashSource.java @@ -0,0 +1,7 @@ +package org.pitest.classinfo; + +import java.util.Optional; + +public interface ClassHashSource { + Optional fetchClassHash(ClassName name); +} diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfo.java b/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfo.java index 19323c70c..5ec45fbf7 100644 --- a/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfo.java +++ b/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfo.java @@ -22,17 +22,22 @@ * a class between runs are tracked by calculating the hash of its bytecode and the * bytecode of classes it has a strong relationship to. */ -public final class ClassInfo { +public final class ClassInfo implements ClassHash { private final ClassIdentifier id; private final ClassPointer outerClass; private final ClassPointer superClass; public ClassInfo(ClassPointer superClass, ClassPointer outerClass, ClassInfoBuilder builder) { + this(superClass, outerClass, builder.id); + } + + public ClassInfo(ClassPointer superClass, ClassPointer outerClass, ClassIdentifier id) { this.superClass = superClass; this.outerClass = outerClass; - this.id = builder.id; + this.id = id; } + public ClassIdentifier getId() { return this.id; } diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfoSource.java b/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfoSource.java deleted file mode 100644 index 6c225c49d..000000000 --- a/pitest-entry/src/main/java/org/pitest/classinfo/ClassInfoSource.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pitest.classinfo; - -import java.util.Optional; - -public interface ClassInfoSource { - Optional fetchClass(ClassName name); -} diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/NameToClassInfo.java b/pitest-entry/src/main/java/org/pitest/classinfo/NameToClassInfo.java deleted file mode 100644 index 89e6da00c..000000000 --- a/pitest-entry/src/main/java/org/pitest/classinfo/NameToClassInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.pitest.classinfo; - -import java.util.function.Function; - -import java.util.Optional; - -public class NameToClassInfo implements Function> { - - private final ClassInfoSource repository; - - public NameToClassInfo(final ClassInfoSource repository) { - this.repository = repository; - } - - @Override - public Optional apply(final ClassName a) { - return this.repository.fetchClass(a); - } - -} \ No newline at end of file diff --git a/pitest-entry/src/main/java/org/pitest/classinfo/Repository.java b/pitest-entry/src/main/java/org/pitest/classinfo/Repository.java index faf07cf7c..1f0bb0667 100644 --- a/pitest-entry/src/main/java/org/pitest/classinfo/Repository.java +++ b/pitest-entry/src/main/java/org/pitest/classinfo/Repository.java @@ -21,7 +21,7 @@ import java.util.Optional; -public class Repository implements ClassInfoSource { +public class Repository implements ClassHashSource { private final HashFunction hashFunction; private final Map knownClasses = new HashMap<>(); @@ -41,17 +41,18 @@ public boolean hasClass(final ClassName name) { return this.knownClasses.containsKey(name) || querySource(name).isPresent(); } - public Optional fetchClass(final Class clazz) { // NO_UCD (test - // only) - return fetchClass(clazz.getName()); + @Override + public Optional fetchClassHash(final ClassName clazz) { + return this.fetchClass(clazz) + .map(ClassHash.class::cast); } - private Optional fetchClass(final String name) { - return fetchClass(ClassName.fromString(name)); + Optional fetchClass(final Class clazz) { // NO_UCD (test + // only) + return fetchClass(ClassName.fromClass(clazz)); } - @Override - public Optional fetchClass(final ClassName name) { + Optional fetchClass(final ClassName name) { final ClassInfo info = this.knownClasses.get(name); if (info != null) { return Optional.ofNullable(info); diff --git a/pitest-entry/src/main/java/org/pitest/classpath/CodeSource.java b/pitest-entry/src/main/java/org/pitest/classpath/CodeSource.java index 74d0f0a5c..6bece2ae4 100644 --- a/pitest-entry/src/main/java/org/pitest/classpath/CodeSource.java +++ b/pitest-entry/src/main/java/org/pitest/classpath/CodeSource.java @@ -1,25 +1,35 @@ package org.pitest.classpath; import java.util.Collection; +import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.classinfo.ClassByteArraySource; -import org.pitest.classinfo.ClassInfo; -import org.pitest.classinfo.ClassInfoSource; +import org.pitest.classinfo.ClassHash; +import org.pitest.classinfo.ClassHashSource; import org.pitest.classinfo.ClassName; /** * Provides access to code and tests on the classpath */ -public interface CodeSource extends ClassInfoSource, ClassByteArraySource { +public interface CodeSource extends ClassHashSource, ClassByteArraySource { Stream codeTrees(); + default Set getAllClassAndTestNames() { + final Set names = new HashSet<>(); + names.addAll(getCodeUnderTestNames()); + names.addAll(getTestClassNames()); + return names; + } + Set getCodeUnderTestNames(); + Set getTestClassNames(); + Stream testTrees(); ClassPath getClassPath(); @@ -28,10 +38,11 @@ public interface CodeSource extends ClassInfoSource, ClassByteArraySource { Optional fetchClassBytes(ClassName clazz); - @Override - Optional fetchClass(ClassName clazz); - Collection getClassInfo(Collection classes); + Optional fetchClassHash(ClassName clazz); + + Collection fetchClassHashes(Collection classes); @Override Optional getBytes(String clazz); + } diff --git a/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java b/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java index 62ac04835..c9fdc61b6 100644 --- a/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java +++ b/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java @@ -1,9 +1,8 @@ package org.pitest.classpath; import org.pitest.bytecode.analysis.ClassTree; -import org.pitest.classinfo.ClassInfo; +import org.pitest.classinfo.ClassHash; import org.pitest.classinfo.ClassName; -import org.pitest.classinfo.NameToClassInfo; import org.pitest.classinfo.Repository; import org.pitest.classinfo.TestToClassMapper; import org.pitest.functional.Streams; @@ -11,7 +10,6 @@ import java.util.Collection; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,6 +39,10 @@ public Set getCodeUnderTestNames() { return this.classPath.code().stream().collect(Collectors.toSet()); } + public Set getTestClassNames() { + return this.classPath.test().stream().collect(Collectors.toSet()); + } + public Stream testTrees() { return this.classPath.test().stream() .map(c -> this.getBytes(c.asJavaName())) @@ -58,9 +60,9 @@ public Optional findTestee(final String className) { return mapper.findTestee(className); } - public Collection getClassInfo(final Collection classes) { + public Collection fetchClassHashes(final Collection classes) { return classes.stream() - .flatMap(nameToClassInfo()) + .flatMap(c -> Streams.fromOptional(classRepository.fetchClassHash(c))) .collect(Collectors.toList()); } @@ -69,13 +71,8 @@ public Optional fetchClassBytes(final ClassName clazz) { } @Override - public Optional fetchClass(final ClassName clazz) { - return this.classRepository.fetchClass(clazz); - } - - private Function> nameToClassInfo() { - return new NameToClassInfo(this.classRepository) - .andThen(Streams::fromOptional); + public Optional fetchClassHash(final ClassName clazz) { + return this.classRepository.fetchClassHash(clazz); } @Override diff --git a/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java b/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java index 6c974a96d..25cfbaff7 100644 --- a/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java +++ b/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java @@ -15,7 +15,7 @@ package org.pitest.coverage; -import org.pitest.classinfo.ClassInfo; +import org.pitest.classinfo.ClassHash; import org.pitest.classinfo.ClassName; import org.pitest.classpath.CodeSource; import org.pitest.testapi.Description; @@ -147,7 +147,7 @@ private BigInteger generateCoverageNumber(Collection coverage) { .map(TestInfo.toDefiningClassName()) .collect(Collectors.toSet()); - for (final ClassInfo each : this.code.getClassInfo(testClasses)) { + for (final ClassHash each : this.code.fetchClassHashes(testClasses)) { coverageNumber = coverageNumber.add(each.getDeepHash()); } diff --git a/pitest-entry/src/main/java/org/pitest/coverage/CoverageGenerator.java b/pitest-entry/src/main/java/org/pitest/coverage/CoverageGenerator.java index f87c79554..908e959d8 100644 --- a/pitest-entry/src/main/java/org/pitest/coverage/CoverageGenerator.java +++ b/pitest-entry/src/main/java/org/pitest/coverage/CoverageGenerator.java @@ -15,12 +15,15 @@ package org.pitest.coverage; +import org.pitest.classinfo.ClassName; import org.pitest.mutationtest.config.TestPluginArguments; import org.pitest.process.LaunchOptions; +import java.util.function.Predicate; + public interface CoverageGenerator { - CoverageDatabase calculateCoverage(); + CoverageDatabase calculateCoverage(Predicate testFilter); TestPluginArguments getConfiguration(); diff --git a/pitest-entry/src/main/java/org/pitest/coverage/execute/DefaultCoverageGenerator.java b/pitest-entry/src/main/java/org/pitest/coverage/execute/DefaultCoverageGenerator.java index fc4dc2d8b..14f505656 100644 --- a/pitest-entry/src/main/java/org/pitest/coverage/execute/DefaultCoverageGenerator.java +++ b/pitest-entry/src/main/java/org/pitest/coverage/execute/DefaultCoverageGenerator.java @@ -16,7 +16,6 @@ package org.pitest.coverage.execute; import org.pitest.bytecode.analysis.ClassTree; -import org.pitest.classinfo.ClassInfo; import org.pitest.classinfo.ClassName; import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageData; @@ -45,7 +44,7 @@ import java.net.ServerSocket; import java.util.List; import java.util.function.Consumer; -import java.util.function.Function; +import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -77,13 +76,14 @@ public DefaultCoverageGenerator(final File workingDir, } @Override - public CoverageData calculateCoverage() { + public CoverageData calculateCoverage(Predicate testFilter) { try { final long t0 = System.nanoTime(); this.timings.registerStart(Timings.Stage.SCAN_CLASS_PATH); List tests = this.code.testTrees() .map(ClassTree::name) + .filter(testFilter) .map(ClassName::asInternalName) .collect(Collectors.toList()); @@ -93,7 +93,13 @@ public CoverageData calculateCoverage() { this.code)); this.timings.registerStart(Timings.Stage.COVERAGE); - gatherCoverageData(tests, coverage); + if (tests.isEmpty()) { + // This may happen as a result of filtering for incremental analysis as well as + // simple misconfiguration. + LOG.info("No test classes identified to scan"); + } else { + gatherCoverageData(tests, coverage); + } this.timings.registerEnd(Timings.Stage.COVERAGE); final long time = NANOSECONDS.toSeconds(System.nanoTime() - t0); @@ -154,10 +160,6 @@ private void gatherCoverageData(List tests, } } - private static Function classInfoToName() { - return a -> a.getName().asInternalName(); - } - private Consumer captureStandardOutIfVerbose() { if (this.verbosity.showMinionOutput()) { return log(); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/ClassHistory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/ClassHistory.java index 99e612285..c30b632b4 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/ClassHistory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/ClassHistory.java @@ -2,6 +2,7 @@ import java.io.Serializable; import java.util.Objects; +import java.util.StringJoiner; import org.pitest.classinfo.ClassName; import org.pitest.classinfo.HierarchicalClassId; @@ -47,4 +48,12 @@ public boolean equals(final Object obj) { return Objects.equals(id, other.id) && Objects.equals(coverageId, other.coverageId); } + + @Override + public String toString() { + return new StringJoiner(", ", ClassHistory.class.getSimpleName() + "[", "]") + .add("id=" + id) + .add("coverageId='" + coverageId + "'") + .toString(); + } } \ No newline at end of file diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/CompoundMutationResultInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/CompoundMutationResultInterceptor.java index a2172880f..004316678 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/CompoundMutationResultInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/CompoundMutationResultInterceptor.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -9,7 +10,14 @@ public class CompoundMutationResultInterceptor implements MutationResultIntercep private final List interceptors; public CompoundMutationResultInterceptor(List interceptors) { - this.interceptors = interceptors; + this.interceptors = interceptors.stream() + .sorted(Comparator.comparing(MutationResultInterceptor::priority)) + .collect(Collectors.toList()); + } + + public CompoundMutationResultInterceptor add(MutationResultInterceptor extra) { + interceptors.add(0, extra); + return this; } @Override diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/History.java b/pitest-entry/src/main/java/org/pitest/mutationtest/History.java new file mode 100644 index 000000000..7b52dc89a --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/History.java @@ -0,0 +1,24 @@ +package org.pitest.mutationtest; + +import org.pitest.classinfo.ClassName; +import org.pitest.coverage.CoverageDatabase; +import org.pitest.mutationtest.engine.MutationDetails; + +import java.util.List; +import java.util.function.Predicate; + +public interface History { + + void initialize(); + default Predicate limitTests() { + return c -> true; + } + void processCoverage(CoverageDatabase coverageData); + + List analyse(List mutationsForClasses); + + void recordResult(MutationResult result); + + void close(); + +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryFactory.java new file mode 100644 index 000000000..a1e10988b --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryFactory.java @@ -0,0 +1,12 @@ +package org.pitest.mutationtest; + +import org.pitest.mutationtest.incremental.WriterFactory; +import org.pitest.plugin.ProvidesFeature; +import org.pitest.plugin.ToolClasspathPlugin; + +import java.io.Reader; +import java.util.Optional; + +public interface HistoryFactory extends ToolClasspathPlugin, ProvidesFeature { + History makeHistory(HistoryParams params, WriterFactory output, Optional input); +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryParams.java b/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryParams.java new file mode 100644 index 000000000..a65117c3f --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryParams.java @@ -0,0 +1,21 @@ +package org.pitest.mutationtest; + +import org.pitest.classpath.CodeSource; +import org.pitest.plugin.FeatureSelector; +public class HistoryParams { + private final FeatureSelector conf; + private final CodeSource code; + + public HistoryParams(FeatureSelector conf, CodeSource code) { + this.conf = conf; + this.code = code; + } + + public FeatureSelector featureSettings() { + return conf; + } + + public CodeSource code() { + return code; + } +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryStore.java b/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryStore.java deleted file mode 100644 index 0f2c0605c..000000000 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/HistoryStore.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.pitest.mutationtest; - -import java.util.Collection; -import java.util.Map; - -import org.pitest.classinfo.ClassName; -import org.pitest.classinfo.HierarchicalClassId; -import org.pitest.coverage.CoverageDatabase; -import org.pitest.mutationtest.engine.MutationIdentifier; - -public interface HistoryStore { - - void initialize(); - - void recordClassPath(Collection ids, CoverageDatabase coverageInfo); - - void recordResult(MutationResult result); - - Map getHistoricResults(); - - Map getHistoricClassPath(); - -} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/MutationAnalyser.java b/pitest-entry/src/main/java/org/pitest/mutationtest/MutationAnalyser.java index 35c4e19f6..9ca561be3 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/MutationAnalyser.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/MutationAnalyser.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest; import java.util.Collection; +import java.util.List; import org.pitest.mutationtest.engine.MutationDetails; @@ -9,7 +10,6 @@ */ public interface MutationAnalyser { - Collection analyse( - Collection mutationsForClasses); + List analyse(Collection mutationsForClasses); } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultInterceptor.java index 5896f3b16..907d2a3c4 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/MutationResultInterceptor.java @@ -29,4 +29,8 @@ default Collection remaining() { default String description() { return ""; } + + default int priority() { + return 10; + } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationTestBuilder.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationTestBuilder.java index 5d77b306f..7ec3e9427 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationTestBuilder.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationTestBuilder.java @@ -15,17 +15,16 @@ package org.pitest.mutationtest.build; import org.pitest.classinfo.ClassName; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.DetectionStatus; -import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationResult; import org.pitest.mutationtest.engine.MutationDetails; +import org.pitest.mutationtest.engine.MutationIdentifier; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.function.Predicate; +import java.util.Set; import java.util.stream.Collectors; import static java.util.Comparator.comparing; @@ -33,12 +32,12 @@ public class MutationTestBuilder { private final MutationSource mutationSource; - private final MutationAnalyser analyser; + private final History analyser; private final WorkerFactory workerFactory; private final MutationGrouper grouper; public MutationTestBuilder(final WorkerFactory workerFactory, - final MutationAnalyser analyser, + final History analyser, final MutationSource mutationSource, final MutationGrouper grouper) { @@ -58,24 +57,21 @@ public List createMutationTestUnits( mutations.sort(comparing(MutationDetails::getId)); - final Collection analysedMutations = this.analyser - .analyse(mutations); + List analysisUnits = this.analyser.analyse(mutations); - final Collection needAnalysis = analysedMutations.stream() - .filter(statusNotKnown()) - .map(MutationResult::getDetails) - .collect(Collectors.toList()); + Collection needProcessing = filterAlreadyAnalysedMutations(mutations, analysisUnits); - final List analysed = FCollection.filter(analysedMutations, - Prelude.not(statusNotKnown())); + List analysedMutations = analysisUnits.stream() + .filter(r -> r.getStatus() != DetectionStatus.NOT_STARTED) + .collect(Collectors.toList()); - if (!analysed.isEmpty()) { - tus.add(makePreAnalysedUnit(analysed)); + if (!analysedMutations.isEmpty()) { + tus.add(makePreAnalysedUnit(analysedMutations)); } - if (!needAnalysis.isEmpty()) { + if (!needProcessing.isEmpty()) { for (final Collection ms : this.grouper.groupMutations( - codeClasses, needAnalysis)) { + codeClasses, needProcessing)) { tus.add(makeUnanalysedUnit(ms)); } } @@ -84,6 +80,28 @@ public List createMutationTestUnits( return tus; } + private static Collection filterAlreadyAnalysedMutations(List mutations, Collection analysedMutations) { + final Set alreadyAnalysed = analysedMutations.stream() + .map(mr -> mr.getDetails().getId()) + .collect(Collectors.toSet()); + + final Collection needAnalysis = mutations.stream() + .filter(m -> !alreadyAnalysed.contains(m.getId())) + .collect(Collectors.toList()); + + // If we've prioritised a test, the mutations will be returned with a status of not started. + // The mutation returned will however have a modified test order so should be used in + // place of the original + final Collection haveBeenAltered = analysedMutations.stream() + .filter(m -> m.getStatus() == DetectionStatus.NOT_STARTED) + .map(r -> r.getDetails()) + .collect(Collectors.toList()); + + needAnalysis.addAll(haveBeenAltered); + + return needAnalysis; + } + private MutationAnalysisUnit makePreAnalysedUnit( final List analysed) { @@ -95,8 +113,4 @@ private MutationAnalysisUnit makeUnanalysedUnit( return new MutationTestUnit(needAnalysis, this.workerFactory); } - private static Predicate statusNotKnown() { - return a -> a.getStatus() == DetectionStatus.NOT_STARTED; - } - } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java index de35bb627..f6a7c5533 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest.config; import org.pitest.classpath.CodeSourceFactory; +import org.pitest.mutationtest.HistoryFactory; import org.pitest.mutationtest.build.CoverageTransformerFactory; import org.pitest.mutationtest.MutationEngineFactory; import org.pitest.mutationtest.MutationResultInterceptor; @@ -57,6 +58,7 @@ public Collection findToolClasspathPlugins() { l.addAll(findCoverageTransformers()); l.addAll(findVerifiers()); l.addAll(findCodeSources()); + l.addAll(findHistory()); return l; } @@ -127,6 +129,10 @@ public List findCodeSources() { return new ArrayList<>(load(CodeSourceFactory.class)); } + public List findHistory() { + return new ArrayList<>(load(HistoryFactory.class)); + } + public Collection findFeatures() { return findToolClasspathPlugins().stream() .filter(p -> p instanceof ProvidesFeature) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java index 56915eb72..c6042dcab 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java @@ -5,6 +5,7 @@ import org.pitest.classpath.DefaultCodeSource; import org.pitest.classpath.ProjectClassPaths; import org.pitest.coverage.CoverageExporter; +import org.pitest.mutationtest.HistoryFactory; import org.pitest.mutationtest.build.CoverageTransformer; import org.pitest.mutationtest.build.CoverageTransformerFactory; import org.pitest.coverage.execute.CoverageOptions; @@ -13,7 +14,6 @@ import org.pitest.functional.FCollection; import org.pitest.mutationtest.CompoundMutationResultInterceptor; import org.pitest.mutationtest.MutationEngineFactory; -import org.pitest.mutationtest.MutationResultInterceptor; import org.pitest.mutationtest.MutationResultListenerFactory; import org.pitest.mutationtest.build.CompoundInterceptorFactory; import org.pitest.mutationtest.build.DefaultMutationGrouperFactory; @@ -21,6 +21,7 @@ import org.pitest.mutationtest.build.MutationGrouperFactory; import org.pitest.mutationtest.build.MutationInterceptorFactory; import org.pitest.mutationtest.build.TestPrioritiserFactory; +import org.pitest.mutationtest.incremental.DefaultHistoryFactory; import org.pitest.mutationtest.verify.BuildVerifierFactory; import org.pitest.mutationtest.verify.CompoundBuildVerifierFactory; import org.pitest.plugin.Feature; @@ -110,11 +111,27 @@ public CodeSource createCodeSource(ProjectClassPaths classPath) { return new DefaultCodeSource(classPath); } if (sources.size() > 1) { - throw new RuntimeException("More than CodeSource found on classpath."); + throw new RuntimeException("More than one CodeSource found on classpath."); } return sources.get(0).createCodeSource(classPath); } + public HistoryFactory createHistory() { + List available = this.plugins.findHistory(); + + final FeatureParser parser = new FeatureParser(); + FeatureSelector historyFeatures = new FeatureSelector<>(parser.parseFeatures(this.options.getFeatures()), available); + List enabledHistory = historyFeatures.getActiveFeatures(); + + if (enabledHistory.isEmpty()) { + return new DefaultHistoryFactory(); + } + if (enabledHistory.size() > 1) { + throw new RuntimeException("More than one HistoryFactory enabled."); + } + return enabledHistory.get(0); + } + public void describeFeatures(Consumer enabled, Consumer disabled) { final FeatureParser parser = new FeatureParser(); final Collection available = new ArrayList<>(this.plugins.findFeatures()); @@ -177,7 +194,7 @@ public BuildVerifierFactory createVerifier() { return new CompoundBuildVerifierFactory(this.plugins.findVerifiers()); } - public MutationResultInterceptor getResultInterceptor() { + public CompoundMutationResultInterceptor getResultInterceptor() { return new CompoundMutationResultInterceptor(this.plugins.findMutationResultInterceptor()); } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/CodeHistory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/CodeHistory.java index af75cb5f7..cb8bdac69 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/CodeHistory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/CodeHistory.java @@ -1,18 +1,50 @@ package org.pitest.mutationtest.incremental; import java.math.BigInteger; +import java.util.Map; +import org.pitest.classinfo.ClassHash; +import org.pitest.classinfo.ClassHashSource; import org.pitest.classinfo.ClassName; import java.util.Optional; +import org.pitest.mutationtest.ClassHistory; import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.engine.MutationIdentifier; -public interface CodeHistory { +class CodeHistory { - Optional getPreviousResult(MutationIdentifier id); + private final ClassHashSource code; + private final Map previousResults; + private final Map previousClassPath; - boolean hasClassChanged(ClassName className); + CodeHistory(final ClassHashSource code, + final Map previousResults, + final Map previousClassPath) { + this.code = code; + this.previousResults = previousResults; + this.previousClassPath = previousClassPath; + } - boolean hasCoverageChanged(ClassName className, BigInteger currentCoverage); + public Optional getPreviousResult( + final MutationIdentifier id) { + return Optional.ofNullable(this.previousResults.get(id)); + } + + public boolean hasClassChanged(final ClassName className) { + final ClassHistory historic = this.previousClassPath.get(className); + if (historic == null) { + return true; + } + + final Optional current = this.code.fetchClassHash(className); + return !current.get().getHierarchicalId().equals(historic.getId()); + + } + + public boolean hasCoverageChanged(final ClassName className, + final BigInteger currentCoverage) { + return !this.previousClassPath.get(className).getCoverageId() + .equals(currentCoverage.toString(16)); + } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultCodeHistory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultCodeHistory.java deleted file mode 100644 index 2790683c0..000000000 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultCodeHistory.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.pitest.mutationtest.incremental; - -import java.math.BigInteger; -import java.util.Map; - -import org.pitest.classinfo.ClassInfo; -import org.pitest.classinfo.ClassInfoSource; -import org.pitest.classinfo.ClassName; -import org.pitest.classpath.CodeSource; -import java.util.Optional; -import org.pitest.mutationtest.ClassHistory; -import org.pitest.mutationtest.HistoryStore; -import org.pitest.mutationtest.MutationStatusTestPair; -import org.pitest.mutationtest.engine.MutationIdentifier; - -public class DefaultCodeHistory implements CodeHistory { - - private final ClassInfoSource code; - private final Map previousResults; - private final Map previousClassPath; - - public DefaultCodeHistory(final CodeSource code, - final HistoryStore historyStore) { - this(code, historyStore.getHistoricResults(), historyStore - .getHistoricClassPath()); - } - - public DefaultCodeHistory(final ClassInfoSource code, - final Map previousResults, - final Map previousClassPath) { - this.code = code; - this.previousResults = previousResults; - this.previousClassPath = previousClassPath; - } - - @Override - public Optional getPreviousResult( - final MutationIdentifier id) { - return Optional.ofNullable(this.previousResults.get(id)); - } - - @Override - public boolean hasClassChanged(final ClassName className) { - final ClassHistory historic = this.previousClassPath.get(className); - if (historic == null) { - return true; - } - - final Optional current = this.code.fetchClass(className); - return !current.get().getHierarchicalId().equals(historic.getId()); - - } - - @Override - public boolean hasCoverageChanged(final ClassName className, - final BigInteger currentCoverage) { - return !this.previousClassPath.get(className).getCoverageId() - .equals(currentCoverage.toString(16)); - } - -} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultHistoryFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultHistoryFactory.java new file mode 100644 index 000000000..ec8d8127b --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/DefaultHistoryFactory.java @@ -0,0 +1,29 @@ +package org.pitest.mutationtest.incremental; + +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.HistoryFactory; +import org.pitest.mutationtest.HistoryParams; +import org.pitest.plugin.Feature; + +import java.io.Reader; +import java.util.Optional; + +public class DefaultHistoryFactory implements HistoryFactory { + @Override + public History makeHistory(HistoryParams params, WriterFactory output, Optional input) { + return new ObjectOutputStreamHistory(params.code(), output, input); + } + + @Override + public String description() { + return "Default history"; + } + + @Override + public Feature provides() { + return Feature.named("default_history") + .withOnByDefault(true) + .asInternalFeature() + .withDescription(description()); + } +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryListener.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryListener.java index c14790658..c6d6a03d1 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryListener.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryListener.java @@ -1,15 +1,14 @@ package org.pitest.mutationtest.incremental; import org.pitest.mutationtest.ClassMutationResults; -import org.pitest.mutationtest.HistoryStore; -import org.pitest.mutationtest.MutationResult; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.MutationResultListener; public class HistoryListener implements MutationResultListener { - private final HistoryStore historyStore; + private final History historyStore; - public HistoryListener(final HistoryStore historyStore) { + public HistoryListener(final History historyStore) { this.historyStore = historyStore; } @@ -20,15 +19,12 @@ public void runStart() { @Override public void handleMutationResult(final ClassMutationResults metaData) { - for (final MutationResult each : metaData.getMutations()) { - this.historyStore.recordResult(each); - } - + // results are collected in an interceptor to ensure we have unmodified mutants } @Override public void runEnd() { - + this.historyStore.close(); } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryResultInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryResultInterceptor.java new file mode 100644 index 000000000..74e0c4af8 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/HistoryResultInterceptor.java @@ -0,0 +1,33 @@ +package org.pitest.mutationtest.incremental; + +import org.pitest.mutationtest.ClassMutationResults; +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.MutationResultInterceptor; + +import java.util.Collection; + +/** + * Records results for history before other interceptors are applied. Artificially + * added via hacky hard coding. + */ +public class HistoryResultInterceptor implements MutationResultInterceptor { + + private final History history; + + public HistoryResultInterceptor(History historyStore) { + this.history = historyStore; + } + + @Override + public Collection modify(Collection results) { + results.stream() + .flatMap(c -> c.getMutations().stream()) + .forEach(this.history::recordResult); + return results; + } + + @Override + public int priority() { + return 0; + } +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/IncrementalAnalyser.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/IncrementalAnalyser.java index 3b13ebd2e..87f6ca191 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/IncrementalAnalyser.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/IncrementalAnalyser.java @@ -22,7 +22,7 @@ import org.pitest.mutationtest.engine.MutationDetails; import org.pitest.util.Log; -public class IncrementalAnalyser implements MutationAnalyser { +class IncrementalAnalyser implements MutationAnalyser { private static final Logger LOG = Log.getLogger(); @@ -30,36 +30,26 @@ public class IncrementalAnalyser implements MutationAnalyser { private final CoverageDatabase coverage; private final Map preAnalysed = new EnumMap<>(DetectionStatus.class); - private final boolean enableLog; - - public IncrementalAnalyser(CodeHistory history, CoverageDatabase coverage) { - this(history, coverage, true); - } - - public IncrementalAnalyser(CodeHistory history, CoverageDatabase coverage, boolean enableLog) { + IncrementalAnalyser(CodeHistory history, CoverageDatabase coverage) { this.history = history; this.coverage = coverage; - this.enableLog = enableLog; } @Override - public Collection analyse(Collection mutation) { + public List analyse(Collection mutation) { final List mrs = new ArrayList<>( mutation.size()); for (final MutationDetails each : mutation) { final Optional maybeResult = this.history .getPreviousResult(each.getId()); - if (!maybeResult.isPresent()) { - mrs.add(analyseFromScratch(each)); - } else { + // discard the mutant if no existing result + if (maybeResult.isPresent()) { mrs.add(analyseFromHistory(each, maybeResult.get())); } } - if (enableLog) { - logTotals(); - } + logTotals(); return mrs; diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistory.java new file mode 100644 index 000000000..be9f74499 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistory.java @@ -0,0 +1,37 @@ +package org.pitest.mutationtest.incremental; + +import java.util.Collections; +import java.util.List; + +import org.pitest.coverage.CoverageDatabase; +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.MutationResult; +import org.pitest.mutationtest.engine.MutationDetails; +public class NullHistory implements History { + + @Override + public void initialize() { + + } + + @Override + public void processCoverage(CoverageDatabase coverageData) { + + } + + @Override + public void recordResult(final MutationResult result) { + + } + + @Override + public List analyse(List mutationsForClasses) { + return Collections.emptyList(); + } + + @Override + public void close() { + + } + +} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistoryStore.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistoryStore.java deleted file mode 100644 index 83b3a265a..000000000 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/NullHistoryStore.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.pitest.mutationtest.incremental; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import org.pitest.classinfo.ClassName; -import org.pitest.classinfo.HierarchicalClassId; -import org.pitest.coverage.CoverageDatabase; -import org.pitest.mutationtest.ClassHistory; -import org.pitest.mutationtest.HistoryStore; -import org.pitest.mutationtest.MutationResult; -import org.pitest.mutationtest.MutationStatusTestPair; -import org.pitest.mutationtest.engine.MutationIdentifier; - -public class NullHistoryStore implements HistoryStore { - - @Override - public void initialize() { - - } - - @Override - public void recordResult(final MutationResult result) { - - } - - @Override - public Map getHistoricResults() { - return Collections.emptyMap(); - } - - @Override - public Map getHistoricClassPath() { - return Collections.emptyMap(); - } - - @Override - public void recordClassPath(final Collection ids, - final CoverageDatabase coverageInfo) { - - } - -} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStore.java b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistory.java similarity index 73% rename from pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStore.java rename to pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistory.java index ff6a05b52..b1a510ca7 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStore.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistory.java @@ -10,34 +10,46 @@ import java.io.Reader; import java.io.Serializable; import java.util.Base64; -import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Logger; +import org.pitest.classinfo.ClassHash; import org.pitest.classinfo.ClassName; import org.pitest.classinfo.HierarchicalClassId; +import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageDatabase; import java.util.Optional; +import java.util.stream.Collectors; + import org.pitest.mutationtest.ClassHistory; -import org.pitest.mutationtest.HistoryStore; +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationResult; import org.pitest.mutationtest.MutationStatusTestPair; +import org.pitest.mutationtest.engine.MutationDetails; import org.pitest.mutationtest.engine.MutationIdentifier; import org.pitest.util.Log; import org.pitest.util.Unchecked; -public class ObjectOutputStreamHistoryStore implements HistoryStore { +public class ObjectOutputStreamHistory implements History { private static final Logger LOG = Log .getLogger(); + + private final CodeSource code; private final WriterFactory outputFactory; private final BufferedReader input; private final Map previousResults = new HashMap<>(); private final Map previousClassPath = new HashMap<>(); + private CoverageDatabase coverageData; - public ObjectOutputStreamHistoryStore(final WriterFactory output, - final Optional input) { + public ObjectOutputStreamHistory(CodeSource code, WriterFactory output, + final Optional input) { + this.code = code; this.outputFactory = output; this.input = createReader(input); } @@ -47,35 +59,24 @@ private BufferedReader createReader(Optional input) { .orElse(null); } - @Override - public void recordClassPath(final Collection ids, - final CoverageDatabase coverageInfo) { - final PrintWriter output = this.outputFactory.create(); - output.println(ids.size()); - for (final HierarchicalClassId each : ids) { - final ClassHistory coverage = new ClassHistory(each, - coverageInfo.getCoverageIdForClass(each.getName()).toString(16)); - output.println(serialize(coverage)); - } - output.flush(); - } - @Override public void recordResult(final MutationResult result) { final PrintWriter output = this.outputFactory.create(); - output.println(serialize(new ObjectOutputStreamHistoryStore.IdResult( + output.println(serialize(new ObjectOutputStreamHistory.IdResult( result.getDetails().getId(), result.getStatusTestPair()))); output.flush(); } @Override - public Map getHistoricResults() { - return this.previousResults; + public List analyse(List mutationsForClasses) { + final MutationAnalyser analyser = new IncrementalAnalyser(new CodeHistory(code, this.previousResults, this.previousClassPath), + this.coverageData); + return analyser.analyse(mutationsForClasses); } @Override - public Map getHistoricClassPath() { - return this.previousClassPath; + public void close() { + } @Override @@ -91,6 +92,32 @@ public void initialize() { } } + @Override + public void processCoverage(CoverageDatabase coverageData) { + this.coverageData = coverageData; + recordClassPath(coverageData); + } + + private void recordClassPath(CoverageDatabase coverageData) { + Set allClassNames = code.getAllClassAndTestNames(); + + // sort by classname to ensure order consistent across machines + List ids = this.code.fetchClassHashes(allClassNames).stream() + .map(ClassHash::getHierarchicalId) + .sorted(Comparator.comparing(HierarchicalClassId::getName)) + .collect(Collectors.toList()); + + final PrintWriter output = this.outputFactory.create(); + output.println(ids.size()); + for (final HierarchicalClassId each : ids) { + final ClassHistory coverage = new ClassHistory(each, + coverageData.getCoverageIdForClass(each.getName()).toString(16)); + output.println(serialize(coverage)); + } + output.flush(); + } + + private void restoreResults() { String line; try { @@ -142,6 +169,14 @@ private String serialize(T t) { } } + public Map getHistoricClassPath() { + return this.previousClassPath; + } + + public Map getHistoricResults() { + return this.previousResults; + } + private static class IdResult implements Serializable { private static final long serialVersionUID = 1L; final MutationIdentifier id; diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/statistics/MutationStatistics.java b/pitest-entry/src/main/java/org/pitest/mutationtest/statistics/MutationStatistics.java index 78220ca99..53dddb29c 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/statistics/MutationStatistics.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/statistics/MutationStatistics.java @@ -63,6 +63,10 @@ public long getTotalMutationsWithCoverage() { return this.totalWithCoverage; } + public long getNumberOfTestsRun() { + return numberOfTestsRun; + } + private long getTotalMutationsWithoutCoverage() { return this.totalMutations - this.totalWithCoverage; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java index 208149d90..e5b6ec305 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java @@ -7,17 +7,21 @@ import org.pitest.coverage.CoverageGenerator; import org.pitest.coverage.execute.CoverageOptions; import org.pitest.coverage.execute.DefaultCoverageGenerator; -import org.pitest.mutationtest.HistoryStore; +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.HistoryFactory; +import org.pitest.mutationtest.HistoryParams; +import org.pitest.mutationtest.incremental.HistoryResultInterceptor; import org.pitest.mutationtest.MutationResultListenerFactory; import org.pitest.mutationtest.config.PluginServices; import org.pitest.mutationtest.config.ReportOptions; import org.pitest.mutationtest.config.SettingsFactory; -import org.pitest.mutationtest.incremental.NullHistoryStore; +import org.pitest.mutationtest.incremental.NullHistory; import org.pitest.mutationtest.incremental.NullWriterFactory; -import org.pitest.mutationtest.incremental.ObjectOutputStreamHistoryStore; import org.pitest.mutationtest.incremental.WriterFactory; import org.pitest.plugin.Feature; import org.pitest.plugin.FeatureParameter; +import org.pitest.plugin.FeatureParser; +import org.pitest.plugin.FeatureSelector; import org.pitest.process.ArgLineParser; import org.pitest.process.JavaAgent; import org.pitest.process.LaunchOptions; @@ -35,6 +39,7 @@ import java.util.Optional; import java.util.function.Consumer; +import static java.util.Collections.singletonList; import static org.pitest.util.Verbosity.VERBOSE; import static org.pitest.util.Verbosity.VERBOSE_NO_SPINNER; @@ -116,10 +121,11 @@ public AnalysisResult execute(File baseDir, ReportOptions data, final Optional maybeWriter = data.createHistoryWriter(); WriterFactory historyWriter = maybeWriter.orElse(new NullWriterFactory()); - final HistoryStore history = makeHistoryStore(data, maybeWriter); + HistoryFactory historyFactory = settings.createHistory(); + final History history = pickHistoryStore(code, data, maybeWriter, historyFactory); final MutationStrategies strategies = new MutationStrategies( - settings.createEngine(), history, coverageDatabase, reportFactory, settings.getResultInterceptor(), + settings.createEngine(), history, coverageDatabase, reportFactory, settings.getResultInterceptor().add(new HistoryResultInterceptor(history)), settings.createCoverageTransformer(code), reportOutput, settings.createVerifier().create(code)); @@ -146,15 +152,16 @@ private List createJvmArgs(ReportOptions data) { private void updateData(ReportOptions data, SettingsFactory settings) { settings.createUpdater().updateConfig(null, data); - } - private HistoryStore makeHistoryStore(ReportOptions data, Optional historyWriter) { + private History pickHistoryStore(CodeSource code, ReportOptions data, Optional historyWriter, HistoryFactory factory) { final Optional reader = data.createHistoryReader(); if (!reader.isPresent() && !historyWriter.isPresent()) { - return new NullHistoryStore(); + return new NullHistory(); } - return new ObjectOutputStreamHistoryStore(historyWriter.orElse(new NullWriterFactory()), reader); + FeatureParser parser = new FeatureParser(); + FeatureSelector select = new FeatureSelector(parser.parseFeatures(data.getFeatures()), singletonList(factory)); + return factory.makeHistory(new HistoryParams(select, code), historyWriter.orElse(new NullWriterFactory()), reader); } private void checkMatrixMode(ReportOptions data) { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java index b66a5b8ec..1e81dbd95 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java @@ -16,9 +16,7 @@ import org.pitest.classinfo.CachingByteArraySource; import org.pitest.classinfo.ClassByteArraySource; -import org.pitest.classinfo.ClassInfo; import org.pitest.classinfo.ClassName; -import org.pitest.classinfo.HierarchicalClassId; import org.pitest.classpath.ClassPathByteArraySource; import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.classpath.CodeSource; @@ -27,14 +25,11 @@ import org.pitest.coverage.CoverageSummary; import org.pitest.coverage.NoCoverage; import org.pitest.coverage.ReportCoverage; -import org.pitest.coverage.TestInfo; -import org.pitest.functional.FCollection; import org.pitest.help.Help; import org.pitest.help.PitHelpError; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.EngineArguments; -import org.pitest.mutationtest.HistoryStore; import org.pitest.mutationtest.ListenerArguments; -import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationConfig; import org.pitest.mutationtest.MutationResultInterceptor; import org.pitest.mutationtest.MutationResultListener; @@ -50,10 +45,8 @@ import org.pitest.mutationtest.config.SettingsFactory; import org.pitest.mutationtest.engine.MutationEngine; import org.pitest.mutationtest.execute.MutationAnalysisExecutor; -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.incremental.NullHistory; import org.pitest.mutationtest.statistics.MutationStatistics; import org.pitest.mutationtest.statistics.MutationStatisticsListener; import org.pitest.mutationtest.statistics.Score; @@ -67,8 +60,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -152,8 +143,11 @@ private CombinedStatistics emptyStatistics() { } private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments args, MutationEngine engine, List issues) { - CoverageDatabase coverageData = coverage().calculateCoverage(); - HistoryStore history = this.strategies.history(); + History history = this.strategies.history(); + history.initialize(); + + CoverageDatabase coverageData = coverage().calculateCoverage(history.limitTests()); + history.processCoverage(coverageData); LOG.fine("Used memory after coverage calculation " + ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " mb"); @@ -162,8 +156,6 @@ private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments final MutationStatisticsListener stats = new MutationStatisticsListener(); - history.initialize(); - this.timings.registerStart(Timings.Stage.BUILD_MUTATION_TESTS); final List tus = buildMutationTests(coverageData, history, engine, args, allInterceptors()); @@ -171,8 +163,6 @@ private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments LOG.info("Created " + tus.size() + " mutation test units" ); - 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) @@ -233,7 +223,7 @@ private List findMutations(MutationEngine engine, EngineAr // 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 mutants = buildMutationTests(new NoCoverage(), new NullHistoryStore(), engine, args, noReportsOrFilters()); + List mutants = buildMutationTests(new NoCoverage(), new NullHistory(), engine, args, noReportsOrFilters()); this.timings.registerEnd(Timings.Stage.MUTATION_PRE_SCAN); return mutants; } @@ -262,7 +252,7 @@ private int numberOfThreads() { private List createConfig(long t0, ReportCoverage coverageData, - HistoryStore history, + History history, MutationStatisticsListener stats, MutationEngine engine) { final List ls = new ArrayList<>(); @@ -289,29 +279,6 @@ private MutationResultInterceptor resultInterceptor() { return this.strategies.resultInterceptor(); } - private void recordClassPath(HistoryStore history, CoverageDatabase coverageData) { - Set allClassNames = getAllClassesAndTests(coverageData); - - // sort by classname to ensure order consistent across machines - List ids = this.code.getClassInfo(allClassNames).stream() - .map(ClassInfo::getHierarchicalId) - .sorted(Comparator.comparing(HierarchicalClassId::getName)) - .collect(Collectors.toList()); - - history.recordClassPath(ids, coverageData); - } - - private Set getAllClassesAndTests( - final CoverageDatabase coverageData) { - final Set names = new HashSet<>(); - for (final ClassName each : this.code.getCodeUnderTestNames()) { - names.add(each); - FCollection.mapTo(coverageData.getTestsForClass(each), - TestInfo.toDefiningClassName(), names); - } - return names; - } - private List verifyBuildSuitableForMutationTesting() { return this.strategies.buildVerifier().verify(); } @@ -353,7 +320,7 @@ private void printStats(CombinedStatistics combinedStatistics) { } private List buildMutationTests(CoverageDatabase coverageData, - HistoryStore history, + History history, MutationEngine engine, EngineArguments args, Predicate interceptorFilter) { @@ -376,9 +343,6 @@ private List buildMutationTests(CoverageDatabase coverageD final MutationSource source = new MutationSource(mutationConfig, testPrioritiser, bas, interceptor); - final MutationAnalyser analyser = new IncrementalAnalyser(new DefaultCodeHistory(this.code, history), - coverageData, - enableIncrementalAnalysisLogging(history)); final WorkerFactory wf = new WorkerFactory(this.baseDir, coverage() .getConfiguration(), mutationConfig, args, @@ -389,19 +353,11 @@ private List buildMutationTests(CoverageDatabase coverageD final MutationGrouper grouper = this.settings.getMutationGrouper().makeFactory( this.data.getFreeFormProperties(), this.code, this.data.getNumberOfThreads(), this.data.getMutationUnitSize()); - final MutationTestBuilder builder = new MutationTestBuilder(wf, analyser, + final MutationTestBuilder builder = new MutationTestBuilder(wf, history, source, grouper); return builder.createMutationTestUnits(this.code.getCodeUnderTestNames()); } - - private boolean enableIncrementalAnalysisLogging(HistoryStore history) { - // Horrible hack to prevent logging during pre scan phase. This would - // cause confusion as if only part of the log is read it appears that - // incremental analysis has affected no mutants - return !(history instanceof NullHistoryStore); - } - private void checkMutationsFound(final List tus) { if (tus.isEmpty()) { if (this.data.shouldFailWhenNoMutations()) { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationStrategies.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationStrategies.java index 4c2e3d235..e3b1a1db0 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationStrategies.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationStrategies.java @@ -1,8 +1,8 @@ package org.pitest.mutationtest.tooling; import org.pitest.coverage.CoverageGenerator; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.build.CoverageTransformer; -import org.pitest.mutationtest.HistoryStore; import org.pitest.mutationtest.MutationEngineFactory; import org.pitest.mutationtest.MutationResultInterceptor; import org.pitest.mutationtest.MutationResultListenerFactory; @@ -11,7 +11,7 @@ public class MutationStrategies { - private final HistoryStore history; + private final History history; private final CoverageGenerator coverage; private final MutationResultListenerFactory listenerFactory; private final MutationResultInterceptor resultsInterceptor; @@ -22,11 +22,11 @@ public class MutationStrategies { private final ResultOutputStrategy output; public MutationStrategies(final MutationEngineFactory factory, - final HistoryStore history, final CoverageGenerator coverage, - final MutationResultListenerFactory listenerFactory, - final MutationResultInterceptor resultsInterceptor, - final CoverageTransformer coverageTransformer, - final ResultOutputStrategy output, final BuildVerifier buildVerifier) { + final History history, final CoverageGenerator coverage, + final MutationResultListenerFactory listenerFactory, + final MutationResultInterceptor resultsInterceptor, + final CoverageTransformer coverageTransformer, + final ResultOutputStrategy output, final BuildVerifier buildVerifier) { this.history = history; this.coverage = coverage; this.listenerFactory = listenerFactory; @@ -37,7 +37,7 @@ public MutationStrategies(final MutationEngineFactory factory, this.output = output; } - public HistoryStore history() { + public History history() { return this.history; } diff --git a/pitest-entry/src/test/java/com/example/history/ClassA.java b/pitest-entry/src/test/java/com/example/history/ClassA.java new file mode 100644 index 000000000..0b7450c9e --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/ClassA.java @@ -0,0 +1,13 @@ +package com.example.history; + +public class ClassA { + public int returnOne() { + System.out.println("I was run"); + return 1; + } + + public int noCoverage() { + System.out.println(""); + return 1; + } +} diff --git a/pitest-entry/src/test/java/com/example/history/ClassATest.java b/pitest-entry/src/test/java/com/example/history/ClassATest.java new file mode 100644 index 000000000..95c37a719 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/ClassATest.java @@ -0,0 +1,14 @@ +package com.example.history; + +import org.pitest.simpletest.TestAnnotationForTesting; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ClassATest { + + @TestAnnotationForTesting + public void testClassA() { + assertThat(new ClassA().returnOne()).isEqualTo(1); + } + +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/com/example/history/ClassB.java b/pitest-entry/src/test/java/com/example/history/ClassB.java new file mode 100644 index 000000000..ac950a0e9 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/ClassB.java @@ -0,0 +1,13 @@ +package com.example.history; + +public class ClassB { + public int returnOne() { + System.out.println("I was run"); + return 1; + } + + public int noCoverage() { + System.out.println(""); + return 1; + } +} diff --git a/pitest-entry/src/test/java/com/example/history/ClassBTest.java b/pitest-entry/src/test/java/com/example/history/ClassBTest.java new file mode 100644 index 000000000..15acb73c0 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/ClassBTest.java @@ -0,0 +1,15 @@ +package com.example.history; + +import org.pitest.simpletest.TestAnnotationForTesting; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ClassBTest { + + + @TestAnnotationForTesting + public void testClassA() { + assertThat(new ClassB().returnOne()).isEqualTo(1); + } + +} diff --git a/pitest-entry/src/test/java/com/example/history/SlowKillingTest.java b/pitest-entry/src/test/java/com/example/history/SlowKillingTest.java new file mode 100644 index 000000000..0bcc6eab8 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/SlowKillingTest.java @@ -0,0 +1,17 @@ +package com.example.history; + +import org.pitest.simpletest.TestAnnotationForTesting; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SlowKillingTest { + @TestAnnotationForTesting + public void slowTest() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + assertThat(new ClassA().returnOne()).isEqualTo(1); + } +} diff --git a/pitest-entry/src/test/java/com/example/history/UselessTest1.java b/pitest-entry/src/test/java/com/example/history/UselessTest1.java new file mode 100644 index 000000000..d98d42740 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/UselessTest1.java @@ -0,0 +1,15 @@ +package com.example.history; + +import org.pitest.simpletest.TestAnnotationForTesting; + +public class UselessTest1 { + @TestAnnotationForTesting + public void fastTest() { + new ClassA().returnOne(); + } + + @TestAnnotationForTesting + public void fastTest2() { + new ClassA().returnOne(); + } +} diff --git a/pitest-entry/src/test/java/com/example/history/UselessTest2.java b/pitest-entry/src/test/java/com/example/history/UselessTest2.java new file mode 100644 index 000000000..f5f650477 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/history/UselessTest2.java @@ -0,0 +1,16 @@ +package com.example.history; + +import org.pitest.simpletest.TestAnnotationForTesting; + +public class UselessTest2 { + @TestAnnotationForTesting + public void fastTest() { + new ClassA().returnOne(); + } + + @TestAnnotationForTesting + public void fastTest2() { + new ClassA().returnOne(); + } + +} diff --git a/pitest-entry/src/test/java/org/pitest/classinfo/NameToClassInfoTest.java b/pitest-entry/src/test/java/org/pitest/classinfo/NameToClassInfoTest.java deleted file mode 100644 index c36518b1c..000000000 --- a/pitest-entry/src/test/java/org/pitest/classinfo/NameToClassInfoTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.pitest.classinfo; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import org.junit.Test; - -public class NameToClassInfoTest { - - @Test - public void shouldFetchClassWhenApplied() { - final Repository repository = mock(Repository.class); - final NameToClassInfo testee = new NameToClassInfo(repository); - testee.apply(ClassName.fromString("foo")); - verify(repository).fetchClass(ClassName.fromString("foo")); - } - -} diff --git a/pitest-entry/src/test/java/org/pitest/classpath/DefaultCodeSourceTest.java b/pitest-entry/src/test/java/org/pitest/classpath/DefaultCodeSourceTest.java index b5a0c678e..b6fd00125 100644 --- a/pitest-entry/src/test/java/org/pitest/classpath/DefaultCodeSourceTest.java +++ b/pitest-entry/src/test/java/org/pitest/classpath/DefaultCodeSourceTest.java @@ -11,6 +11,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.pitest.classinfo.ClassHash; import org.pitest.classinfo.ClassInfo; import org.pitest.classinfo.ClassInfoMother; import org.pitest.classinfo.ClassName; @@ -27,9 +28,9 @@ public class DefaultCodeSourceTest { @Mock private ProjectClassPaths classPath; - private ClassInfo foo; + private ClassHash foo; - private ClassInfo bar; + private ClassHash bar; @Before public void setUp() { @@ -83,11 +84,11 @@ public void shouldReturnNoneWhenNoTesteeExistsMatchingNamingConvention() { @Test public void shouldProvideDetailsOfRequestedClasses() { - when(this.repository.fetchClass(ClassName.fromString("Foo"))).thenReturn( + when(this.repository.fetchClassHash(ClassName.fromString("Foo"))).thenReturn( Optional.ofNullable(this.foo)); - when(this.repository.fetchClass(ClassName.fromString("Unknown"))) - .thenReturn(Optional. empty()); - assertEquals(Arrays.asList(this.foo), this.testee.getClassInfo(Arrays + when(this.repository.fetchClassHash(ClassName.fromString("Unknown"))) + .thenReturn(Optional. empty()); + assertEquals(Arrays.asList(this.foo), this.testee.fetchClassHashes(Arrays .asList(ClassName.fromString("Foo"), ClassName.fromString("Unknown")))); } @@ -99,7 +100,7 @@ public void shouldAllowClientsToRetrieveBytecode() { private ClassInfo makeClassInfo(final String name) { final ClassInfo ci = ClassInfoMother.make(name); - when(this.repository.fetchClass(ClassName.fromString(name))).thenReturn( + when(this.repository.fetchClassHash(ClassName.fromString(name))).thenReturn( Optional.ofNullable(ci)); return ci; } diff --git a/pitest-entry/src/test/java/org/pitest/coverage/CoverageDataTest.java b/pitest-entry/src/test/java/org/pitest/coverage/CoverageDataTest.java index 5a2beb71e..bd9efd474 100644 --- a/pitest-entry/src/test/java/org/pitest/coverage/CoverageDataTest.java +++ b/pitest-entry/src/test/java/org/pitest/coverage/CoverageDataTest.java @@ -28,7 +28,6 @@ import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageMother.BlockLocationBuilder; import org.pitest.coverage.CoverageMother.CoverageResultBuilder; -import org.pitest.functional.FCollection; import org.pitest.mutationtest.engine.Location; import org.pitest.testapi.Description; @@ -151,7 +150,7 @@ public void shouldReturnNonZeroCoverageIdWhenTestsCoverClass() { final ClassName foo = ClassName.fromString("Foo"); final ClassInfo ci = ClassInfoMother.make(foo); - when(this.code.getClassInfo(any(Collection.class))).thenReturn( + when(this.code.fetchClassHashes(any(Collection.class))).thenReturn( Collections.singletonList(ci)); final BlockLocationBuilder block = aBlockLocation().withLocation( diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/CompoundMutationResultInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/CompoundMutationResultInterceptorTest.java index a0e19f0ca..f4246f74a 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/CompoundMutationResultInterceptorTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/CompoundMutationResultInterceptorTest.java @@ -4,7 +4,6 @@ import org.pitest.mutationtest.report.MutationTestResultMother; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -16,9 +15,9 @@ public class CompoundMutationResultInterceptorTest { @Test - public void chainsChildCallsToModify() { - MutationResultInterceptor a = appendToDesc("foo"); - MutationResultInterceptor b = appendToDesc("bar"); + public void chainsChildCallsToModifyByPriority() { + MutationResultInterceptor a = appendToDesc("bar", 1); + MutationResultInterceptor b = appendToDesc("foo", 0); CompoundMutationResultInterceptor underTest = new CompoundMutationResultInterceptor(asList(a,b)); @@ -28,6 +27,7 @@ public void chainsChildCallsToModify() { .allMatch(m -> m.getDetails().getDescription().endsWith("foobar")); } + @Test public void combinesRemainingResults() { MutationResultInterceptor a = hasResult(aMutationTestResult().withMutationDetails(aMutationDetail().withDescription("a"))); @@ -59,7 +59,7 @@ private static List someClassResults() { return asList(MutationTestResultMother.createClassResults(aMutationTestResult().build(2))); } - private MutationResultInterceptor appendToDesc(final String foo) { + private MutationResultInterceptor appendToDesc(final String foo, final int priority) { return new MutationResultInterceptor() { @Override public Collection modify(Collection results) { @@ -68,6 +68,11 @@ public Collection modify(Collection .collect(Collectors.toList()); } + @Override + public int priority() { + return priority; + } + private MutationResult appendToDesc(MutationResult result, String toAppend) { return new MutationResult(result.getDetails().withDescription(result.getDetails().getDescription() + toAppend) , result.getStatusTestPair()); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/NullAnalyser.java b/pitest-entry/src/test/java/org/pitest/mutationtest/NullAnalyser.java deleted file mode 100644 index d8fc4dd31..000000000 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/NullAnalyser.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.pitest.mutationtest; - -import java.util.Collection; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.pitest.functional.FCollection; -import org.pitest.mutationtest.engine.MutationDetails; - -/** - * Always selects the start status for a mutation - * - */ -public class NullAnalyser implements MutationAnalyser { - - @Override - public Collection analyse( - final Collection mutationsForClasses) { - return mutationsForClasses.stream() - .map(mutationToResult()) - .collect(Collectors.toList()); - } - - private Function mutationToResult() { - return a -> new MutationResult(a, MutationStatusTestPair.notAnalysed(0, - DetectionStatus.NOT_STARTED)); - } - -} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java index 0c7d81c10..c3ff2a11d 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; -import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -26,7 +25,7 @@ import org.pitest.mutationtest.config.SettingsFactory; import org.pitest.mutationtest.config.TestPluginArguments; import org.pitest.mutationtest.engine.gregor.config.GregorEngineFactory; -import org.pitest.mutationtest.incremental.NullHistoryStore; +import org.pitest.mutationtest.incremental.NullHistory; import org.pitest.mutationtest.tooling.JarCreatingJarFinder; import org.pitest.mutationtest.tooling.MutationCoverage; import org.pitest.mutationtest.tooling.MutationStrategies; @@ -121,7 +120,7 @@ protected void createAndRun(SettingsFactory settings) { null, coverageOptions, launchOptions, code, new NullCoverageExporter(), timings, Verbosity.DEFAULT); - final HistoryStore history = new NullHistoryStore(); + final History history = new NullHistory(); final MutationStrategies strategies = new MutationStrategies( new GregorEngineFactory(), history, coverageDatabase, diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java b/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java index 2dac08967..28245dd8f 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java @@ -73,6 +73,7 @@ import org.pitest.mutationtest.engine.MutationEngine; import org.pitest.mutationtest.engine.gregor.config.GregorEngineFactory; import org.pitest.mutationtest.execute.MutationAnalysisExecutor; +import org.pitest.mutationtest.incremental.NullHistory; import org.pitest.mutationtest.tooling.JarCreatingJarFinder; import org.pitest.process.DefaultJavaExecutableLocator; import org.pitest.process.JavaAgent; @@ -247,7 +248,7 @@ private void createEngineAndRun(final ReportOptions data, null, coverageOptions, launchOptions, code, new NullCoverageExporter(), timings, Verbosity.DEFAULT); - final CoverageDatabase coverageData = coverageGenerator.calculateCoverage(); + final CoverageDatabase coverageData = coverageGenerator.calculateCoverage(c -> true); final Collection codeClasses = code.getCodeUnderTestNames(); @@ -278,7 +279,7 @@ null, coverageOptions, launchOptions, code, new NullCoverageExporter(), final MutationTestBuilder builder = new MutationTestBuilder(wf, - new NullAnalyser(), source, new DefaultGrouper(0)); + new NullHistory(), source, new DefaultGrouper(0)); final List tus = builder .createMutationTestUnits(codeClasses); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestBuilderTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestBuilderTest.java index f288fad5a..0699388ac 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestBuilderTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestBuilderTest.java @@ -16,8 +16,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.pitest.classinfo.ClassName; -import org.pitest.mutationtest.NullAnalyser; import org.pitest.mutationtest.engine.MutationDetails; +import org.pitest.mutationtest.incremental.NullHistory; public class MutationTestBuilderTest { @@ -91,7 +91,7 @@ private void assertCreatesOneTestUnitForTwoMutations() { } private void makeTesteeWithUnitSizeOf(int unitSize) { - this.testee = new MutationTestBuilder(this.wf, new NullAnalyser(), + this.testee = new MutationTestBuilder(this.wf, new NullHistory(), this.source, new DefaultGrouper(unitSize)); } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/config/PluginServicesTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/config/PluginServicesTest.java index 32f41bcc0..10992e208 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/config/PluginServicesTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/config/PluginServicesTest.java @@ -47,6 +47,10 @@ public void shouldListAllTypesOfFeature() { assertThat(testee.findFeatures()).hasAtLeastOneElementOfType(MutationResultListenerFactory.class); } + @Test + public void noHistoryStoresProvidedByDefault() { + assertThat(testee.findHistory()).isEmpty(); + } private PluginServices createWithFeatures(MutationInterceptorFactory ... features) { Services loader = Mockito.mock(Services.class); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/config/SettingsFactoryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/config/SettingsFactoryTest.java index b4cff87b7..0d868f487 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/config/SettingsFactoryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/config/SettingsFactoryTest.java @@ -6,6 +6,7 @@ import org.pitest.coverage.execute.CoverageOptions; import org.pitest.coverage.export.NullCoverageExporter; import org.pitest.mutationtest.engine.gregor.config.GregorEngineFactory; +import org.pitest.mutationtest.incremental.DefaultHistoryFactory; import org.pitest.plugin.Feature; import org.pitest.testapi.TestGroupConfig; import org.pitest.util.PitError; @@ -15,6 +16,7 @@ import java.util.Collections; import java.util.function.Consumer; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -146,4 +148,9 @@ public void shouldTreatFeaturesAsCaseInsensitive() { } + @Test + public void producesDefaultHistoryStore() { + assertThat(this.testee.createHistory()).isInstanceOf(DefaultHistoryFactory.class); + } + } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/DefaultCodeHistoryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/CodeHistoryTest.java similarity index 90% rename from pitest-entry/src/test/java/org/pitest/mutationtest/incremental/DefaultCodeHistoryTest.java rename to pitest-entry/src/test/java/org/pitest/mutationtest/incremental/CodeHistoryTest.java index 6137fdd70..61fa0bb82 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/DefaultCodeHistoryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/CodeHistoryTest.java @@ -13,10 +13,10 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.pitest.classinfo.ClassHashSource; import org.pitest.classinfo.ClassIdentifier; import org.pitest.classinfo.ClassInfo; import org.pitest.classinfo.ClassInfoMother; -import org.pitest.classinfo.ClassInfoSource; import org.pitest.classinfo.ClassName; import org.pitest.classinfo.HierarchicalClassId; import java.util.Optional; @@ -25,12 +25,12 @@ import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.engine.MutationIdentifier; -public class DefaultCodeHistoryTest { +public class CodeHistoryTest { - private DefaultCodeHistory testee; + private CodeHistory testee; @Mock - private ClassInfoSource classInfoSource; + private ClassHashSource classInfoSource; private final Map historicClassPath = new HashMap<>(); @@ -39,7 +39,7 @@ public class DefaultCodeHistoryTest { @Before public void setUp() { MockitoAnnotations.openMocks(this); - this.testee = new DefaultCodeHistory(this.classInfoSource, this.results, + this.testee = new CodeHistory(this.classInfoSource, this.results, this.historicClassPath); } @@ -111,12 +111,12 @@ public void shouldTreatClassesWithSameHashAsUnChanged() { private void setCurrentClassPath(final HierarchicalClassId currentId) { final ClassInfo currentClass = ClassInfoMother.make(currentId.getId()); - when(this.classInfoSource.fetchClass(ClassName.fromString("foo"))) + when(this.classInfoSource.fetchClassHash(ClassName.fromString("foo"))) .thenReturn(Optional.ofNullable(currentClass)); } private void setCurrentClassPath(final ClassInfo info) { - when(this.classInfoSource.fetchClass(ClassName.fromString("foo"))) + when(this.classInfoSource.fetchClassHash(ClassName.fromString("foo"))) .thenReturn(Optional.ofNullable(info)); } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryListenerTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryListenerTest.java index 122751187..4db5610e5 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryListenerTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryListenerTest.java @@ -5,8 +5,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.pitest.mutationtest.ClassMutationResults; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.DetectionStatus; -import org.pitest.mutationtest.HistoryStore; import org.pitest.mutationtest.MutationResult; import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.report.MutationTestResultMother; @@ -18,7 +18,7 @@ public class HistoryListenerTest { private HistoryListener testee; @Mock - private HistoryStore store; + private History store; @Before public void setUp() { @@ -27,18 +27,10 @@ public void setUp() { } @Test - public void shouldRecordMutationResults() { - final MutationResult mr = makeResult(); - final ClassMutationResults metaData = MutationTestResultMother - .createClassResults(mr); - this.testee.handleMutationResult(metaData); - verify(this.store).recordResult(mr); + public void closesTheAttachedHistory() { + this.testee.runEnd(); + verify(this.store).close(); } - private MutationResult makeResult() { - return new MutationResult( - MutationTestResultMother.createDetails(), MutationStatusTestPair.notAnalysed(0, - DetectionStatus.KILLED)); - } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryResultInterceptorTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryResultInterceptorTest.java new file mode 100644 index 000000000..ddfb231eb --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryResultInterceptorTest.java @@ -0,0 +1,43 @@ +package org.pitest.mutationtest.incremental; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.pitest.mutationtest.ClassMutationResults; +import org.pitest.mutationtest.DetectionStatus; +import org.pitest.mutationtest.History; +import org.pitest.mutationtest.MutationResult; +import org.pitest.mutationtest.MutationStatusTestPair; +import org.pitest.mutationtest.report.MutationTestResultMother; + +import java.util.Collection; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +public class HistoryResultInterceptorTest { + private History store = Mockito.mock(History.class); + private HistoryResultInterceptor testee = new HistoryResultInterceptor(this.store); + + @Test + public void recordsMutationResults() { + final MutationResult mr = makeResult(); + final ClassMutationResults metaData = MutationTestResultMother + .createClassResults(mr); + Collection mutants = asList(metaData); + Collection actual = this.testee.modify(mutants); + verify(this.store).recordResult(mr); + assertThat(actual).isSameAs(mutants); + } + + + private MutationResult makeResult() { + return new MutationResult( + MutationTestResultMother.createDetails(), MutationStatusTestPair.notAnalysed(0, + DetectionStatus.KILLED)); + } + +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryTest.java new file mode 100644 index 000000000..de1a9f5b6 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/HistoryTest.java @@ -0,0 +1,271 @@ +package org.pitest.mutationtest.incremental; + +import com.example.history.ClassA; +import com.example.history.ClassATest; +import com.example.history.SlowKillingTest; +import com.example.history.UselessTest1; +import com.example.history.UselessTest2; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.classinfo.ClassHash; +import org.pitest.classinfo.ClassIdentifier; +import org.pitest.classinfo.ClassName; +import org.pitest.classinfo.HierarchicalClassId; +import org.pitest.classinfo.Repository; +import org.pitest.classpath.ClassPath; +import org.pitest.classpath.ClassloaderByteArraySource; +import org.pitest.classpath.CodeSource; +import org.pitest.classpath.ProjectClassPaths; +import org.pitest.functional.Streams; +import org.pitest.mutationtest.config.PluginServices; +import org.pitest.mutationtest.config.ReportOptions; +import org.pitest.mutationtest.config.SettingsFactory; +import org.pitest.mutationtest.tooling.AnalysisResult; +import org.pitest.mutationtest.tooling.EntryPoint; +import org.pitest.testapi.TestGroupConfig; +import org.pitest.util.Verbosity; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class HistoryTest { + + @Rule + public TemporaryFolder root = new TemporaryFolder(); + + @Test + public void sameResultsWhenNoChanges() throws Exception { + Project project = createProject(root, ClassA.class, ClassATest.class); + AnalysisResult run1 = runPitest(project); + AnalysisResult run2 = runPitest(project); + + assertSameNumberOfMutationsKilled(run1, run2); + assertThat(numberOfTestsRun(run2)).isEqualTo(0); + } + + + @Test + public void sameResultWhenCodeUnderTestTouched() throws Exception { + Project project = createProject(root, ClassA.class, ClassATest.class); + + AnalysisResult run1 = runPitest(project); + + project.modifyClass(ClassA.class); + + AnalysisResult run2 = runPitest(project); + + assertSameNumberOfMutationsKilled(run1, run2); + assertThat(numberOfTestsRun(run2)).isGreaterThan(0); + } + + @Test + public void testsReRunWhenTestAltered() throws Exception { + Project project = createProject(root, ClassA.class, ClassATest.class); + + runPitest(project); + + project.modifyClass(ClassATest.class); + + AnalysisResult run2 = runPitest(project); + + assertThat(numberOfTestsRun(run2)).isGreaterThan(0); + } + + + @Test + public void mutationsUncoveredWhenTestRemoved() throws Exception { + Project project = createProject(root, ClassA.class, ClassATest.class); + AnalysisResult run1 = runPitest(project); + + project.removeTest(ClassATest.class); + + AnalysisResult run2 = runPitest(project); + + assertThat(getTotalDetectedMutations(run2)).isEqualTo(0); + assertThat(run1).isNotEqualTo(run2); + } + + @Test + public void mutationsKilledWhenTestAdded() throws Exception { + Project project = createProject(root, ClassA.class); + AnalysisResult run1 = runPitest(project); + + project.addTest(ClassATest.class); + + AnalysisResult run2 = runPitest(project); + + assertThat(getTotalDetectedMutations(run2)).isEqualTo(1); + assertThat(run1).isNotEqualTo(run2); + } + + @Test + public void prioritisesLastKillingTest() throws Exception { + Project project = createProject(root, ClassA.class, UselessTest1.class, UselessTest2.class, SlowKillingTest.class); + AnalysisResult run1 = runPitest(project); + + project.modifyClass(ClassA.class); + + AnalysisResult run2 = runPitest(project); + + assertThat(numberOfTestsRun(run2)).isLessThan(numberOfTestsRun(run1)); + } + + + private static long getTotalDetectedMutations(AnalysisResult run2) { + return run2.getStatistics().get().getMutationStatistics().getTotalDetectedMutations(); + } + + private AnalysisResult runPitest(Project project) throws IOException { + EntryPoint entryPoint = new EntryPoint(); + ReportOptions data = new ReportOptions(); + data.setReportDir(project.reportsDir()); + data.setGroupConfig(new TestGroupConfig()); + data.setExcludedRunners(Collections.emptyList()); + data.setSourceDirs(Collections.emptyList()); + data.setVerbosity(Verbosity.VERBOSE); + data.setTargetClasses(singletonList("com.example.*")); + + data.setHistoryInputLocation(project.root().resolve("history.txt").toFile()); + data.setHistoryOutputLocation(project.root().resolve("history.txt").toFile()); + + SettingsFactory settings = settingsFactory(project, data); + return entryPoint.execute(project.root().toFile(), data, settings, new HashMap<>()); + + } + + private SettingsFactory settingsFactory(Project project, ReportOptions data) { + return new SettingsFactory(data, PluginServices.makeForContextLoader()) { + public CodeSource createCodeSource(ProjectClassPaths classPath) { + ClassloaderByteArraySource bas = ClassloaderByteArraySource.fromContext(); + Repository r = new Repository(bas); + return new CodeSource() { + @Override + public Stream codeTrees() { + return project.classes().stream() + .map(c -> bas.getBytes(c.getName()).get()) + .map(ClassTree::fromBytes); + } + + @Override + public Set getCodeUnderTestNames() { + return project.classes().stream() + .map(ClassName::fromClass) + .collect(Collectors.toSet()); + } + + @Override + public Set getTestClassNames() { + return project.tests().stream() + .map(ClassName::fromClass) + .peek(System.out::println) + .collect(Collectors.toSet()); + } + + @Override + public Stream testTrees() { + return project.tests().stream() + .map(c -> bas.getBytes(c.getName()).get()) + .map(ClassTree::fromBytes); + } + + @Override + public ClassPath getClassPath() { + // return our own classpath??? + return new ClassPath(); + } + + @Override + public Optional findTestee(String s) { + return Optional.empty(); + } + + @Override + public Optional fetchClassBytes(ClassName className) { + return getBytes(className.asJavaName()); + } + + @Override + public Optional fetchClassHash(ClassName className) { + return project.hasClass(className) + .flatMap(r::fetchClassHash) + .map(this::modify); + } + + private ClassHash modify(ClassHash classInfo) { + if (project.isModified(classInfo.getName())) { + + return new ClassHash() { + + @Override + public ClassIdentifier getId() { + return new ClassIdentifier(classInfo.getId().getHash() + 1, classInfo.getName()); + } + + @Override + public ClassName getName() { + return classInfo.getName(); + } + + @Override + public BigInteger getDeepHash() { + return classInfo.getDeepHash(); + } + + @Override + public HierarchicalClassId getHierarchicalId() { + return new HierarchicalClassId(this.getId(), classInfo.getHierarchicalId().getHierarchicalHash()); + } + }; + } + return classInfo; + } + + @Override + public Collection fetchClassHashes(Collection classes) { + return classes.stream() + .flatMap(c -> Streams.fromOptional(fetchClassHash(c))) + .collect(Collectors.toList()); + } + + @Override + public Optional getBytes(String s) { + return project.hasClass(ClassName.fromString(s)). + flatMap(c -> bas.getBytes(c.asJavaName())); + } + }; + } + }; + } + + private Project createProject(TemporaryFolder tempFolder, Class c, Class... tests) throws IOException { + Path root = tempFolder.getRoot().toPath(); + Path project = Files.createDirectories(root.resolve("project")); + Files.createDirectory(project.resolve("reports")); + return new Project(project, asList(c), asList(tests)); + } + + private void assertSameNumberOfMutationsKilled(AnalysisResult r, AnalysisResult r2) { + long detected1 = getTotalDetectedMutations(r); + long detected2 = getTotalDetectedMutations(r2); + assertThat(detected1).isEqualTo(detected2); + } + + private static long numberOfTestsRun(AnalysisResult r) { + return r.getStatistics().get().getMutationStatistics().getNumberOfTestsRun(); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/IncrementalAnalyserTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/IncrementalAnalyserTest.java index 528461e19..ded47ea8b 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/IncrementalAnalyserTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/IncrementalAnalyserTest.java @@ -81,17 +81,16 @@ public void removeLogCatcher() { } @Test - public void shouldStartNewMutationsAtAStatusOfNotStarted() { + public void shouldNotPreprocessNewMutations() { final MutationDetails md = makeMutation("foo"); when(this.history.getPreviousResult(any(MutationIdentifier.class))) .thenReturn(Optional.empty()); final Collection actual = this.testee.analyse(singletonList(md)); - assertThat(actual, hasItem(withStatus(NOT_STARTED))); + assertThat(actual, Matchers.hasSize(0)); assertThat(logCatcher.logEntries, hasItems( - "Incremental analysis set 1 mutations to a status of NOT_STARTED", "Incremental analysis reduced number of mutations by 0" )); } @@ -337,12 +336,10 @@ public void assessMultipleMutationsAtATime() { assertThat(actual, contains( withStatus(KILLED), withStatus(KILLED), - withStatus(SURVIVED), - withStatus(NOT_STARTED) + withStatus(SURVIVED) )); assertThat(logCatcher.logEntries, hasItems( - "Incremental analysis set 1 mutations to a status of NOT_STARTED", "Incremental analysis set 2 mutations to a status of KILLED", "Incremental analysis set 1 mutations to a status of SURVIVED", "Incremental analysis reduced number of mutations by 3" diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryStoreTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryStoreTest.java deleted file mode 100644 index 4c3c47f55..000000000 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryStoreTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.pitest.mutationtest.incremental; - -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class NullHistoryStoreTest { - - private final NullHistoryStore testee = new NullHistoryStore(); - - @Test - public void shouldReturnEmptyClassPath() { - assertTrue(this.testee.getHistoricClassPath().isEmpty()); - } - - @Test - public void shouldReturnEmptyResults() { - assertTrue(this.testee.getHistoricResults().isEmpty()); - } - -} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryTest.java new file mode 100644 index 000000000..96c348330 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/NullHistoryTest.java @@ -0,0 +1,17 @@ +package org.pitest.mutationtest.incremental; + +import org.junit.Test; +import org.pitest.mutationtest.engine.MutationDetailsMother; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NullHistoryTest { + + private final NullHistory testee = new NullHistory(); + + @Test + public void returnsNoAnalysedMutants() { + assertThat(testee.analyse(MutationDetailsMother.aMutationDetail().build(2))).isEmpty(); + } + +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStoreTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryTest.java similarity index 74% rename from pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStoreTest.java rename to pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryTest.java index 1e8ae6ca6..125d36505 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryStoreTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/ObjectOutputStreamHistoryTest.java @@ -22,11 +22,15 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.pitest.classinfo.ClassHash; import org.pitest.classinfo.ClassIdentifier; import org.pitest.classinfo.ClassName; import org.pitest.classinfo.HierarchicalClassId; +import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageDatabase; import java.util.Optional; +import java.util.stream.Collectors; + import org.pitest.mutationtest.ClassHistory; import org.pitest.mutationtest.DetectionStatus; import org.pitest.mutationtest.MutationResult; @@ -34,15 +38,18 @@ import org.pitest.mutationtest.engine.MutationIdentifier; import org.pitest.mutationtest.report.MutationTestResultMother; -public class ObjectOutputStreamHistoryStoreTest { +public class ObjectOutputStreamHistoryTest { private static final String COV = BigInteger.TEN.toString(16); - private ObjectOutputStreamHistoryStore testee; + private ObjectOutputStreamHistory testee; @Mock private CoverageDatabase coverage; + @Mock + private CodeSource code; + private final Writer output = new StringWriter(); private final WriterFactory writerFactory = new WriterFactory() { @@ -50,7 +57,7 @@ public class ObjectOutputStreamHistoryStoreTest { @Override public PrintWriter create() { return new PrintWriter( - ObjectOutputStreamHistoryStoreTest.this.output); + ObjectOutputStreamHistoryTest.this.output); } @Override @@ -77,7 +84,7 @@ public void shouldRecordAndRetrieveClassPath() { recordClassPathWithTestee(foo.getId(), bar.getId()); final Reader reader = new StringReader(this.output.toString()); - this.testee = new ObjectOutputStreamHistoryStore(this.writerFactory, + this.testee = new ObjectOutputStreamHistory(this.code, this.writerFactory, Optional.ofNullable(reader)); this.testee.initialize(); @@ -100,7 +107,7 @@ public void shouldRecordAndRetrieveResults() { this.testee.recordResult(mr); final Reader reader = new StringReader(this.output.toString()); - this.testee = new ObjectOutputStreamHistoryStore(this.writerFactory, + this.testee = new ObjectOutputStreamHistory(this.code, this.writerFactory, Optional.ofNullable(reader)); this.testee.initialize(); final Map expected = new HashMap<>(); @@ -111,7 +118,7 @@ public void shouldRecordAndRetrieveResults() { @Test public void shouldNotAttemptToWriteToFileWhenNoneSupplied() { try { - this.testee = new ObjectOutputStreamHistoryStore(this.writerFactory, + this.testee = new ObjectOutputStreamHistory(this.code, this.writerFactory, Optional. empty()); this.testee.initialize(); } catch (final Exception ex) { @@ -133,7 +140,7 @@ public void shouldReadCorruptFiles() throws IOException { this.output.append("rubbish"); final Reader reader = new StringReader(this.output.toString()); - this.testee = new ObjectOutputStreamHistoryStore(this.writerFactory, + this.testee = new ObjectOutputStreamHistory(this.code, this.writerFactory, Optional.ofNullable(reader)); this.testee.initialize(); @@ -142,10 +149,33 @@ public void shouldReadCorruptFiles() throws IOException { private void recordClassPathWithTestee( final HierarchicalClassId... classIdentifiers) { - this.testee = new ObjectOutputStreamHistoryStore(this.writerFactory, + this.testee = new ObjectOutputStreamHistory(this.code, this.writerFactory, Optional. empty()); - final Collection ids = Arrays.asList(classIdentifiers); - this.testee.recordClassPath(ids, this.coverage); + final Collection ids = Arrays.asList(classIdentifiers).stream() + .map(id -> new ClassHash() { + @Override + public ClassIdentifier getId() { + return id.getId(); + } + + @Override + public ClassName getName() { + return id.getName(); + } + + @Override + public BigInteger getDeepHash() { + return BigInteger.ZERO; + } + + @Override + public HierarchicalClassId getHierarchicalId() { + return id; + } + }).collect(Collectors.toList()); + + when(code.fetchClassHashes(any(Collection.class))).thenReturn(ids); + this.testee.processCoverage(this.coverage); } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/Project.java b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/Project.java new file mode 100644 index 000000000..f57b3bf48 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/incremental/Project.java @@ -0,0 +1,69 @@ +package org.pitest.mutationtest.incremental; + +import org.pitest.classinfo.ClassName; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class Project { + private final Path root; + private final List> code; + private final List> tests; + + private final Set modified = new HashSet<>(); + + public Project(Path root, List> code, List> tests) { + this.root = root; + this.code = new ArrayList<>(code); + this.tests = new ArrayList<>(tests); + } + + public String reportsDir() { + return root.resolve("reports").toString(); + } + + public Path root() { + return root; + } + + public List> classes() { + return code; + } + + public List> tests() { + return tests; + } + + public void removeTest(Class toRemove) { + tests.remove(toRemove); + } + + public Optional hasClass(ClassName clazz) { + if (includes(classes(), clazz) || includes(tests(), clazz)) { + return Optional.of(clazz); + } + return Optional.empty(); + } + + public boolean isModified(ClassName clazz) { + return modified.contains(clazz); + } + + private boolean includes(List> cs, ClassName clazz) { + return cs.stream() + .map(ClassName::fromClass) + .anyMatch(c -> c.equals(clazz)); + } + + public void addTest(Class test) { + tests.add(test); + } + + public void modifyClass(Class clazz) { + modified.add(ClassName.fromClass(clazz)); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/tooling/MutationCoverageReportTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/tooling/MutationCoverageReportTest.java index 15a333540..3318c8b74 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/tooling/MutationCoverageReportTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/tooling/MutationCoverageReportTest.java @@ -14,7 +14,6 @@ */ package org.pitest.mutationtest.tooling; -import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyCollection; @@ -25,11 +24,10 @@ import static org.pitest.mutationtest.LocationMother.aLocation; import static org.pitest.mutationtest.LocationMother.aMutationId; -import java.io.File; import java.io.IOException; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Predicate; import org.junit.Before; import org.junit.Test; @@ -47,8 +45,8 @@ import org.pitest.coverage.CoverageGenerator; import org.pitest.help.Help; import org.pitest.help.PitHelpError; +import org.pitest.mutationtest.History; import org.pitest.mutationtest.EngineArguments; -import org.pitest.mutationtest.HistoryStore; import org.pitest.mutationtest.ListenerArguments; import org.pitest.mutationtest.MutationEngineFactory; import org.pitest.mutationtest.MutationResultListener; @@ -88,7 +86,7 @@ public class MutationCoverageReportTest { private CodeSource code; @Mock - private HistoryStore history; + private History history; @Mock private MutationEngineFactory mutationFactory; @@ -110,10 +108,11 @@ public void setUp() { MockitoAnnotations.openMocks(this); this.data = new ReportOptions(); this.data.setSourceDirs(Collections.emptyList()); - when(this.coverage.calculateCoverage()).thenReturn(this.coverageDb); + when(this.coverage.calculateCoverage(any(Predicate.class))).thenReturn(this.coverageDb); when( this.listenerFactory.getListener(any(), any(ListenerArguments.class))).thenReturn(this.listener); + when(history.limitTests()).thenReturn(c -> true); mockMutationEngine(); } @@ -158,39 +157,13 @@ public void shouldRecordClassPath() { when(this.code.getCodeUnderTestNames()).thenReturn( Collections.singleton(clazz)); - when(this.code.getClassInfo(anyCollection())).thenReturn( + when(this.code.fetchClassHashes(anyCollection())).thenReturn( Collections.singletonList(foo)); when(this.coverageDb.getCodeLinesForClass(clazz)).thenReturn(new ClassLines(clazz, Collections.emptySet())); createAndRunTestee(); - verify(this.history).recordClassPath(asList(fooId), this.coverageDb); - } - - @Test - public void ordersHistoryEntries() { - - final ClassName clazz = ClassName.fromClass(Foo.class); - - final HierarchicalClassId fooId = new HierarchicalClassId( - new ClassIdentifier(0, clazz), "0"); - final HierarchicalClassId barId = new HierarchicalClassId( - new ClassIdentifier(0, ClassName.fromString("Bar")), "0"); - final ClassInfo foo = ClassInfoMother.make(fooId.getId()); - final ClassInfo bar = ClassInfoMother.make(barId.getId()); - - when(this.mutater.findMutations(ClassName.fromClass(Foo.class))).thenReturn(aMutantIn(Foo.class)); - - when(this.code.getCodeUnderTestNames()).thenReturn( - Collections.singleton(clazz)); - when(this.code.getClassInfo(anyCollection())).thenReturn( - asList(foo, bar)); - when(this.coverageDb.getCodeLinesForClass(clazz)).thenReturn(new ClassLines(clazz, Collections.emptySet())); - - createAndRunTestee(); - - // bar comes first alphabetically - verify(this.history).recordClassPath(asList(barId, fooId), this.coverageDb); + verify(this.history).processCoverage(this.coverageDb); } @Test @@ -210,7 +183,7 @@ public void shouldReportNoMutationsFoundWhenNoneDetected() { public void shouldNotRunCoverageWhenNoMutationsFound() { this.data.setFailWhenNoMutations(false); createAndRunTestee(); - verify(coverage, never()).calculateCoverage(); + verify(coverage, never()).calculateCoverage(any(Predicate.class)); } @Test diff --git a/pitest/src/main/java/org/pitest/plugin/FeatureSetting.java b/pitest/src/main/java/org/pitest/plugin/FeatureSetting.java index 35bb23ccd..383f92915 100644 --- a/pitest/src/main/java/org/pitest/plugin/FeatureSetting.java +++ b/pitest/src/main/java/org/pitest/plugin/FeatureSetting.java @@ -55,4 +55,9 @@ public Optional getInteger(String key) { return val.map(Integer::parseInt); } + public Optional getBoolean(String key) { + Optional val = this.getString(key); + return val.map(Boolean::parseBoolean); + } + } diff --git a/pitest/src/test/java/org/pitest/junit/JUnitCustomRunnerTestUnitFinderTest.java b/pitest/src/test/java/org/pitest/junit/JUnitCustomRunnerTestUnitFinderTest.java index 13b89d7f6..423895787 100644 --- a/pitest/src/test/java/org/pitest/junit/JUnitCustomRunnerTestUnitFinderTest.java +++ b/pitest/src/test/java/org/pitest/junit/JUnitCustomRunnerTestUnitFinderTest.java @@ -45,7 +45,6 @@ import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.model.InitializationError; import org.junit.runners.model.RunnerBuilder; -import org.mockito.MockitoAnnotations; import org.pitest.junit.RunnerSuiteFinderTest.ThrowsOnDiscoverySuite; import org.pitest.testapi.NullExecutionListener; import org.pitest.testapi.TestGroupConfig; @@ -63,7 +62,6 @@ public class JUnitCustomRunnerTestUnitFinderTest { @Before public void setup() { - MockitoAnnotations.openMocks(this); this.testee = new JUnitCustomRunnerTestUnitFinder(new TestGroupConfig(), Collections.emptyList(), Collections.emptyList()); } diff --git a/pitest/src/test/java/org/pitest/plugin/FeatureParserTest.java b/pitest/src/test/java/org/pitest/plugin/FeatureParserTest.java index dc29dc200..6014f2cd6 100644 --- a/pitest/src/test/java/org/pitest/plugin/FeatureParserTest.java +++ b/pitest/src/test/java/org/pitest/plugin/FeatureParserTest.java @@ -70,6 +70,12 @@ public void shouldParseSingleIntegerConfigValues() { assertThat(actual.getInteger("size")).contains(42); } + @Test + public void shouldParseSingleBooleanConfigValues() { + final FeatureSetting actual = parse("+BAR(on[true])"); + assertThat(actual.getBoolean("on")).contains(true); + } + @Test public void shouldParseMultipleConfigValues() { final FeatureSetting actual = parse("+BAR(name[hello]size[42])"); @@ -90,7 +96,6 @@ public void failsCleanlyWhenBracketsWrongWayRound() { .hasMessageContaining("Could not parse feature. Parameters should be configured with +feature(param[value], param2[value2])"); } - private FeatureSetting parse(String dsl) { final List actual = this.testee.parseFeatures(Collections.singletonList(dsl)); return actual.get(0);