diff --git a/pitest-entry/pom.xml b/pitest-entry/pom.xml
index d4b7a85f3..bf2260517 100644
--- a/pitest-entry/pom.xml
+++ b/pitest-entry/pom.xml
@@ -175,6 +175,19 @@
${testng.version}
test
+
+
+ org.hamcrest
+ hamcrest-core
+ ${hamcrest.version}
+ test
+
+
+ org.hamcrest
+ hamcrest-library
+ ${hamcrest.version}
+ test
+
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 f91735caf..a42b56696 100644
--- a/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java
+++ b/pitest-entry/src/main/java/org/pitest/coverage/CoverageData.java
@@ -15,6 +15,8 @@
package org.pitest.coverage;
+import static java.util.stream.Collectors.toCollection;
+
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
@@ -31,7 +33,6 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.pitest.classinfo.ClassInfo;
@@ -61,7 +62,7 @@ public class CoverageData implements CoverageDatabase {
private final List failingTestDescriptions = new ArrayList<>();
public CoverageData(final CodeSource code, final LineMap lm) {
- this(code, lm, new LinkedHashMap>());
+ this(code, lm, new LinkedHashMap<>());
}
@@ -116,13 +117,10 @@ public int getNumberOfCoveredLines(final Collection mutatedClass) {
@Override
public Collection getTestsForClass(final ClassName clazz) {
- final Set tis = new TreeSet<>(
- new TestInfoNameComparator());
- tis.addAll(this.instructionCoverage.entrySet().stream().filter(isFor(clazz))
- .flatMap(toTests())
- .collect(Collectors.toList())
- );
- return tis;
+ return this.instructionCoverage.entrySet().stream()
+ .filter(isFor(clazz))
+ .flatMap(toTests())
+ .collect(toCollection(() -> new TreeSet<>(new TestInfoNameComparator())));
}
public void calculateClassCoverage(final CoverageResult cr) {
@@ -172,7 +170,7 @@ public Collection getClassesForFile(final String sourceFile,
final Collection value = this.getClassesForFileCache().get(
keyFromSourceAndPackage(sourceFile, packageName));
if (value == null) {
- return Collections. emptyList();
+ return Collections.emptyList();
} else {
return value;
}
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 7dd152469..872c29a7e 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
@@ -28,7 +28,7 @@ public class IncrementalAnalyser implements MutationAnalyser {
private final CodeHistory history;
private final CoverageDatabase coverage;
- private final Map preAnalysed = createStatusMap();
+ private final Map preAnalysed = new EnumMap<>(DetectionStatus.class);
public IncrementalAnalyser(final CodeHistory history,
final CoverageDatabase coverage) {
@@ -36,14 +36,6 @@ public IncrementalAnalyser(final CodeHistory history,
this.coverage = coverage;
}
- private static Map createStatusMap() {
- final EnumMap map = new EnumMap<>(DetectionStatus.class);
- for (final DetectionStatus each : DetectionStatus.values()) {
- map.put(each, 0L);
- }
- return map;
- }
-
@Override
public Collection analyse(
final Collection mutation) {
@@ -67,13 +59,18 @@ public Collection analyse(
}
private void logTotals() {
+ int numberOfReducedMutations = 0;
for (final Entry each : this.preAnalysed.entrySet()) {
- if (each.getValue() != 0) {
- LOG.fine("Incremental analysis set " + each.getValue()
- + " mutations to a status of " + each.getKey());
+ final Long numberOfMutationsInStatus = each.getValue();
+ final DetectionStatus mutationStatus = each.getKey();
+ LOG.fine("Incremental analysis set " + numberOfMutationsInStatus
+ + " mutations to a status of " + mutationStatus);
+ if (mutationStatus != DetectionStatus.NOT_STARTED) {
+ numberOfReducedMutations += numberOfMutationsInStatus;
}
}
+ LOG.info("Incremental analysis reduced number of mutations by " + numberOfReducedMutations );
}
private MutationResult analyseFromHistory(final MutationDetails each,
@@ -89,10 +86,16 @@ private MutationResult analyseFromHistory(final MutationDetails each,
return makeResult(each, DetectionStatus.TIMED_OUT);
}
- if ((mutationStatusTestPair.getStatus() == DetectionStatus.KILLED)
- && killingTestHasNotChanged(each, mutationStatusTestPair)) {
- return makeResult(each, DetectionStatus.KILLED, mutationStatusTestPair
- .getKillingTests(), mutationStatusTestPair.getSucceedingTests());
+ if ((mutationStatusTestPair.getStatus() == DetectionStatus.KILLED)) {
+ final List killingTestNames = filterUnchangedKillingTests(each, mutationStatusTestPair);
+
+ if (!killingTestNames.isEmpty()) {
+ return makeResult(
+ each,
+ DetectionStatus.KILLED,
+ killingTestNames,
+ mutationStatusTestPair.getSucceedingTests());
+ }
}
if ((mutationStatusTestPair.getStatus() == DetectionStatus.SURVIVED)
@@ -104,26 +107,23 @@ && killingTestHasNotChanged(each, mutationStatusTestPair)) {
return analyseFromScratch(each);
}
- private boolean killingTestHasNotChanged(final MutationDetails each,
- final MutationStatusTestPair mutationStatusTestPair) {
- final Collection allTests = this.coverage.getTestsForClass(each
- .getClassName());
+ private List filterUnchangedKillingTests(final MutationDetails each,
+ final MutationStatusTestPair mutationStatusTestPair) {
- final List testClasses = allTests.stream()
- .filter(testIsCalled(mutationStatusTestPair.getKillingTest().get()))
- .map(TestInfo.toDefiningClassName())
+ return this.coverage.getTestsForClass(each.getClassName()).stream()
+ .filter(isAKillingTestFor(mutationStatusTestPair))
+ .filter(testClassDidNotChange())
+ .map(TestInfo::getName)
.collect(Collectors.toList());
+ }
- if (testClasses.isEmpty()) {
- return false;
- }
-
- return !this.history.hasClassChanged(testClasses.get(0));
-
+ private Predicate testClassDidNotChange() {
+ return a -> !this.history.hasClassChanged(TestInfo.toDefiningClassName().apply(a));
}
- private static Predicate testIsCalled(final String testName) {
- return a -> a.getName().equals(testName);
+ private static Predicate isAKillingTestFor(final MutationStatusTestPair mutation) {
+ final List killingTestNames = mutation.getKillingTests();
+ return a -> killingTestNames.contains(a.getName());
}
private MutationResult analyseFromScratch(final MutationDetails mutation) {
@@ -144,10 +144,7 @@ private MutationResult makeResult(final MutationDetails each,
}
private void updatePreanalysedTotal(final DetectionStatus status) {
- if (status != DetectionStatus.NOT_STARTED) {
- final long count = this.preAnalysed.get(status);
- this.preAnalysed.put(status, count + 1);
- }
+ this.preAnalysed.merge(status, 1L, Long::sum);
}
}
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 f1f1c3f3e..087e50fa0 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
@@ -1,15 +1,36 @@
package org.pitest.mutationtest.incremental;
-import static org.junit.Assert.assertEquals;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasItems;
+import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
+import static org.pitest.mutationtest.DetectionStatus.KILLED;
+import static org.pitest.mutationtest.DetectionStatus.NOT_STARTED;
+import static org.pitest.mutationtest.DetectionStatus.SURVIVED;
+import static org.pitest.mutationtest.DetectionStatus.TIMED_OUT;
import static org.pitest.mutationtest.LocationMother.aLocation;
import static org.pitest.mutationtest.LocationMother.aMutationId;
import java.math.BigInteger;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Optional;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -17,12 +38,12 @@
import org.pitest.classinfo.ClassName;
import org.pitest.coverage.CoverageDatabase;
import org.pitest.coverage.TestInfo;
-import java.util.Optional;
import org.pitest.mutationtest.DetectionStatus;
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;
public class IncrementalAnalyserTest {
@@ -34,23 +55,43 @@ public class IncrementalAnalyserTest {
@Mock
private CoverageDatabase coverage;
+ private LogCatcher logCatcher;
+
+
+ @Before
+ public void configureLogCatcher() {
+ logCatcher = new LogCatcher();
+ final Logger logger = Log.getLogger();
+
+ logger.addHandler(logCatcher);
+ logger.setLevel(Level.ALL);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.testee = new IncrementalAnalyser(this.history, this.coverage);
}
+ @After
+ public void removeLogCatcher() {
+ Log.getLogger().removeHandler(logCatcher);
+ }
+
@Test
public void shouldStartNewMutationsAtAStatusOfNotStarted() {
final MutationDetails md = makeMutation("foo");
when(this.history.getPreviousResult(any(MutationIdentifier.class)))
- .thenReturn(Optional. empty());
+ .thenReturn(Optional.empty());
- final Collection actual = this.testee.analyse(Collections
- .singletonList(md));
+ final Collection actual = this.testee.analyse(singletonList(md));
- assertEquals(DetectionStatus.NOT_STARTED, actual.iterator().next()
- .getStatus());
+ assertThat(actual, hasItem(withStatus(NOT_STARTED)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of NOT_STARTED",
+ "Incremental analysis reduced number of mutations by 0"
+ ));
}
@Test
@@ -60,10 +101,14 @@ public void shouldStartPreviousSurvivedMutationsAtAStatusOfNotStartedWhenCoverag
when(
this.history.hasCoverageChanged(any(ClassName.class),
any(BigInteger.class))).thenReturn(true);
- final Collection actual = this.testee.analyse(Collections
- .singletonList(md));
- assertEquals(DetectionStatus.NOT_STARTED, actual.iterator().next()
- .getStatus());
+ final Collection actual = this.testee.analyse(singletonList(md));
+
+ assertThat(actual, hasItem(withStatus(NOT_STARTED)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of NOT_STARTED",
+ "Incremental analysis reduced number of mutations by 0"
+ ));
}
@Test
@@ -73,9 +118,14 @@ public void shouldStartPreviousSurvivedMutationsAtAStatusOfSurvivedWhenCoverageH
when(
this.history.hasCoverageChanged(any(ClassName.class),
any(BigInteger.class))).thenReturn(false);
- final Collection actual = this.testee.analyse(Collections
- .singletonList(md));
- assertEquals(DetectionStatus.SURVIVED, actual.iterator().next().getStatus());
+ final Collection actual = this.testee.analyse(singletonList(md));
+
+ assertThat(actual, hasItem(withStatus(SURVIVED)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of SURVIVED",
+ "Incremental analysis reduced number of mutations by 1"
+ ));
}
@Test
@@ -83,11 +133,14 @@ public void shouldStartPreviousTimedOutMutationsAtAStatusOfNotStartedWhenClassHa
final MutationDetails md = makeMutation("foo");
setHistoryForAllMutationsTo(DetectionStatus.TIMED_OUT);
when(this.history.hasClassChanged(any(ClassName.class))).thenReturn(true);
- final Collection actual = this.testee.analyse(Collections
- .singletonList(md));
+ final Collection actual = this.testee.analyse(singletonList(md));
- assertEquals(DetectionStatus.NOT_STARTED, actual.iterator().next()
- .getStatus());
+ assertThat(actual, hasItem(withStatus(NOT_STARTED)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of NOT_STARTED",
+ "Incremental analysis reduced number of mutations by 0"
+ ));
}
@Test
@@ -95,49 +148,202 @@ public void shouldStartPreviousTimedOutMutationsAtAStatusOfTimedOutWhenClassHasN
final MutationDetails md = makeMutation("foo");
setHistoryForAllMutationsTo(DetectionStatus.TIMED_OUT);
when(this.history.hasClassChanged(any(ClassName.class))).thenReturn(false);
- final Collection actual = this.testee.analyse(Collections
- .singletonList(md));
+ final Collection actual = this.testee.analyse(singletonList(md));
- assertEquals(DetectionStatus.TIMED_OUT, actual.iterator().next()
- .getStatus());
+ assertThat(actual, hasItem(withStatus(TIMED_OUT)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of TIMED_OUT",
+ "Incremental analysis reduced number of mutations by 1"
+ ));
}
@Test
- public void shouldStartPreviousKilledMutationsAtAStatusOfNotStartedWhenNeitherClassOrTestHasChanged() {
+ public void shouldStartPreviousKilledMutationsAtAStatusOfKilledWhenNeitherClassOrTestHasChanged() {
final MutationDetails md = makeMutation("foo");
final String killingTest = "fooTest";
setHistoryForAllMutationsTo(DetectionStatus.KILLED, killingTest);
final Collection tests = Collections.singleton(new TestInfo(
- "TEST_CLASS", killingTest, 0, Optional. empty(), 0));
+ "TEST_CLASS", killingTest, 0, Optional.empty(), 0));
when(this.coverage.getTestsForClass(any(ClassName.class)))
.thenReturn(tests);
when(this.history.hasClassChanged(any(ClassName.class))).thenReturn(false);
- final MutationResult actual = this.testee
- .analyse(Collections.singletonList(md)).iterator().next();
+ final Collection actual = this.testee
+ .analyse(singletonList(md));
+
+ assertThat(actual, hasItem(allOf(withStatus(KILLED), withKillingTest(killingTest))));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of KILLED",
+ "Incremental analysis reduced number of mutations by 1"
+ ));
- assertEquals(DetectionStatus.KILLED, actual.getStatus());
- assertEquals(Optional.ofNullable(killingTest), actual.getKillingTest());
}
@Test
- public void shouldStartPreviousKilledMutationsAtAStatusOfKilledWhenNeitherClassOrTestHasChanged() {
+ public void shouldStartPreviousKilledMutationsAtAStatusOfKilledWhenNeitherClassHasChangedNorTestHasChangedForAtLeastOneKillingTest() {
+ final MutationDetails md = makeMutation("foo");
+ final String killingTestChanged = "fooTest";
+ final String killingTestUnchanged = "killerTest";
+
+ setHistoryForAllMutationsTo(DetectionStatus.KILLED, killingTestChanged, killingTestUnchanged );
+
+ final TestInfo testChanged = new TestInfo("TEST_CLASS_CHANGED", killingTestChanged, 0, Optional.empty(), 0);
+ final TestInfo testUnchanged = new TestInfo("TEST_CLASS_UNCHANGED", killingTestUnchanged, 0, Optional.empty(), 0);
+
+ when(this.coverage.getTestsForClass(ClassName.fromString("clazz")))
+ .thenReturn(asList(testChanged,testUnchanged));
+
+ when(this.history.hasClassChanged(ClassName.fromString("clazz")))
+ .thenReturn(false);
+
+ when(this.history.hasClassChanged(ClassName.fromString("TEST_CLASS_CHANGED")))
+ .thenReturn(true);
+ when(this.history.hasClassChanged(ClassName.fromString("TEST_CLASS_UNCHANGED")))
+ .thenReturn(false);
+
+
+ final Collection actual = this.testee.analyse(singletonList(md));
+
+ assertThat(actual, hasItem(allOf(withStatus(KILLED), withKillingTest(killingTestUnchanged))));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of KILLED",
+ "Incremental analysis reduced number of mutations by 1"
+ ));
+ }
+
+ @Test
+ public void shouldStartPreviousKilledMutationsAtAStatusOfNotStartedWhenTestHasChanged() {
final MutationDetails md = makeMutation("foo");
final String killingTest = "fooTest";
setHistoryForAllMutationsTo(DetectionStatus.KILLED, killingTest);
final Collection tests = Collections.singleton(new TestInfo(
- "TEST_CLASS", killingTest, 0, Optional. empty(), 0));
+ "TEST_CLASS", killingTest, 0, Optional.empty(), 0));
when(this.coverage.getTestsForClass(any(ClassName.class)))
- .thenReturn(tests);
- when(this.history.hasClassChanged(ClassName.fromString("foo"))).thenReturn(
- false);
+ .thenReturn(tests);
+ when(this.history.hasClassChanged(ClassName.fromString("clazz"))).thenReturn(
+ false);
when(this.history.hasClassChanged(ClassName.fromString("TEST_CLASS")))
- .thenReturn(true);
- final MutationResult actual = this.testee
- .analyse(Collections.singletonList(md)).iterator().next();
+ .thenReturn(true);
+
+ final Collection actual = this.testee.analyse(singletonList(md));
- assertEquals(DetectionStatus.NOT_STARTED, actual.getStatus());
+ assertThat(actual, hasItem(withStatus(NOT_STARTED)));
+ assertThat(logCatcher.logEntries,
+ hasItems(
+ "Incremental analysis set 1 mutations to a status of NOT_STARTED",
+ "Incremental analysis reduced number of mutations by 0"
+ ));
+ }
+
+ @Test
+ public void assessMultipleMutationsAtATime() {
+ final MutationDetails md1 = makeMutation("foo");
+ final MutationDetails md2 = makeMutation("bar");
+ final MutationDetails md3 = makeMutation("baz");
+ final MutationDetails md4 = makeMutation("bumm");
+
+ final String killingTest = "killerTest";
+ final TestInfo test = new TestInfo("TEST_CLASS", killingTest, 0, Optional.empty(), 0);
+
+ when(this.history.getPreviousResult(md1.getId()))
+ .thenReturn(Optional.of(
+ new MutationStatusTestPair(
+ 0,
+ KILLED,
+ singletonList(killingTest),
+ emptyList())));
+
+ when(this.history.getPreviousResult(md2.getId()))
+ .thenReturn(Optional.of(
+ new MutationStatusTestPair(
+ 0,
+ KILLED,
+ singletonList(killingTest),
+ emptyList())));
+
+ when(this.history.getPreviousResult(md3.getId()))
+ .thenReturn(Optional.of(
+ new MutationStatusTestPair(0, SURVIVED, emptyList(), emptyList())));
+
+ when(this.history.getPreviousResult(md4.getId()))
+ .thenReturn(Optional.empty());
+
+ when(this.coverage.getTestsForClass(ClassName.fromString("clazz")))
+ .thenReturn(singletonList(test));
+
+ when(this.history.hasClassChanged(ClassName.fromString("clazz")))
+ .thenReturn(false);
+ when(this.history.hasClassChanged(ClassName.fromString("TEST_CLASS")))
+ .thenReturn(false);
+
+ final Collection actual = this.testee.analyse(asList(md1, md2, md3, md4));
+
+ assertThat(actual, contains(
+ withStatus(KILLED),
+ withStatus(KILLED),
+ withStatus(SURVIVED),
+ withStatus(NOT_STARTED)
+ ));
+ 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"
+ ));
+ }
+
+ private Matcher withStatus(final DetectionStatus status) {
+ return new TypeSafeDiagnosingMatcher() {
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("a mutation result with status ").appendValue(status);
+ }
+
+ @Override
+ protected boolean matchesSafely(final MutationResult item,
+ final Description mismatchDescription) {
+ mismatchDescription
+ .appendText("a mutation result with status ")
+ .appendValue(item.getStatus());
+
+ return status.equals(item.getStatus());
+ }
+ };
+ }
+
+ private Matcher withKillingTest(final String expectedKillingTest) {
+ return new TypeSafeDiagnosingMatcher() {
+ @Override
+ public void describeTo(final Description description) {
+ description
+ .appendText("a mutation result with killing test named ")
+ .appendValue(expectedKillingTest);
+ }
+
+ @Override
+ protected boolean matchesSafely(final MutationResult item,
+ final Description mismatchDescription) {
+ Optional itemKillingTest = item.getKillingTest();
+ if (!itemKillingTest.isPresent()) {
+ mismatchDescription
+ .appendText("a mutation result with no killing test");
+ return false;
+ }
+
+ final String killingTestName = itemKillingTest.get();
+ mismatchDescription
+ .appendText("a mutation result with killing test named ")
+ .appendValue(killingTestName);
+
+ return expectedKillingTest.equals(killingTestName);
+ }
+ };
}
private MutationDetails makeMutation(final String method) {
@@ -151,9 +357,25 @@ private void setHistoryForAllMutationsTo(final DetectionStatus status) {
}
private void setHistoryForAllMutationsTo(final DetectionStatus status,
- final String test) {
+ final String... test) {
when(this.history.getPreviousResult(any(MutationIdentifier.class)))
- .thenReturn(Optional.ofNullable(new MutationStatusTestPair(0, status, test)));
+ .thenReturn(Optional.of(
+ new MutationStatusTestPair(0, status, asList(test), emptyList())));
}
+ private static class LogCatcher extends Handler {
+
+ final ArrayList logEntries = new ArrayList<>();
+
+ @Override
+ public void publish(final LogRecord record) {
+ logEntries.add(record.getMessage());
+ }
+
+ @Override
+ public void flush() { }
+
+ @Override
+ public void close() throws SecurityException { }
+ }
}