From ca430f0e98772a6fee7ac4caa1ef88da1f67985c Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Tue, 10 May 2022 09:26:10 +0100 Subject: [PATCH 01/10] support reverse loops --- .../timeout/AvoidForLoopCounterFilter.java | 20 +++++++++++++++++++ .../timeout/AvoidForLoopCounterTest.java | 14 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index a50260bd2..bac84e98a 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -59,6 +59,7 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { .match(Match.never()) .or(conditionalAtEnd()) .or(conditionalAtStart()) + .or(biPushAtStart()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(IGNORE) .withDebug(DEBUG) @@ -109,6 +110,25 @@ private static SequenceQuery conditionalAtStart() { .zeroOrMore(anything()); } + private static SequenceQuery biPushAtStart() { + final Slot counterVariable = Slot.create(Integer.class); + final Slot loopStart = Slot.create(LabelNode.class); + final Slot loopEnd = Slot.create(LabelNode.class); + return QueryStart + .any(AbstractInsnNode.class) + .then(opCode(Opcodes.BIPUSH)) + .then(anIStore(counterVariable.write()).and(debug("store"))) + .then(aLabelNode(loopStart.write()).and(debug("label"))) + .then(anILoadOf(counterVariable.read()).and(debug("load"))) + .then(jumpsTo(loopEnd.write()).and(aConditionalJump())) + .then(isA(LabelNode.class)) + .zeroOrMore(anything()) + .then(targetInstruction(counterVariable).and(debug("target"))) + .then(jumpsTo(loopStart.read()).and(debug("jump"))) + .then(labelNode(loopEnd.read())) + .zeroOrMore(anything()); + } + private static Match loadsAnIntegerToCompareTo() { return opCode(Opcodes.BIPUSH) .or(integerMethodCall()) diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java index 4e9e2c7fa..a9536229c 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java @@ -69,6 +69,20 @@ public void shouldFilterLoopsWithSmallConstant() { this.verifier.assertFiltersNMutationFromClass(1, SmallConstantLoop.class); } + @Test + public void shouldFilterReverseLoops() { + this.verifier.assertFiltersNMutationFromClass(1, ReverseLoop.class); + } + + + + static class ReverseLoop { + void foo() { + for (int i = 9; i > 0; i--) { + System.out.println("" + i); + } + } + } static class LessThanLoop { void foo() { From 3c96c8a1fd178bbdf7f56b36f7d0f15cf3b93c92 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 12 May 2022 15:39:25 +0100 Subject: [PATCH 02/10] Branch contexts with nfa state The slot storage system in the NFA matching library was a bit of hack, storing any matched values even if that branch do not match. Working around this complicated using the matching library as the behaviour was not intuitive. This change fixes the implementation so updates to the context are tied to the state that made them. The behaviour is now 'correct' and much easier to understand. Unfortunately this has two consequences. 1. It is much slower. A 1 second analysis on an example project now takes 8 seconds. 2. The try with resources filter is broken as it relied on peculiar behaviour of the original implementation. Some mitigation has been put in place for the first point. Previously the filters were run in the prescan (because they were so cheap). Most filters will now not be run, unless they have the new type of PRESCAN_FILTER. Before this can be merged the try with resources filter must be fixed and further benchmarks taken with large projects to understand the impact. --- .../analysis/InstructionMatchers.java | 73 +++++---- .../mutationtest/build/InterceptorType.java | 18 ++- .../EqualsPerformanceShortcutFilter.java | 2 +- .../EquivalentReturnMutationFilter.java | 15 +- .../javafeatures/ForEachLoopFilter.java | 10 +- .../javafeatures/ImplicitNullCheckFilter.java | 4 +- .../InlinedFinallyBlockFilter.java | 11 +- .../MethodReferenceNullCheckFilter.java | 4 +- .../javafeatures/TryWithResourcesFilter.java | 111 +++++++------ .../timeout/AvoidForLoopCounterFilter.java | 39 ++--- .../timeout/InfiniteIteratorLoopFilter.java | 2 +- .../tooling/MutationCoverage.java | 7 +- .../java/org/pitest/sequence/Context.java | 60 +++++++ .../main/java/org/pitest/sequence/Match.java | 59 +++++++ .../java/org/pitest/sequence/QueryParams.java | 0 .../java/org/pitest/sequence/QueryStart.java | 0 .../main/java/org/pitest/sequence/Result.java | 24 +++ .../org/pitest/sequence/SequenceMatcher.java | 2 +- .../org/pitest/sequence/SequenceQuery.java | 92 +++++++---- .../main/java/org/pitest/sequence/Slot.java | 0 .../java/org/pitest/sequence/SlotRead.java | 0 .../java/org/pitest/sequence/SlotWrite.java | 0 .../analysis/InstructionMatchersTest.java | 106 +++++++------ .../build/MutationDiscoveryTest.java | 7 + .../intercept/javafeatures/FilterTester.java | 3 +- .../TryWithResourcesFilterTest.java | 3 +- .../timeout/AvoidForLoopCounterTest.java | 12 ++ .../java/org/pitest/sequence/MatchTest.java | 60 +++++++ .../pitest/sequence/SequenceQueryTest.java | 149 ++++++++++++++++++ .../java/org/pitest/sequence/Context.java | 56 ------- .../main/java/org/pitest/sequence/Match.java | 39 ----- .../java/org/pitest/sequence/MatchTest.java | 60 ------- .../pitest/sequence/SequenceQueryTest.java | 117 -------------- 33 files changed, 654 insertions(+), 491 deletions(-) create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/Context.java create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/Match.java rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/QueryParams.java (100%) rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/QueryStart.java (100%) create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/Result.java rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/SequenceMatcher.java (67%) rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/SequenceQuery.java (66%) rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/Slot.java (100%) rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/SlotRead.java (100%) rename {pitest => pitest-entry}/src/main/java/org/pitest/sequence/SlotWrite.java (100%) create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java delete mode 100644 pitest/src/main/java/org/pitest/sequence/Context.java delete mode 100644 pitest/src/main/java/org/pitest/sequence/Match.java delete mode 100644 pitest/src/test/java/org/pitest/sequence/MatchTest.java delete mode 100644 pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java diff --git a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java index d77e0ac65..bba0acf98 100644 --- a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java +++ b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java @@ -10,6 +10,7 @@ import static org.objectweb.asm.Opcodes.ICONST_M1; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.ISTORE; +import static org.pitest.sequence.Result.result; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -41,17 +42,17 @@ public static Match notAnInstruction() { } public static Match opCode(final int opcode) { - return (c, a) -> a.getOpcode() == opcode; + return (c, a) -> result(a.getOpcode() == opcode, c); } public static Match isA( final Class cls) { - return (c, a) -> a.getClass().isAssignableFrom(cls); + return (c, a) -> result(a.getClass().isAssignableFrom(cls), c); } public static Match incrementsVariable(final SlotRead counterVariable) { - return (context, a) -> (a instanceof IincInsnNode) - && context.retrieve(counterVariable).filter(isEqual(((IincInsnNode)a).var)).isPresent(); + return (context, a) -> result((a instanceof IincInsnNode) + && context.retrieve(counterVariable).filter(isEqual(((IincInsnNode)a).var)).isPresent(), context); } public static Match anIStore( @@ -59,9 +60,19 @@ public static Match anIStore( return opCode(Opcodes.ISTORE).and(aVariableAccess(counterVariable)); } + public static Match anILoad( + final SlotWrite counterVariable) { + return opCode(Opcodes.ILOAD).and(aVariableAccess(counterVariable)); + } + public static Match aVariableAccess( final SlotWrite counterVariable) { - return (c, t) -> (t instanceof VarInsnNode) && c.store(counterVariable, ((VarInsnNode) t).var); + return (c, t) -> { + if (t instanceof VarInsnNode) { + return result(true, c.store(counterVariable, ((VarInsnNode) t).var)); + } + return result(false, c); + }; } public static Match anIStoreTo( @@ -76,8 +87,8 @@ public static Match anILoadOf( public static Match variableMatches( final SlotRead counterVariable) { - return (c, t) -> (t instanceof VarInsnNode) - && c.retrieve(counterVariable).filter(isEqual(((VarInsnNode) t).var)).isPresent(); + return (c, t) -> result((t instanceof VarInsnNode) + && c.retrieve(counterVariable).filter(isEqual(((VarInsnNode) t).var)).isPresent(), c); } @@ -100,9 +111,9 @@ public static Match aJump() { } public static Match aConditionalJump() { - return (c, t) -> (t instanceof JumpInsnNode) + return (c, t) -> result((t instanceof JumpInsnNode) && (t.getOpcode() != Opcodes.GOTO) - && (t.getOpcode() != Opcodes.JSR); + && (t.getOpcode() != Opcodes.JSR), c); } public static Match aConditionalJumpTo(Slot label) { @@ -113,19 +124,18 @@ public static Match aConditionalJumpTo(Slot label) public static Match writeNodeToSlot(final SlotWrite slot, final Class clazz) { return (c, t) -> { if (clazz.isAssignableFrom(t.getClass()) ) { - c.store(slot, clazz.cast(t)); - return true; + return result(true, c.store(slot, clazz.cast(t))); } - return false; + return result(false, c); }; } public static Match methodCallThatReturns(final ClassName type) { return (c, t) -> { if ( t instanceof MethodInsnNode ) { - return ((MethodInsnNode) t).desc.endsWith(type.asInternalName() + ";"); + return result(((MethodInsnNode) t).desc.endsWith(type.asInternalName() + ";"), c); } - return false; + return result(false, c); }; } @@ -137,9 +147,9 @@ public static Match methodCallNamed(String name) { return (c, t) -> { if ( t instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) t; - return call.name.equals(name); + return result(call.name.equals(name), c); } - return false; + return result(false, c); }; } @@ -147,24 +157,24 @@ public static Match methodCallTo(final ClassName owner, final return (c, t) -> { if ( t instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) t; - return call.name.equals(name) && call.owner.equals(owner.asInternalName()); + return result( call.name.equals(name) && call.owner.equals(owner.asInternalName()), c); } - return false; + return result(false, c); }; } public static Match isInstruction(final SlotRead target) { - return (c, t) -> c.retrieve(target).get() == t; + return (c, t) -> result(c.retrieve(target).get() == t, c); } public static Match getStatic(String owner, String field) { return (c, t) -> { if (t instanceof FieldInsnNode) { FieldInsnNode fieldNode = (FieldInsnNode) t; - return t.getOpcode() == Opcodes.GETSTATIC && fieldNode.name.equals(field) && fieldNode.owner.equals(owner); + return result( t.getOpcode() == Opcodes.GETSTATIC && fieldNode.name.equals(field) && fieldNode.owner.equals(owner), c); } - return false; + return result(false, c); }; } @@ -174,9 +184,9 @@ public static Match getStatic(String owner, String field) { public static Match recordTarget(final SlotRead target, final SlotWrite found) { return (c, t) -> { if (c.retrieve(target).get() == t) { - c.store(found, true); + return result(true, c.store(found, true)); } - return true; + return result(true, c); }; } @@ -185,10 +195,9 @@ private static Match storeJumpTarget( final SlotWrite label) { return (c, t) -> { if (t instanceof JumpInsnNode ) { - c.store(label, ((JumpInsnNode) t).label); - return true; + return result(true, c.store(label, ((JumpInsnNode) t).label)); } - return false; + return result(false, c); }; } @@ -196,11 +205,11 @@ public static Match jumpsTo( final SlotRead loopStart) { return (context, a) -> { if (!(a instanceof JumpInsnNode)) { - return false; + return result(false, context); } final JumpInsnNode jump = (JumpInsnNode) a; - return context.retrieve(loopStart).filter(isEqual(jump.label)).isPresent(); + return result(context.retrieve(loopStart).filter(isEqual(jump.label)).isPresent(), context); }; } @@ -218,19 +227,19 @@ public static Match labelNode( final SlotRead loopEnd) { return (c, t) -> { if (!(t instanceof LabelNode)) { - return false; + return result(false, c); } final LabelNode l = (LabelNode) t; - return c.retrieve(loopEnd).filter(isEqual(l)).isPresent(); + return result(c.retrieve(loopEnd).filter(isEqual(l)).isPresent(), c); }; } public static Match debug(final String msg) { return (context, a) -> { - context.debug(msg); - return true; + context.debug(msg, a); + return result(true, context); }; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java index 4611863ef..b74267301 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorType.java @@ -8,11 +8,27 @@ * * OTHER - * MODIFY - Modify mutants in a way that is functionally significant (e.g mark as poisoning JVM) + * PRE_SCAN_FILTER - Remove mutants from processing, in prescan and main scan * FILTER - Remove mutants from processing * MODIFY_COSMETIC - Modify mutants in way that will not affect processing (e.g update descriptions) * REPORT - Output mutant in their final state * */ public enum InterceptorType { - OTHER, MODIFY, FILTER, MODIFY_COSMETIC, REPORT + OTHER(true), + MODIFY(true), + PRE_SCAN_FILTER(true), + FILTER(false), + MODIFY_COSMETIC(false), + REPORT(false); + + private final boolean includeInPrescan; + + InterceptorType(boolean includeInPrescan) { + this.includeInPrescan = includeInPrescan; + } + + public boolean includeInPrescan() { + return includeInPrescan; + } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java index aac7438d8..2b1915c58 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java @@ -102,7 +102,7 @@ private Boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { private boolean mutatesAConditionalJump(MethodTree tree, int index) { final AbstractInsnNode mutatedInsns = tree.instruction(index); - return InstructionMatchers.aConditionalJump().test(null, mutatedInsns); + return InstructionMatchers.aConditionalJump().test(null, mutatedInsns).result(); } private Predicate inEqualsMethod() { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java index de4230d94..fb525298c 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EquivalentReturnMutationFilter.java @@ -12,6 +12,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; import static org.pitest.bytecode.analysis.InstructionMatchers.variableMatches; +import static org.pitest.sequence.Result.result; import java.util.Arrays; import java.util.Collection; @@ -151,8 +152,8 @@ private boolean primitiveTrue(int instruction, MethodTree method) { } private boolean boxedTrue(int instruction, MethodTree method) { - final Context context = Context.start(method.instructions(), false); - context.store(MUTATED_INSTRUCTION.write(), method.instruction(instruction)); + Context context = Context.start(); + context = context.store(MUTATED_INSTRUCTION.write(), method.instruction(instruction)); return EQUIVALENT_TRUE.matches(method.instructions(), context); } }; @@ -295,7 +296,7 @@ private static Match aStoreTo(Slot variable) { } private static Match isZeroConstant() { - return (context,node) -> ZERO_CONSTANTS.contains(node.getOpcode()); + return (context,node) -> result(ZERO_CONSTANTS.contains(node.getOpcode()), context); } private Predicate isEquivalent(Mutater m) { @@ -318,8 +319,8 @@ public boolean test(MutationDetails a) { private Boolean returnsZeroValue(SequenceMatcher sequence, MethodTree method, int mutatedInstruction) { - final Context context = Context.start(method.instructions(), false); - context.store(MUTATED_INSTRUCTION.write(), method.instruction(mutatedInstruction)); + Context context = Context.start(); + context = context.store(MUTATED_INSTRUCTION.write(), method.instruction(mutatedInstruction)); return sequence.matches(method.instructions(), context); } @@ -354,9 +355,9 @@ private static Match takesNoArgs() { return (c,node) -> { if (node instanceof MethodInsnNode ) { final MethodInsnNode call = (MethodInsnNode) node; - return Type.getArgumentTypes(call.desc).length == 0; + return result(Type.getArgumentTypes(call.desc).length == 0, c); } - return false; + return result(false, c); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index 3443b0c02..3729146cd 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -15,6 +15,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; +import static org.pitest.sequence.Result.result; import java.util.Collection; import java.util.Iterator; @@ -155,15 +156,14 @@ private static Match aMethodCallReturningAnIterator() { } private static Match mutationPoint() { - return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()); + return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()).and(debug("Mutation point")); } private static Match containMutation(final Slot found) { - return (c, t) -> c.retrieve(found.read()).isPresent(); + return (c, t) -> result(c.retrieve(found.read()).isPresent(), c); } - @Override public InterceptorType type() { return InterceptorType.FILTER; @@ -189,8 +189,8 @@ private Predicate mutatesIteratorLoopPlumbing() { .get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return ITERATOR_LOOP.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java index 9c57d33d8..769761c11 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java @@ -72,8 +72,8 @@ private Predicate isAnImplicitNullCheck() { final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return GET_CLASS_NULL_CHECK.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java index 64a6e3680..2c5439b7e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java @@ -45,6 +45,7 @@ import static org.pitest.functional.FCollection.map; import static org.pitest.functional.FCollection.mapTo; import static org.pitest.functional.prelude.Prelude.not; +import static org.pitest.sequence.Result.result; /** * Detects mutations on same line, but within different code blocks. This @@ -92,9 +93,9 @@ private static Match handlerLabel(Slot handlers) { if (t instanceof LabelNode) { LabelNode label = (LabelNode) t; List labels = c.retrieve(handlers.read()).get(); - return labels.contains(label); + return result(labels.contains(label), c); } - return false; + return result(false, c); }; } @@ -180,9 +181,9 @@ private boolean isInFinallyBlock(MutationDetails m) { AbstractInsnNode mutatedInstruction = method.instruction(m.getInstructionIndex()); - Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - context.store(HANDLERS.write(), handlers); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + context = context.store(HANDLERS.write(), handlers); return IS_IN_HANDLER.matches(method.instructions(), context); } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java index 4c9a0432c..17960e5b2 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java @@ -78,8 +78,8 @@ private Predicate isAnImplicitNullCheck() { final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return NULL_CHECK.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java index b5003b7ca..3e9a38165 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java @@ -35,6 +35,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; import static org.pitest.sequence.QueryStart.any; import static org.pitest.sequence.QueryStart.match; +import static org.pitest.sequence.Result.result; public class TryWithResourcesFilter implements MutationInterceptor { @@ -46,58 +47,71 @@ public class TryWithResourcesFilter implements MutationInterceptor { private static SequenceQuery javac11() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) + .then(closeCallMutationPoint()) + .then(aGotoMutationPoint()) .then(aLabel()) - .then(anALoad()) - .then(closeCall()) + .then(anAStoreMutationPoint()) .then(aLabel()) - .then(aGoto()) + .then(anALoadMutationPoint()) + .then(closeCallMutationPoint()) .then(aLabel()) - .then(anAStore()) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) + .then(aGotoMutationPoint()) + .then(aLabel()) + .then(anAStoreMutationPoint()) + .then(anALoadMutationPoint()) + .then(anALoadMutationPoint()) + .then(addSuppressedCallMutationPoint()) .zeroOrMore(match(anyInstruction())); } private static SequenceQuery javac8() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(ifNull()) - .then(anALoad()) - .then(ifNull()) + .then(ifNullMutationPoint()) + .then(anALoadMutationPoint()) + .then(ifNullMutationPoint()) .then(aLabel()) - .then(anALoad()) - .then(closeCall()) + .then(anALoadMutationPoint()) + .then(closeCallMutationPoint()) .then(aLabel()) - .then(aGoto()) + .then(aGotoMutationPoint()) .then(aLabel()) - .then(anAStore()) + .then(anAStoreMutationPoint()) .then(aLabel()) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) + .then(anALoadMutationPoint()) + .then(anALoadMutationPoint()) + .then(addSuppressedCallMutationPoint()) .then(aLabel()) - .then(aGoto()) + .then(aGotoMutationPoint()) .then(aLabel()) - .then(anALoad()) - .then(closeCall()) + .then(anALoadMutationPoint()) + .then(closeCallMutationPoint()) .zeroOrMore(match(anyInstruction())); } private static SequenceQuery ecj() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(closeCall()) + .then(ifNullMutationPoint()) + .then(anALoadMutationPoint()) + .then(closeCallMutationPoint()) + .then(aGotoMutationPoint()) + .then(aLabel()) + .then(anAStoreMutationPoint()) + .then(anALoadMutationPoint()) + .then(ifNullMutationPoint()) + .then(anALoadMutationPoint()) + .then(closeCallMutationPoint()) .then(aLabel()) - .then(anALoad()) + .then(anALoadMutationPoint()) .then(opCode(ATHROW).and(mutationPoint())) .then(aLabel()) - .then(anAStore()) - .then(anALoad()) - .then(ifNonNull()) - .then(anALoad()) - .then(anAStore()) - .then(aGoto()) + .then(anAStoreMutationPoint()) + .then(anALoadMutationPoint()) + .then(ifNonNullMutationPoint()) + .then(anALoadMutationPoint()) + .then(anAStoreMutationPoint()) + .then(aGotoMutationPoint()) .then(aLabel()) .zeroOrMore(match(anyInstruction())); } @@ -105,17 +119,17 @@ private static SequenceQuery ecj() { private static SequenceQuery ecjAddSuppressedCheck() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(ifNonNull()) - .then(anALoad()) - .then(anAStore()) - .then(aGoto()) + .then(ifNonNullMutationPoint()) + .then(anALoadMutationPoint()) + .then(anAStoreMutationPoint()) + .then(aGotoMutationPoint()) .then(aLabel()) - .then(anALoad()) - .then(anALoad()) + .then(anALoadMutationPoint()) + .then(anALoadMutationPoint()) .then(opCode(IF_ACMPEQ).and(mutationPoint())) - .then(anALoad()) - .then(anALoad()) - .then(addSuppressedCall()) + .then(anALoadMutationPoint()) + .then(anALoadMutationPoint()) + .then(addSuppressedCallMutationPoint()) .then(aLabel()) .zeroOrMore(match(anyInstruction())); } @@ -163,9 +177,10 @@ private Predicate mutatesTryWithResourcesScaffolding() { AbstractInsnNode mutatedInstruction = method.instruction(instruction); - Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return TRY_WITH_RESOURCES.matches(method.instructions(), context); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + boolean result = TRY_WITH_RESOURCES.matches(method.instructions(), context); + return result; }; } @@ -177,31 +192,31 @@ public void end() { private static Match aLabel() { return isA(LabelNode.class); } - private static Match anALoad() { + private static Match anALoadMutationPoint() { return opCode(ALOAD).and(mutationPoint()); } - private static Match aGoto() { + private static Match aGotoMutationPoint() { return opCode(GOTO).and(mutationPoint()); } - private static Match addSuppressedCall() { + private static Match addSuppressedCallMutationPoint() { return methodCallNamed("addSuppressed").and(mutationPoint()); } - private static Match anAStore() { + private static Match anAStoreMutationPoint() { return opCode(ASTORE).and(mutationPoint()); } - private static Match closeCall() { + private static Match closeCallMutationPoint() { return methodCallNamed("close").and(mutationPoint()); } - private static Match ifNonNull() { + private static Match ifNonNullMutationPoint() { return opCode(IFNONNULL).and(mutationPoint()); } - private static Match ifNull() { + private static Match ifNullMutationPoint() { return opCode(IFNULL).and(mutationPoint()); } @@ -210,7 +225,7 @@ private static Match mutationPoint() { } private static Match containMutation(final Slot found) { - return (c, t) -> c.retrieve(found.read()).isPresent(); + return (c, t) -> result(c.retrieve(found.read()).isPresent(), c); } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index bac84e98a..fe958955d 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -3,6 +3,7 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.aConditionalJump; import static org.pitest.bytecode.analysis.InstructionMatchers.aConditionalJumpTo; import static org.pitest.bytecode.analysis.InstructionMatchers.aLabelNode; +import static org.pitest.bytecode.analysis.InstructionMatchers.anILoad; import static org.pitest.bytecode.analysis.InstructionMatchers.anILoadOf; import static org.pitest.bytecode.analysis.InstructionMatchers.anIStore; import static org.pitest.bytecode.analysis.InstructionMatchers.anIntegerConstant; @@ -59,7 +60,6 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { .match(Match.never()) .or(conditionalAtEnd()) .or(conditionalAtStart()) - .or(biPushAtStart()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(IGNORE) .withDebug(DEBUG) @@ -76,8 +76,8 @@ private static SequenceQuery conditionalAtEnd() { return QueryStart .any(AbstractInsnNode.class) .then(anIStore(counterVariable.write()).and(debug("end_counter"))) - .then(isA(LabelNode.class)) - .then(gotoLabel(loopEnd.write())) + .then(isA(LabelNode.class).and(debug("label 1"))) + .then(gotoLabel(loopEnd.write()).and(debug("goto"))) .then(aLabelNode(loopStart.write()).and(debug("loop start"))) .zeroOrMore(anything()) .then(targetInstruction(counterVariable).and(debug("target"))) @@ -91,36 +91,15 @@ private static SequenceQuery conditionalAtEnd() { private static SequenceQuery conditionalAtStart() { - final Slot counterVariable = Slot.create(Integer.class); - final Slot loopStart = Slot.create(LabelNode.class); - final Slot loopEnd = Slot.create(LabelNode.class); - return QueryStart - .any(AbstractInsnNode.class) - .then(anIStore(counterVariable.write()).and(debug("store"))) - .then(aLabelNode(loopStart.write()).and(debug("label"))) - .then(anILoadOf(counterVariable.read()).and(debug("load"))) - .zeroOrMore(QueryStart.match(opCode(Opcodes.ALOAD))) // optionally put object on stack - .then(loadsAnIntegerToCompareTo().and(debug("push"))) - .then(jumpsTo(loopEnd.write()).and(aConditionalJump())) - .then(isA(LabelNode.class)) - .zeroOrMore(anything()) - .then(targetInstruction(counterVariable).and(debug("target"))) - .then(jumpsTo(loopStart.read()).and(debug("jump"))) - .then(labelNode(loopEnd.read())) - .zeroOrMore(anything()); - } - - private static SequenceQuery biPushAtStart() { final Slot counterVariable = Slot.create(Integer.class); final Slot loopStart = Slot.create(LabelNode.class); final Slot loopEnd = Slot.create(LabelNode.class); return QueryStart .any(AbstractInsnNode.class) - .then(opCode(Opcodes.BIPUSH)) - .then(anIStore(counterVariable.write()).and(debug("store"))) - .then(aLabelNode(loopStart.write()).and(debug("label"))) - .then(anILoadOf(counterVariable.read()).and(debug("load"))) - .then(jumpsTo(loopEnd.write()).and(aConditionalJump())) + .then(aLabelNode(loopStart.write()).and(debug("conditional at start label"))) + .then(anILoad(counterVariable.write()).and(debug("iload"))) + .zeroOrMore(anything()) + .then(jumpsTo(loopEnd.write()).and(aConditionalJump()).and(debug("jump"))) .then(isA(LabelNode.class)) .zeroOrMore(anything()) .then(targetInstruction(counterVariable).and(debug("target"))) @@ -176,8 +155,8 @@ private Predicate mutatesAForLoopCounter() { final MethodTree method = AvoidForLoopCounterFilter.this.currentClass.method(a.getId().getLocation()).get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - final Context context = Context.start(method.instructions(), DEBUG); - context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); + Context context = Context.start(DEBUG); + context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return MUTATED_FOR_COUNTER.matches(method.instructions(), context); }; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java index 34b7d4035..0f8e41d2d 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java @@ -57,7 +57,7 @@ private static SequenceQuery doesNotBreakIteratorLoop() { } private boolean isIteratorNext(AbstractInsnNode instruction) { - return InstructionMatchers.methodCallTo(ClassName.fromClass(Iterator.class), "next").test(null, instruction); + return InstructionMatchers.methodCallTo(ClassName.fromClass(Iterator.class), "next").test(null, instruction).result(); } private static SequenceQuery inifniteIteratorLoop() { 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 38ae304b6..d80ec4e2e 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 @@ -36,7 +36,6 @@ import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationConfig; import org.pitest.mutationtest.MutationResultListener; -import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationAnalysisUnit; import org.pitest.mutationtest.build.MutationGrouper; import org.pitest.mutationtest.build.MutationInterceptor; @@ -202,13 +201,13 @@ 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, noReports()); + List mutants = buildMutationTests(new NoCoverage(), new NullHistoryStore(), engine, args, noReportsOrFilters()); this.timings.registerEnd(Timings.Stage.MUTATION_PRE_SCAN); return mutants; } - private Predicate noReports() { - return i -> !i.type().equals(InterceptorType.REPORT); + private Predicate noReportsOrFilters() { + return i -> i.type().includeInPrescan(); } diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context.java b/pitest-entry/src/main/java/org/pitest/sequence/Context.java new file mode 100644 index 000000000..1e00cc3f4 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context.java @@ -0,0 +1,60 @@ +package org.pitest.sequence; + +import java.util.IdentityHashMap; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class Context { + + private final boolean debug; + private final Map slots; + + Context(Map slots, boolean debug) { + this.slots = slots; + this.debug = debug; + } + + public static Context start() { + return start(false); + } + + public static Context start(boolean debug) { + return new Context(new IdentityHashMap<>(), debug); + } + + public Context store(SlotWrite slot, S value) { + Map mutatedSlots = new IdentityHashMap<>(slots); + mutatedSlots.put(slot.slot(), value); + return new Context(mutatedSlots, debug); + } + + @SuppressWarnings("unchecked") + public Optional retrieve(SlotRead slot) { + return Optional.ofNullable((S)slots.get(slot.slot())); + } + + public void debug(String msg, T t) { + if (this.debug) { + System.out.println(msg + " for " + t); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Context context = (Context) o; + return debug == context.debug && Objects.equals(slots, context.slots); + } + + @Override + public int hashCode() { + return slots.hashCode(); + } +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Match.java b/pitest-entry/src/main/java/org/pitest/sequence/Match.java new file mode 100644 index 000000000..514d6f57b --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Match.java @@ -0,0 +1,59 @@ +package org.pitest.sequence; + +import static org.pitest.sequence.Result.result; + +/** + * Predicate with additional context. + * + * Implemented as abstract class as we're still on Java + * 6 and don't have default methods + * + * @param Type to match + */ +@FunctionalInterface +public interface Match { + + Result test(Context c, T t); + + static Match always() { + return (c, t) -> result(true, c); + } + + static Match never() { + return (c, t) -> result(false, c); + } + + static Match isEqual(final Object targetRef) { + return (c, t) -> result(targetRef.equals(t), c); + } + + default Match and(final Match other) { + return (c, t) -> { + Result r = this.test(c,t); + if (!r.result()) { + return r; + } + return other.test(r.context(), t); + }; + } + + default Match negate() { + return (c, t) -> { + Result r = this.test(c,t); + if (!r.result()) { + return result(true, r.context()); + } + return result(false, c); + }; + } + + default Match or(final Match other) { + return (c, t) -> { + Result r = this.test(c,t); + if (r.result()) { + return r; + } + return other.test(c,t); + }; + } +} diff --git a/pitest/src/main/java/org/pitest/sequence/QueryParams.java b/pitest-entry/src/main/java/org/pitest/sequence/QueryParams.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/QueryParams.java rename to pitest-entry/src/main/java/org/pitest/sequence/QueryParams.java diff --git a/pitest/src/main/java/org/pitest/sequence/QueryStart.java b/pitest-entry/src/main/java/org/pitest/sequence/QueryStart.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/QueryStart.java rename to pitest-entry/src/main/java/org/pitest/sequence/QueryStart.java diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Result.java b/pitest-entry/src/main/java/org/pitest/sequence/Result.java new file mode 100644 index 000000000..2af85d0d6 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Result.java @@ -0,0 +1,24 @@ +package org.pitest.sequence; + +public class Result { + private final boolean result; + private final Context context; + + Result(boolean result, Context context) { + this.result = result; + this.context = context; + } + + public static Result result(boolean result, Context context) { + return new Result(result, context); + } + + public boolean result() { + return result; + } + + public Context context() { + return context; + } + +} diff --git a/pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java similarity index 67% rename from pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java rename to pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java index 2f152139b..8e24c53fb 100644 --- a/pitest/src/main/java/org/pitest/sequence/SequenceMatcher.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java @@ -6,5 +6,5 @@ public interface SequenceMatcher { boolean matches(List sequence); - boolean matches(List sequence, Context initialContext); + boolean matches(List sequence, Context initialContext); } diff --git a/pitest/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java similarity index 66% rename from pitest/src/main/java/org/pitest/sequence/SequenceQuery.java rename to pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index 9372a7d6c..26b61a28b 100644 --- a/pitest/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; public class SequenceQuery { @@ -151,58 +152,68 @@ class NFASequenceMatcher implements SequenceMatcher { @Override public boolean matches(List sequence) { - return matches(sequence, Context.start(sequence, this.debug)); + return matches(sequence, Context.start(this.debug)); } @Override - public boolean matches(List sequence, Context context) { - Set> currentState = new HashSet<>(); - addstate(currentState, this.start); + public boolean matches(List sequence, Context context) { + Set> currentState = new HashSet<>(); + addState(currentState, new StateContext<>(this.start, context)); for (final T t : sequence) { - context.moveForward(); - - if (this.ignore.test(context, t)) { + // context not allowed in ignore checks + if (this.ignore.test(null, t).result()) { continue; } - final Set> nextStates = step(context, currentState, t); + final Set> nextStates = step(currentState, t); currentState = nextStates; + } return isMatch(currentState); } - private static void addstate(Set> set, State state) { + private static void addState(Set> set, StateContext state) { if (state == null) { return; } - if (state instanceof Split) { - final Split split = (Split) state; - addstate(set, split.out1); - addstate(set, split.out2); + + if (state.state == null) { + return; + } + + if (state.state instanceof Split) { + final Split split = (Split) state.state; + addState(set, new StateContext(split.out1, state.context)); + addState(set, new StateContext(split.out2, state.context)); } else { set.add(state); } } - private static Set> step(Context context, Set> currentState, T c) { + private static Set> step(Set> currentState, T c) { + + final Set> nextStates = new HashSet<>(); + for (final StateContext each : currentState) { + if (each.state instanceof Consume) { + final Consume consume = (Consume) each.state; - final Set> nextStates = new HashSet<>(); - for (final State each : currentState) { - if (each instanceof Consume) { - final Consume consume = (Consume) each; - if (consume.c.test(context, c)) { - addstate(nextStates, consume.out); + final Result result = consume.c.test(each.context, c); + if (result.result()) { + // note, context updated here + addState(nextStates, new StateContext<>(consume.out, result.context())); } } } return nextStates; } - private static boolean isMatch(Set> currentState) { - return currentState.contains(EndMatch.MATCH); + private static boolean isMatch(Set> currentState) { + return currentState.stream() + .map(c -> c.state) + .anyMatch(s -> s != null && s == EndMatch.MATCH); } } @@ -212,17 +223,14 @@ interface State { } class Consume implements State { - final Match< T> c; - final State out; + final Match c; + final State out; Consume(Match c, State out) { this.c = c; this.out = out; } - boolean matches(Context context, T t) { - return this.c.test(context, t); - } } class Split implements State { @@ -239,3 +247,33 @@ class Split implements State { enum EndMatch implements State { MATCH } + +/** + * Pair class to hold state and context. + */ +class StateContext { + + StateContext(State state, Context context) { + this.state = state; + this.context = context; + } + final State state; + final Context context; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StateContext that = (StateContext) o; + return Objects.equals(state, that.state) && Objects.equals(context, that.context); + } + + @Override + public int hashCode() { + return Objects.hash(state, context); + } +} diff --git a/pitest/src/main/java/org/pitest/sequence/Slot.java b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/Slot.java rename to pitest-entry/src/main/java/org/pitest/sequence/Slot.java diff --git a/pitest/src/main/java/org/pitest/sequence/SlotRead.java b/pitest-entry/src/main/java/org/pitest/sequence/SlotRead.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/SlotRead.java rename to pitest-entry/src/main/java/org/pitest/sequence/SlotRead.java diff --git a/pitest/src/main/java/org/pitest/sequence/SlotWrite.java b/pitest-entry/src/main/java/org/pitest/sequence/SlotWrite.java similarity index 100% rename from pitest/src/main/java/org/pitest/sequence/SlotWrite.java rename to pitest-entry/src/main/java/org/pitest/sequence/SlotWrite.java diff --git a/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java b/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java index c615117ef..de12c91b6 100644 --- a/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java +++ b/pitest-entry/src/test/java/org/pitest/bytecode/analysis/InstructionMatchersTest.java @@ -30,38 +30,39 @@ import org.pitest.classinfo.ClassName; import java.util.Optional; import org.pitest.sequence.Context; +import org.pitest.sequence.Result; import org.pitest.sequence.Slot; public class InstructionMatchersTest { - private final Context context = Context.start(Collections.emptyList()); + private Context context = Context.start(); @Test public void anyInstructionShouldMatchAnything() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(anyInstruction().test(this.context, node)); + assertTrue(anyInstruction().test(this.context, node).result()); } @Test public void opCodeShouldMatchOnOpcode() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(opCode(-1).test(this.context, node)); - assertFalse(opCode(0).test(this.context, node)); + assertTrue(opCode(-1).test(this.context, node).result()); + assertFalse(opCode(0).test(this.context, node).result()); } @Test public void isAShouldMatchOnType() { final AbstractInsnNode node = new InsnNode(-1); - assertTrue(isA(InsnNode.class).test(this.context, node)); - assertFalse(isA(LabelNode.class).test(this.context, node)); + assertTrue(isA(InsnNode.class).test(this.context, node).result()); + assertFalse(isA(LabelNode.class).test(this.context, node).result()); } @Test public void shouldMatchIncrementsToStoredLocalVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 42); + context = context.store(slot.write(), 42); final IincInsnNode node = new IincInsnNode(42, 1); - assertTrue(incrementsVariable(slot.read()).test(this.context,node)); + assertTrue(incrementsVariable(slot.read()).test(context,node).result()); } @Test @@ -69,96 +70,99 @@ public void shouldNotMatchIncrementsToDifferentLocalVariable() { final Slot slot = Slot.create(Integer.class); this.context.store(slot.write(), 42); final IincInsnNode node = new IincInsnNode(42 + 1, 1); - assertFalse(incrementsVariable(slot.read()).test(this.context,node)); + assertFalse(incrementsVariable(slot.read()).test(this.context,node).result()); } @Test public void shouldCaptureIStoreVariable() { final Slot slot = Slot.create(Integer.class); final VarInsnNode node = new VarInsnNode(Opcodes.ISTORE, 3); - assertTrue(anIStore(slot.write()).test(this.context,node)); - assertThat(this.context.retrieve(slot.read())).isEqualTo(Optional.ofNullable(3)); + Result result = anIStore(slot.write()).test(this.context,node); + assertTrue(result.result()); + assertThat(result.context().retrieve(slot.read())).isEqualTo(Optional.ofNullable(3)); } @Test public void shouldMatchAgainstCapturedIStoreVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 3); + context = context.store(slot.write(), 3); final VarInsnNode matchingNode = new VarInsnNode(Opcodes.ISTORE, 3); - assertTrue(anIStoreTo(slot.read()).test(this.context,matchingNode)); + assertTrue(anIStoreTo(slot.read()).test(context,matchingNode).result()); final VarInsnNode nonMatchingNode = new VarInsnNode(Opcodes.ISTORE, 4); - assertFalse(anIStoreTo(slot.read()).test(this.context,nonMatchingNode)); + assertFalse(anIStoreTo(slot.read()).test(context,nonMatchingNode).result()); } @Test public void shouldMatchAgainstCapturedILoadVariable() { final Slot slot = Slot.create(Integer.class); - this.context.store(slot.write(), 3); + this.context = this.context.store(slot.write(), 3); final VarInsnNode matchingNode = new VarInsnNode(Opcodes.ILOAD, 3); - assertTrue(anILoadOf(slot.read()).test(this.context,matchingNode)); + assertTrue(anILoadOf(slot.read()).test(this.context,matchingNode).result()); final VarInsnNode nonMatchingNode = new VarInsnNode(Opcodes.ILOAD, 4); - assertFalse(anILoadOf(slot.read()).test(this.context,nonMatchingNode)); + assertFalse(anILoadOf(slot.read()).test(this.context,nonMatchingNode).result()); } @Test public void shouldMatchAllIntegerConstants() { - assertFalse(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ACONST_NULL)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_M1)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_0)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_1)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_2)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_3)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_4)))); - assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_5)))); + assertFalse(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ACONST_NULL))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_M1))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_0))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_1))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_2))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_3))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_4))).result()); + assertTrue(anIntegerConstant().test(this.context,(new InsnNode(Opcodes.ICONST_5))).result()); } @Test public void shouldCaptureLabels() { final Slot slot = Slot.create(LabelNode.class); final LabelNode label = new LabelNode(); - assertFalse(aLabelNode(slot.write()).test(this.context,new InsnNode(Opcodes.NULL))); - assertTrue(aLabelNode(slot.write()).test(this.context,label)); - assertThat(this.context.retrieve(slot.read())).isEqualTo(Optional.ofNullable(label)); + assertFalse(aLabelNode(slot.write()).test(this.context,new InsnNode(Opcodes.NULL)).result()); + + Result result = aLabelNode(slot.write()).test(this.context,label); + assertTrue(result.result()); + assertThat(result.context().retrieve(slot.read())).isEqualTo(Optional.ofNullable(label)); } @Test public void shouldMatchJumps() { - assertTrue(aJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null))); - assertFalse(aJump().test(this.context, new InsnNode(Opcodes.ACONST_NULL))); + assertTrue(aJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null)).result()); + assertFalse(aJump().test(this.context, new InsnNode(Opcodes.ACONST_NULL)).result()); } @Test public void shouldMatchConditionalJumps() { - assertFalse(aConditionalJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null))); - assertFalse(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.JSR, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGT, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPEQ, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPNE, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNULL, null))); - assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNONNULL, null))); + assertFalse(aConditionalJump().test(this.context,new JumpInsnNode(Opcodes.GOTO, null)).result()); + assertFalse(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.JSR, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFGT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFLE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPGT, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ICMPLE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPEQ, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IF_ACMPNE, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNULL, null)).result()); + assertTrue(aConditionalJump().test(this.context, new JumpInsnNode(Opcodes.IFNONNULL, null)).result()); } @Test public void shouldMatchMethodCallByOwnerAndName() { final ClassName clazz = ClassName.fromString("clazz"); assertTrue(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "name", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "name", "desc", true)).result()); assertFalse(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "notName", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "clazz", "notName", "desc", true)).result()); assertFalse(methodCallTo(clazz, "name") - .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "notClazz", "name", "desc", true))); + .test(this.context, new MethodInsnNode(Opcodes.INVOKEINTERFACE, "notClazz", "name", "desc", true)).result()); } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java index ba8f314ad..63d26da8f 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java @@ -1,6 +1,7 @@ package org.pitest.mutationtest.build; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.pitest.classinfo.ClassByteArraySource; import org.pitest.classinfo.ClassName; @@ -51,6 +52,7 @@ public void shouldFilterMutantsInTryCatchFinallyCompiledWithJavaC() { } @Test + @Ignore public void shouldFilterMutantsInTryCatchFinallyCompiledWithEcj() { this.data.setDetectInlinedCode(true); @@ -60,6 +62,7 @@ public void shouldFilterMutantsInTryCatchFinallyCompiledWithEcj() { } @Test + @Ignore public void shouldFilterMutantsInTryCatchFinallyCompiledWithAspectJ() { this.data.setDetectInlinedCode(true); @@ -78,6 +81,7 @@ public void shouldFilterMutantsInTryFinallyCompiledWithJavaC() { } @Test + @Ignore public void shouldFilterMutantsInTryFinallyCompiledWithEcj() { this.data.setDetectInlinedCode(true); @@ -87,6 +91,7 @@ public void shouldFilterMutantsInTryFinallyCompiledWithEcj() { } @Test + @Ignore public void shouldFilterMutantsInTryFinallyCompiledWithAspectJ() { this.data.setDetectInlinedCode(true); @@ -116,6 +121,7 @@ public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithJavac() { } @Test + @Ignore public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithEcj() { final ClassName clazz = ClassName.fromString("trywithresources/TryWithTwoCloseableExample_ecj"); final Collection actual = findMutants(clazz); @@ -123,6 +129,7 @@ public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithEcj() { } @Test + @Ignore public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithApectj() { final ClassName clazz = ClassName.fromString("trywithresources/TryWithTwoCloseableExample_aspectj"); final Collection actual = findMutants(clazz); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java index e7094ecb4..0ba4fbdc4 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java @@ -107,11 +107,12 @@ public void assertLeavesNMutants(int n, String sample) { final SoftAssertions softly = new SoftAssertions(); for (final Sample s : samples(sample)) { + System.out.println(s.compiler); final List mutations = mutator.findMutations(s.className); final Collection actual = filter(s.clazz, mutations, mutator); softly.assertThat(actual) - .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz) + .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz + " (started with " + mutations.size() + ")") .hasSize(n); } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java index 3e61b9b1a..80c95dc61 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; -import com.example.trywithresources.SimpleCloseCall; +import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.engine.gregor.config.Mutator; @@ -41,6 +41,7 @@ public void shouldWorkWithTryWithNestedTry() { } @Test + @Ignore public void shouldWorkWithTwoClosables() { this.verifier.assertLeavesNMutants(1, "TryWithTwoCloseableExample"); } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java index a9536229c..33d120aae 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterTest.java @@ -4,6 +4,7 @@ import java.util.List; +import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.intercept.javafeatures.FilterTester; @@ -74,7 +75,18 @@ public void shouldFilterReverseLoops() { this.verifier.assertFiltersNMutationFromClass(1, ReverseLoop.class); } + @Test + public void shouldFilterLoopsWithoutInitialiser() { + this.verifier.assertFiltersNMutationFromClass(1, ReverseNoInitialiseLoop.class); + } + static class ReverseNoInitialiseLoop { + void foo(int i) { + for (; i > 0; i--) { + System.out.println("" + i); + } + } + } static class ReverseLoop { void foo() { diff --git a/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java b/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java new file mode 100644 index 000000000..7a8a53e1c --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/MatchTest.java @@ -0,0 +1,60 @@ +package org.pitest.sequence; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class MatchTest { + + private final Context unused = null; + + @Test + public void alwaysShouldAlwaysMatch() { + final Match testee = Match.always(); + assertTrue(testee.test(this.unused, 1).result()); + assertTrue(testee.test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void neverShouldNeverMatch() { + final Match testee = Match.never(); + assertFalse(testee.test(this.unused, 1).result()); + assertFalse(testee.test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void negateShouldInvertLogic() { + final Match testee = Match.never(); + assertTrue(testee.negate().test(this.unused, 1).result()); + assertFalse(testee.negate().negate().test(this.unused, Integer.MAX_VALUE).result()); + } + + @Test + public void isEqualShouldCheckEquality() { + final Match testee = Match.isEqual(1); + assertTrue(testee.test(this.unused, 1).result()); + assertFalse(testee.test(this.unused, 2).result()); + } + + @Test + public void andShouldLogicallyAnd() { + final Match isTrue = Match.always(); + final Match isFalse = Match.never(); + assertTrue(isTrue.and(isTrue).test(this.unused, 1).result()); + assertFalse(isTrue.and(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.and(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.and(isTrue).test(this.unused, 1).result()); + } + + @Test + public void orShouldLogicallyOr() { + final Match isTrue = Match.always(); + final Match isFalse = Match.never(); + assertTrue(isTrue.or(isTrue).test(this.unused, 1).result()); + assertTrue(isTrue.or(isFalse).test(this.unused, 1).result()); + assertFalse(isFalse.or(isFalse).test(this.unused, 1).result()); + assertTrue(isFalse.or(isTrue).test(this.unused, 1).result()); + } + +} diff --git a/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java new file mode 100644 index 000000000..625620282 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java @@ -0,0 +1,149 @@ +package org.pitest.sequence; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.pitest.sequence.Result.result; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class SequenceQueryTest { + + private static List asList(Integer... is) { + return Arrays.asList(is); + } + + @Test + public void shouldMatchSingleLiterals() { + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertFalse(testee.matches(asList(2))); + } + + @Test + public void shouldMatchSimpleSequences() { + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .then(eq(2)) + .then(eq(3)) + .compile(); + + assertTrue(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 2))); + assertFalse(testee.matches(asList(1, 2, 3, 4))); + } + + @Test + public void shouldMatchSimpleOrs() { + final SequenceQuery right = QueryStart.match(eq(2)); + + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .or(right) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertTrue(testee.matches(asList(2))); + assertFalse(testee.matches(asList(3))); + } + + @Test + public void shouldMatchSimpleZeroOrMores() { + final SequenceQuery right = QueryStart.match(eq(2)); + + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .zeroOrMore(right) + .compile(); + + assertTrue(testee.matches(asList(1))); + assertTrue(testee.matches(asList(1, 2))); + assertTrue(testee.matches(asList(1, 2, 2, 2))); + assertFalse(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 3))); + } + + @Test + public void shouldMatchSimpleOneOrMores() { + final SequenceQuery right = QueryStart.match(eq(2)); + + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .oneOrMore(right) + .compile(); + + assertFalse(testee.matches(asList(1))); + assertTrue(testee.matches(asList(1, 2))); + assertTrue(testee.matches(asList(1, 2, 2, 2))); + assertFalse(testee.matches(asList(1, 2, 3))); + assertFalse(testee.matches(asList(1, 3))); + } + + @Test + public void shouldMatchAnyOf() { + final SequenceQuery left = QueryStart.match(eq(2)); + + final SequenceQuery right = QueryStart.match(eq(3)); + + final SequenceMatcher testee = QueryStart.match(eq(1)) + .thenAnyOf(left, right) + .then(eq(99)) + .compile(); + + assertTrue(testee.matches(asList(1, 2, 99))); + assertTrue(testee.matches(asList(1, 3, 99))); + assertFalse(testee.matches(asList(1, 2))); + assertFalse(testee.matches(asList(1, 2, 3, 99))); + } + + @Test + public void shouldSkipItemsMatchingIgnoreList() { + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .then(eq(2)) + .compile(QueryParams.params(Integer.class).withIgnores(eq(99))); + + assertTrue(testee.matches(asList(1, 99, 2))); + } + + @Test + public void contextBranchesWithAnd() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 2, 4); + + Context context = Context.start() + .store(slot1.write(), 2); + + final SequenceMatcher testee = QueryStart + .match(eq(1)) + .then(matchesSlot(slot1.read()).and(write(slot2.write()))) + .then(matchesSlot(slot2.read())) + .then(eq(4)) + .compile(QueryParams.params(Integer.class)); + + assertTrue(testee.matches(sequence, context)); + } + + private Match write(SlotWrite slot) { + return (c, i) -> result(true, c.store(slot, i)); + } + + private Match matchesSlot(SlotRead read) { + return (c, i) -> { + boolean b = c.retrieve(read).get().equals(i); + return result(b,c); + }; + } + + private Match eq(final int i) { + return Match.isEqual(i); + } + +} diff --git a/pitest/src/main/java/org/pitest/sequence/Context.java b/pitest/src/main/java/org/pitest/sequence/Context.java deleted file mode 100644 index 32f268915..000000000 --- a/pitest/src/main/java/org/pitest/sequence/Context.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.pitest.sequence; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import java.util.Optional; - -public class Context { - - private final boolean debug; - private final Map, Object> slots; - private final List sequence; - private int position; - - Context(Map, Object> slots, List sequence, int position, boolean debug) { - this.slots = slots; - this.sequence = sequence; - this.position = position; - this.debug = debug; - } - - public static Context start(List sequence) { - return start(sequence, false); - } - - public static Context start(List sequence, boolean debug) { - return new Context<>(new HashMap<>(), sequence, -1, debug); - } - - public boolean store(SlotWrite slot, S value) { - this.slots.put(slot.slot(), value); - return true; - } - - @SuppressWarnings("unchecked") - public Optional retrieve(SlotRead slot) { - return (Optional) Optional.ofNullable(this.slots.get(slot.slot())); - } - - - void moveForward() { - this.position = this.position + 1; - } - - public int position() { - return this.position; - } - - public void debug(String msg) { - if (this.debug) { - System.out.println(msg + " at " + this.position + " for " + this.sequence.get(this.position)); - } - } - -} diff --git a/pitest/src/main/java/org/pitest/sequence/Match.java b/pitest/src/main/java/org/pitest/sequence/Match.java deleted file mode 100644 index 97aaf9734..000000000 --- a/pitest/src/main/java/org/pitest/sequence/Match.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.pitest.sequence; - -/** - * Predicate with additional context. - * - * Implemented as abstract class as we're still on Java - * 6 and don't have default methods - * - * @param Type to match - */ -@FunctionalInterface -public interface Match { - - boolean test(Context c, T t); - - static Match always() { - return (c, t) -> true; - } - - static Match never() { - return (c, t) -> false; - } - - static Match isEqual(final Object targetRef) { - return (c, t) -> targetRef.equals(t); - } - - default Match and(final Match other) { - return (c, t) -> this.test(c,t) && other.test(c,t); - } - - default Match negate() { - return (c, t) -> !this.test(c,t); - } - - default Match or(final Match other) { - return (c, t) -> this.test(c,t) || other.test(c,t); - } -} diff --git a/pitest/src/test/java/org/pitest/sequence/MatchTest.java b/pitest/src/test/java/org/pitest/sequence/MatchTest.java deleted file mode 100644 index d92e2175c..000000000 --- a/pitest/src/test/java/org/pitest/sequence/MatchTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.pitest.sequence; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class MatchTest { - - private final Context unused = null; - - @Test - public void alwaysShouldAlwaysMatch() { - final Match testee = Match.always(); - assertTrue(testee.test(this.unused, 1)); - assertTrue(testee.test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void neverShouldNeverMatch() { - final Match testee = Match.never(); - assertFalse(testee.test(this.unused, 1)); - assertFalse(testee.test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void negateShouldInvertLogic() { - final Match testee = Match.never(); - assertTrue(testee.negate().test(this.unused, 1)); - assertFalse(testee.negate().negate().test(this.unused, Integer.MAX_VALUE)); - } - - @Test - public void isEqualShouldCheckEquality() { - final Match testee = Match.isEqual(1); - assertTrue(testee.test(this.unused, 1)); - assertFalse(testee.test(this.unused, 2)); - } - - @Test - public void andShouldLogicallyAnd() { - final Match isTrue = Match.always(); - final Match isFalse = Match.never(); - assertTrue(isTrue.and(isTrue).test(this.unused, 1)); - assertFalse(isTrue.and(isFalse).test(this.unused, 1)); - assertFalse(isFalse.and(isFalse).test(this.unused, 1)); - assertFalse(isFalse.and(isTrue).test(this.unused, 1)); - } - - @Test - public void orShouldLogicallyOr() { - final Match isTrue = Match.always(); - final Match isFalse = Match.never(); - assertTrue(isTrue.or(isTrue).test(this.unused, 1)); - assertTrue(isTrue.or(isFalse).test(this.unused, 1)); - assertFalse(isFalse.or(isFalse).test(this.unused, 1)); - assertTrue(isFalse.or(isTrue).test(this.unused, 1)); - } - -} diff --git a/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java b/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java deleted file mode 100644 index efa5c6cf0..000000000 --- a/pitest/src/test/java/org/pitest/sequence/SequenceQueryTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.pitest.sequence; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -public class SequenceQueryTest { - - @Test - public void shouldMatchSingleLiterals() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertFalse(testee.matches( asList(2))); - } - - @Test - public void shouldMatchSimpleSequences() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .then(eq(2)) - .then(eq(3)) - .compile(); - - assertTrue(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 2))); - assertFalse(testee.matches(asList(1, 2, 3, 4))); - } - - @Test - public void shouldMatchSimpleOrs() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .or(right) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertTrue(testee.matches(asList(2))); - assertFalse(testee.matches(asList(3))); - } - - @Test - public void shouldMatchSimpleZeroOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .zeroOrMore(right) - .compile(); - - assertTrue(testee.matches(asList(1))); - assertTrue(testee.matches(asList(1, 2))); - assertTrue(testee.matches(asList(1, 2, 2, 2))); - assertFalse(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 3))); - } - - @Test - public void shouldMatchSimpleOneOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); - - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .oneOrMore(right) - .compile(); - - assertFalse(testee.matches(asList(1))); - assertTrue(testee.matches(asList(1, 2))); - assertTrue(testee.matches(asList(1, 2, 2, 2))); - assertFalse(testee.matches(asList(1, 2, 3))); - assertFalse(testee.matches(asList(1, 3))); - } - - @Test - public void shouldMatchAnyOf() { - final SequenceQuery left = QueryStart.match(eq(2)); - - final SequenceQuery right = QueryStart.match(eq(3)); - - final SequenceMatcher testee = QueryStart.match(eq(1)) - .thenAnyOf(left, right) - .then(eq(99)) - .compile(); - - assertTrue(testee.matches(asList(1, 2, 99))); - assertTrue(testee.matches(asList(1, 3, 99))); - assertFalse(testee.matches(asList(1, 2))); - assertFalse(testee.matches(asList(1, 2, 3, 99))); - } - - @Test - public void shouldSkipItemsMatchingIgnoreList() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) - .then(eq(2)) - .compile(QueryParams.params(Integer.class).withIgnores(eq(99))); - - assertTrue(testee.matches(asList(1, 99, 2))); - } - - private Match eq(final int i) { - return Match.isEqual(i); - } - - private static List asList(Integer... is) { - return Arrays.asList(is); - } - -} From 7e8de0b3a25bca964436257b5102c384ee7ea3e0 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 16 May 2022 10:15:00 +0100 Subject: [PATCH 03/10] extract values from nfa matches --- .../org/pitest/sequence/SequenceMatcher.java | 2 + .../org/pitest/sequence/SequenceQuery.java | 33 +++++--- .../pitest/sequence/SequenceQueryTest.java | 84 ++++++++++++++----- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java index 8e24c53fb..4c976d442 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceMatcher.java @@ -7,4 +7,6 @@ public interface SequenceMatcher { boolean matches(List sequence); boolean matches(List sequence, Context initialContext); + + List contextMatches(List sequence, Context initialContext); } diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index 26b61a28b..1690ec378 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; public class SequenceQuery { @@ -156,13 +157,29 @@ public boolean matches(List sequence) { } @Override - public boolean matches(List sequence, Context context) { + public boolean matches(List sequence, Context initialContext) { + Set> currentState = run(sequence, initialContext); + return currentState.stream() + .map(c -> c.state) + .anyMatch(s -> s != null && s == EndMatch.MATCH); + } + + @Override + public List contextMatches(List sequence, Context initialContext) { + Set> currentState = run(sequence, initialContext); + return currentState.stream() + .filter(s -> s.state != null && s.state == EndMatch.MATCH) + .map(c -> c.context) + .collect(Collectors.toList()); + } + + private Set> run(List sequence, Context initialContext) { Set> currentState = new HashSet<>(); - addState(currentState, new StateContext<>(this.start, context)); + addState(currentState, new StateContext<>(this.start, initialContext)); for (final T t : sequence) { - // context not allowed in ignore checks - if (this.ignore.test(null, t).result()) { + // only initial context used in ignore checks + if (this.ignore.test(initialContext, t).result()) { continue; } @@ -170,7 +187,7 @@ public boolean matches(List sequence, Context context) { currentState = nextStates; } - return isMatch(currentState); + return currentState; } @@ -210,12 +227,6 @@ private static Set> step(Set> currentState, return nextStates; } - private static boolean isMatch(Set> currentState) { - return currentState.stream() - .map(c -> c.state) - .anyMatch(s -> s != null && s == EndMatch.MATCH); - } - } interface State { diff --git a/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java index 625620282..cc596a062 100644 --- a/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java +++ b/pitest-entry/src/test/java/org/pitest/sequence/SequenceQueryTest.java @@ -1,11 +1,14 @@ package org.pitest.sequence; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.pitest.sequence.QueryStart.match; import static org.pitest.sequence.Result.result; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.junit.Test; @@ -17,8 +20,7 @@ private static List asList(Integer... is) { @Test public void shouldMatchSingleLiterals() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .compile(); assertTrue(testee.matches(asList(1))); @@ -27,8 +29,7 @@ public void shouldMatchSingleLiterals() { @Test public void shouldMatchSimpleSequences() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .then(eq(2)) .then(eq(3)) .compile(); @@ -40,10 +41,9 @@ public void shouldMatchSimpleSequences() { @Test public void shouldMatchSimpleOrs() { - final SequenceQuery right = QueryStart.match(eq(2)); + final SequenceQuery right = match(eq(2)); - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .or(right) .compile(); @@ -54,10 +54,9 @@ public void shouldMatchSimpleOrs() { @Test public void shouldMatchSimpleZeroOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); + final SequenceQuery right = match(eq(2)); - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .zeroOrMore(right) .compile(); @@ -70,10 +69,9 @@ public void shouldMatchSimpleZeroOrMores() { @Test public void shouldMatchSimpleOneOrMores() { - final SequenceQuery right = QueryStart.match(eq(2)); + final SequenceQuery right = match(eq(2)); - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .oneOrMore(right) .compile(); @@ -86,11 +84,11 @@ public void shouldMatchSimpleOneOrMores() { @Test public void shouldMatchAnyOf() { - final SequenceQuery left = QueryStart.match(eq(2)); + final SequenceQuery left = match(eq(2)); - final SequenceQuery right = QueryStart.match(eq(3)); + final SequenceQuery right = match(eq(3)); - final SequenceMatcher testee = QueryStart.match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .thenAnyOf(left, right) .then(eq(99)) .compile(); @@ -103,8 +101,7 @@ public void shouldMatchAnyOf() { @Test public void shouldSkipItemsMatchingIgnoreList() { - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .then(eq(2)) .compile(QueryParams.params(Integer.class).withIgnores(eq(99))); @@ -121,8 +118,7 @@ public void contextBranchesWithAnd() { Context context = Context.start() .store(slot1.write(), 2); - final SequenceMatcher testee = QueryStart - .match(eq(1)) + final SequenceMatcher testee = match(eq(1)) .then(matchesSlot(slot1.read()).and(write(slot2.write()))) .then(matchesSlot(slot2.read())) .then(eq(4)) @@ -131,6 +127,54 @@ public void contextBranchesWithAnd() { assertTrue(testee.matches(sequence, context)); } + @Test + public void returnsSingleMatchingContexts() { + Slot slot1 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 3); + + Context context = Context.start(); + + final SequenceMatcher testee = match(eq(1)) + .then(eq(2).and(write(slot1.write()))) + .then(eq(3)) + .compile(QueryParams.params(Integer.class)); + + List actual = testee.contextMatches(sequence, context); + assertThat(actual).hasSize(1); + assertThat(actual.get(0).retrieve(slot1.read())).contains(2); + } + + @Test + public void returnsMultipleMatchingContexts() { + Slot slot1 = Slot.create(Integer.class); + + List sequence = asList(1, 2, 3); + + Context context = Context.start(); + + SequenceQuery a = match(anyThing().and(write(slot1.write()))) + .zeroOrMore(match(anyThing())); + SequenceQuery b = match(eq(1)) + .then(anyThing().and(write(slot1.write()))) + .zeroOrMore(match(anyThing())); + + final SequenceMatcher testee = + a.or(b) + .compile(QueryParams.params(Integer.class)); + + List actual = testee.contextMatches(sequence, context) + .stream().map(c -> c.retrieve(slot1.read()).get()) + .collect(Collectors.toList()); + + assertThat(actual).hasSize(2); + assertThat(actual).containsExactlyInAnyOrder(1,2); + } + + private Match anyThing() { + return (c,i) -> result(true,c); + } + private Match write(SlotWrite slot) { return (c, i) -> result(true, c.store(slot, i)); } From a66e9905ad8e7b1c168105d29c929cb302857124 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 16 May 2022 16:01:12 +0100 Subject: [PATCH 04/10] Rework try with resources filter Rework filter based on the analysis done by the jacoco team. Also puts mitigation in place against the increased analysis cost of properly branching the filter context. --- .../analysis/InstructionMatchers.java | 9 + .../EqualsPerformanceShortcutFilter.java | 14 +- .../javafeatures/ForEachLoopFilter.java | 5 +- .../javafeatures/ImplicitNullCheckFilter.java | 6 + .../MethodReferenceNullCheckFilter.java | 6 +- .../javafeatures/TryWithResourcesFilter.java | 313 +++++++++++------- .../intercept/logging/LoggingCallsFilter.java | 8 +- .../timeout/AvoidForLoopCounterFilter.java | 6 + .../main/java/org/pitest/sequence/Slot.java | 24 +- .../LargeTryWithResources.java | 23 ++ .../build/MutationDiscoveryTest.java | 7 - .../intercept/javafeatures/FilterTester.java | 12 +- .../TryWithResourcesFilterTest.java | 12 +- .../LargeTryWithResources_javac.class.bin | Bin 0 -> 1986 bytes .../LargeTryWithResources_javac11.class.bin | Bin 0 -> 1458 bytes 15 files changed, 300 insertions(+), 145 deletions(-) create mode 100644 pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java create mode 100644 pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin create mode 100644 pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin diff --git a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java index bba0acf98..d7d5351f6 100644 --- a/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java +++ b/pitest-entry/src/main/java/org/pitest/bytecode/analysis/InstructionMatchers.java @@ -153,6 +153,15 @@ public static Match methodCallNamed(String name) { }; } + public static Match methodDescEquals(final String desc) { + return (c, t) -> { + if ( t instanceof MethodInsnNode ) { + return result(((MethodInsnNode) t).desc.equals(desc), c); + } + return result(false, c); + }; + } + public static Match methodCallTo(final ClassName owner, final String name) { return (c, t) -> { if ( t instanceof MethodInsnNode ) { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java index 2b1915c58..a21b98b6f 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java @@ -17,7 +17,6 @@ import org.pitest.bytecode.analysis.InstructionMatchers; import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; -import org.pitest.functional.FCollection; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Location; @@ -60,9 +59,14 @@ public void begin(ClassTree clazz) { @Override public Collection intercept( Collection mutations, Mutater m) { - final List doNotTouch = FCollection.filter(mutations, inEqualsMethod().negate()); + final List doNotTouch = mutations.stream() + .filter(inEqualsMethod().negate()) + .collect(Collectors.toList()); + if (doNotTouch.size() != mutations.size()) { - final List inEquals = FCollection.filter(mutations, inEqualsMethod()); + final List inEquals = mutations.stream() + .filter(inEqualsMethod()) + .collect(Collectors.toList()); final List filtered = filter(inEquals, m); doNotTouch.addAll(filtered); } @@ -83,10 +87,10 @@ private List filter( } private Predicate isShortcutEquals(final MethodTree tree, final Mutater m) { - return a -> shortCutEquals(tree,a, m); + return a -> shortCutEquals(tree, a, m); } - private Boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { + private boolean shortCutEquals(MethodTree tree, MutationDetails a, Mutater m) { if (!mutatesAConditionalJump(tree, a.getInstructionIndex())) { return false; } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index 3729146cd..7a78b5878 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -50,9 +50,8 @@ public class ForEachLoopFilter implements MutationInterceptor { private static final Slot FOUND = Slot.create(Boolean.class); - private static final SequenceMatcher ITERATOR_LOOP = QueryStart - .match(Match.never()) - .or(conditionalAtStart()) + private static final SequenceMatcher ITERATOR_LOOP = + conditionalAtStart() .or(conditionalAtEnd()) .or(arrayConditionalAtEnd()) .or(arrayConditionalAtStart()) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java index 769761c11..5a5cb3252 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ImplicitNullCheckFilter.java @@ -13,6 +13,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; @@ -72,6 +73,11 @@ private Predicate isAnImplicitNullCheck() { final AbstractInsnNode mutatedInstruction = method.instruction(instruction); + // performance hack + if (!(mutatedInstruction instanceof MethodInsnNode)) { + return false; + } + Context context = Context.start(DEBUG); context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return GET_CLASS_NULL_CHECK.matches(method.instructions(), context); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java index 17960e5b2..b7441232e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/MethodReferenceNullCheckFilter.java @@ -12,6 +12,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; @@ -77,7 +78,10 @@ private Predicate isAnImplicitNullCheck() { .get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - + // performance hack + if (!(mutatedInstruction instanceof MethodInsnNode)) { + return false; + } Context context = Context.start(DEBUG); context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return NULL_CHECK.matches(method.instructions(), context); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java index 3e9a38165..cc78f4f0a 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilter.java @@ -13,26 +13,36 @@ import org.pitest.sequence.Context; import org.pitest.sequence.Match; import org.pitest.sequence.QueryParams; +import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; import org.pitest.sequence.SequenceQuery; import org.pitest.sequence.Slot; +import org.pitest.sequence.SlotRead; import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ASTORE; -import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.IFNONNULL; import static org.objectweb.asm.Opcodes.IFNULL; import static org.objectweb.asm.Opcodes.IF_ACMPEQ; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.pitest.bytecode.analysis.InstructionMatchers.anyInstruction; +import static org.pitest.bytecode.analysis.InstructionMatchers.debug; import static org.pitest.bytecode.analysis.InstructionMatchers.isA; import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallNamed; +import static org.pitest.bytecode.analysis.InstructionMatchers.methodDescEquals; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; -import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; +import static org.pitest.bytecode.analysis.InstructionMatchers.writeNodeToSlot; import static org.pitest.sequence.QueryStart.any; import static org.pitest.sequence.QueryStart.match; import static org.pitest.sequence.Result.result; @@ -41,112 +51,165 @@ public class TryWithResourcesFilter implements MutationInterceptor { private static final boolean DEBUG = false; - private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - private static final Slot FOUND = Slot.create(Boolean.class); + private static final Slot> HANDLERS = Slot.createList(LabelNode.class); + + private static final Slot START = Slot.create(AbstractInsnNode.class); + private static final Slot END = Slot.create(AbstractInsnNode.class); + + private ClassTree currentClass; + private Map> cache; + + private static final SequenceMatcher TRY_WITH_RESOURCES = + javac11() + .or(javac()) + .or(ecj()) + .compile(QueryParams.params(AbstractInsnNode.class) + .withIgnores(notAnInstruction().or(aLabel().and(isLabel(HANDLERS.read()).negate()))) + .withDebug(DEBUG) + ); private static SequenceQuery javac11() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(closeCallMutationPoint()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anAStoreMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(closeCallMutationPoint()) - .then(aLabel()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anAStoreMutationPoint()) - .then(anALoadMutationPoint()) - .then(anALoadMutationPoint()) - .then(addSuppressedCallMutationPoint()) + .then(closeSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(closeSequence(false)) + .then(opCode(GOTO)) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall().and(debug("add suppressed"))) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } - private static SequenceQuery javac8() { + private static SequenceQuery javac() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(ifNullMutationPoint()) - .then(anALoadMutationPoint()) - .then(ifNullMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(closeCallMutationPoint()) - .then(aLabel()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anAStoreMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(anALoadMutationPoint()) - .then(addSuppressedCallMutationPoint()) - .then(aLabel()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(closeCallMutationPoint()) + .then(javacCloseSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(ATHROW)) + .then(opCode(ASTORE)) + .then(javacCloseSequence(false)) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } private static SequenceQuery ecj() { return any(AbstractInsnNode.class) .zeroOrMore(match(anyInstruction())) - .then(ifNullMutationPoint()) - .then(anALoadMutationPoint()) - .then(closeCallMutationPoint()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anAStoreMutationPoint()) - .then(anALoadMutationPoint()) - .then(ifNullMutationPoint()) - .then(anALoadMutationPoint()) - .then(closeCallMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(opCode(ATHROW).and(mutationPoint())) - .then(aLabel()) - .then(anAStoreMutationPoint()) - .then(anALoadMutationPoint()) - .then(ifNonNullMutationPoint()) - .then(anALoadMutationPoint()) - .then(anAStoreMutationPoint()) - .then(aGotoMutationPoint()) - .then(aLabel()) + .then(ecjCloseSequence(true)) + .zeroOrMore(match(anyInstruction())) + .then(ecjCloseAndThrow()) + .zeroOrMore(ecjCloseSuppress()) + .then(ecjSuppress()) + .then(opCode(ALOAD)) + .then(opCode(ATHROW).and(recordPoint(END, true))) .zeroOrMore(match(anyInstruction())); } - private static SequenceQuery ecjAddSuppressedCheck() { - return any(AbstractInsnNode.class) - .zeroOrMore(match(anyInstruction())) - .then(ifNonNullMutationPoint()) - .then(anALoadMutationPoint()) - .then(anAStoreMutationPoint()) - .then(aGotoMutationPoint()) - .then(aLabel()) - .then(anALoadMutationPoint()) - .then(anALoadMutationPoint()) - .then(opCode(IF_ACMPEQ).and(mutationPoint())) - .then(anALoadMutationPoint()) - .then(anALoadMutationPoint()) - .then(addSuppressedCallMutationPoint()) - .then(aLabel()) - .zeroOrMore(match(anyInstruction())); + private static SequenceQuery ecjCloseSuppress() { + return ecjCloseSequence(false) + .then(opCode(GOTO)) // FIXME check jump target? + .then(ecjSuppress()) + .then(ecjCloseAndThrow()); } + private static SequenceQuery ecjSuppress() { + return match(opCode(ASTORE)) + .then(opCode(ALOAD)) + .then(opCode(IFNONNULL)) + .then(opCode(ALOAD)) + .then(opCode(ASTORE)) + .then(opCode(GOTO)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(opCode(IF_ACMPEQ)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall()); + } - private static final SequenceMatcher TRY_WITH_RESOURCES = match(Match.never()) - .or(javac11()) - .or(javac8()) - .or(ecj()) - .or(ecjAddSuppressedCheck()) - .then(containMutation(FOUND)) - .compile(QueryParams.params(AbstractInsnNode.class) - .withIgnores(notAnInstruction()) - .withDebug(DEBUG) - ); + private static SequenceQuery ecjCloseSequence(boolean record) { + return match(opCode(ALOAD).and(recordPoint(START,record))) + .then(opCode(IFNULL)) // FIXME check jump target? + .then(opCode(ALOAD)) + .then(closeMethodCall()); + } + + private static SequenceQuery ecjCloseAndThrow() { + return match(opCode(ALOAD)) + .then(opCode(IFNULL)) // FIXME check jump target? + .then(opCode(ALOAD)) + .then(closeMethodCall()) + // omit label check ? + .then(opCode(ALOAD)) + .then(opCode(ATHROW)); + } + + private static SequenceQuery javacCloseSequence(boolean record) { + // javac may (or may not) generate a null check before the close + return methodSequence(record) + .or(fullSequence(record)) + .or(omittedNullCheckSequence(record)) + .or(optimalSequence(record)); + } + + private static SequenceQuery methodSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(closeResourceMethodCall()); + } + + private static SequenceQuery fullSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(omittedNullCheckSequence(false)); + } + + private static SequenceQuery omittedNullCheckSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(IFNULL)) + .then(opCode(ALOAD)) + .then(closeMethodCall()) + .then(opCode(GOTO).and(debug("goto"))) + .then(isLabel(HANDLERS.read()).and(debug("handler"))) + .then(opCode(ASTORE).and(debug("store"))) + .then(opCode(ALOAD)) + .then(opCode(ALOAD)) + .then(addSuppressedMethodCall()) + .then(opCode(GOTO)) + .then(opCode(ALOAD)) + .then(closeMethodCall().and(debug("end of sequence"))); + } + + private static SequenceQuery optimalSequence(boolean record) { + return QueryStart.match(opCode(ALOAD).and(recordPoint(START, record))) + .then(opCode(ALOAD)) + .then(closeResourceMethodCall()); + } + + private static SequenceQuery closeSequence(boolean record) { + // javac may (or may not) generate a null check before the close + return match(closeMethodCall().and(recordPoint(START, record))) + .or(match(opCode(IFNULL).and(recordPoint(START, record))) + .then(opCode(ALOAD)) + .then(closeMethodCall())); + } - private ClassTree currentClass; @Override public InterceptorType type() { @@ -156,6 +219,7 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override @@ -175,57 +239,76 @@ private Predicate mutatesTryWithResourcesScaffolding() { return false; } - AbstractInsnNode mutatedInstruction = method.instruction(instruction); + List regions = cache.computeIfAbsent(method, this::computeRegions); + + return regions.stream() + .anyMatch(r -> instruction >= method.instructions().indexOf(r.start) && instruction <= method.instructions().indexOf(r.end)); - Context context = Context.start(DEBUG); - context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - boolean result = TRY_WITH_RESOURCES.matches(method.instructions(), context); - return result; }; } + private List computeRegions(MethodTree method) { + List handlers = method.rawNode().tryCatchBlocks.stream() + .filter(t -> "java/lang/Throwable".equals(t.type)) + .filter(t -> t.handler != null) + .map(t -> t.handler) + .collect(Collectors.toList()); + + + Context context = Context.start(DEBUG); + context = context.store(HANDLERS.write(), handlers); + List regions = TRY_WITH_RESOURCES.contextMatches(method.instructions(), context).stream() + .map(c -> new Region(c.retrieve(START.read()).get(), c.retrieve(END.read()).get())) + .collect(Collectors.toList()); + return regions; + } + + static class Region { + final AbstractInsnNode start; + final AbstractInsnNode end; + Region(AbstractInsnNode start, AbstractInsnNode end) { + this.start = start; + this.end = end; + } + + } + @Override public void end() { this.currentClass = null; + this.cache = null; } private static Match aLabel() { return isA(LabelNode.class); } - private static Match anALoadMutationPoint() { - return opCode(ALOAD).and(mutationPoint()); - } - private static Match aGotoMutationPoint() { - return opCode(GOTO).and(mutationPoint()); + private static Match isLabel(SlotRead> read) { + return aLabel().and((c,t) -> result(c.retrieve(read).get().contains(t), c)); } - private static Match addSuppressedCallMutationPoint() { - return methodCallNamed("addSuppressed").and(mutationPoint()); + private static Match closeMethodCall() { + return methodCallNamed("close") + .and(opCode(INVOKEINTERFACE).or(opCode(INVOKEVIRTUAL))) + .and(methodDescEquals("()V")); } - private static Match anAStoreMutationPoint() { - return opCode(ASTORE).and(mutationPoint()); + private static Match closeResourceMethodCall() { + return methodCallNamed("$closeResource") + .and(methodDescEquals("(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V")); } - private static Match closeCallMutationPoint() { - return methodCallNamed("close").and(mutationPoint()); + private static Match addSuppressedMethodCall() { + return methodCallNamed("addSuppressed").and(methodDescEquals("(Ljava/lang/Throwable;)V")); } - private static Match ifNonNullMutationPoint() { - return opCode(IFNONNULL).and(mutationPoint()); + private static Match recordPoint(Slot slot, boolean record) { + if (!record) { + return (c,t) -> result(true,c); + } + return writeNodeToSlot(slot.write(), AbstractInsnNode.class); } - private static Match ifNullMutationPoint() { - return opCode(IFNULL).and(mutationPoint()); - } - - private static Match mutationPoint() { - return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()); - } +} - private static Match containMutation(final Slot found) { - return (c, t) -> result(c.retrieve(found.read()).isPresent(), c); - } -} diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java index dba30164b..06480195e 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/logging/LoggingCallsFilter.java @@ -5,6 +5,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; @@ -12,7 +13,6 @@ import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -47,7 +47,9 @@ private void findLoggingLines(MethodTree each, Set lines) { @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(isOnLoggingLine())); + return mutations.stream() + .filter(isOnLoggingLine().negate()) + .collect(Collectors.toList()); } private Predicate isOnLoggingLine() { @@ -80,7 +82,7 @@ class LoggingLineScanner extends MethodVisitor { @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, boolean itf) { - if (FCollection.contains(this.loggingClasses, owner::startsWith)) { + if (this.loggingClasses.stream().anyMatch(owner::startsWith)) { this.lines.add(this.currentLineNumber); } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index fe958955d..7df79f992 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -23,6 +23,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; @@ -155,6 +156,11 @@ private Predicate mutatesAForLoopCounter() { final MethodTree method = AvoidForLoopCounterFilter.this.currentClass.method(a.getId().getLocation()).get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); + // performance hack + if (!(mutatedInstruction instanceof IincInsnNode)) { + return false; + } + Context context = Context.start(DEBUG); context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); return MUTATED_FOR_COUNTER.matches(method.instructions(), context); diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Slot.java b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java index ad8535677..06da33173 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/Slot.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/Slot.java @@ -1,15 +1,21 @@ package org.pitest.sequence; +import java.util.List; + public final class Slot { - public static Slot create(Class clazz) { - return new Slot<>(); - } + public static Slot create(Class clazz) { + return new Slot<>(); + } + + public static Slot> createList(Class clazz) { + return new Slot<>(); + } - public SlotWrite write() { - return new SlotWrite<>(this); - } + public SlotWrite write() { + return new SlotWrite<>(this); + } - public SlotRead read() { - return new SlotRead<>(this); - } + public SlotRead read() { + return new SlotRead<>(this); + } } diff --git a/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java b/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java new file mode 100644 index 000000000..4b3c9149f --- /dev/null +++ b/pitest-entry/src/test/java/com/example/trywithresources/LargeTryWithResources.java @@ -0,0 +1,23 @@ +package com.example.trywithresources; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class LargeTryWithResources { + public static void main(String[] args) { + try (ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos3 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos4 = new ByteArrayOutputStream() + ) { + baos1.flush(); + baos2.flush(); + baos3.flush(); + baos4.flush(); + System.out.println("bo!"); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java index 63d26da8f..ba8f314ad 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java @@ -1,7 +1,6 @@ package org.pitest.mutationtest.build; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.pitest.classinfo.ClassByteArraySource; import org.pitest.classinfo.ClassName; @@ -52,7 +51,6 @@ public void shouldFilterMutantsInTryCatchFinallyCompiledWithJavaC() { } @Test - @Ignore public void shouldFilterMutantsInTryCatchFinallyCompiledWithEcj() { this.data.setDetectInlinedCode(true); @@ -62,7 +60,6 @@ public void shouldFilterMutantsInTryCatchFinallyCompiledWithEcj() { } @Test - @Ignore public void shouldFilterMutantsInTryCatchFinallyCompiledWithAspectJ() { this.data.setDetectInlinedCode(true); @@ -81,7 +78,6 @@ public void shouldFilterMutantsInTryFinallyCompiledWithJavaC() { } @Test - @Ignore public void shouldFilterMutantsInTryFinallyCompiledWithEcj() { this.data.setDetectInlinedCode(true); @@ -91,7 +87,6 @@ public void shouldFilterMutantsInTryFinallyCompiledWithEcj() { } @Test - @Ignore public void shouldFilterMutantsInTryFinallyCompiledWithAspectJ() { this.data.setDetectInlinedCode(true); @@ -121,7 +116,6 @@ public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithJavac() { } @Test - @Ignore public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithEcj() { final ClassName clazz = ClassName.fromString("trywithresources/TryWithTwoCloseableExample_ecj"); final Collection actual = findMutants(clazz); @@ -129,7 +123,6 @@ public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithEcj() { } @Test - @Ignore public void shouldFilterMutantsInTryWithResourcesClosableCompiledWithApectj() { final ClassName clazz = ClassName.fromString("trywithresources/TryWithTwoCloseableExample_aspectj"); final Collection actual = findMutants(clazz); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java index 0ba4fbdc4..89d31e446 100755 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/FilterTester.java @@ -100,6 +100,17 @@ public void assertCombinedMutantExists(Predicate match, Class m.getId().getIndexes().size() > 1)); } + public void assertLeavesNMutants(int n, Class clazz) { + final Sample s = makeSampleForCurrentCompiler(clazz); + GregorMutater mutator = mutateFromClassLoader(); + final List mutations = mutator.findMutations(s.className); + final Collection actual = filter(s.clazz, mutations, mutator); + + assertThat(actual) + .describedAs("Wrong number of mutants with " + s.compiler + " for class \n" + s.clazz + " (started with " + mutations.size() + ")") + .hasSize(n); + } + public void assertLeavesNMutants(int n, String sample) { final GregorMutater mutator = mutateFromResourceDir(); atLeastOneSampleExists(sample); @@ -107,7 +118,6 @@ public void assertLeavesNMutants(int n, String sample) { final SoftAssertions softly = new SoftAssertions(); for (final Sample s : samples(sample)) { - System.out.println(s.compiler); final List mutations = mutator.findMutations(s.className); final Collection actual = filter(s.clazz, mutations, mutator); diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java index 80c95dc61..0f5f8a7f5 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; +import com.example.trywithresources.LargeTryWithResources; import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; @@ -41,7 +42,6 @@ public void shouldWorkWithTryWithNestedTry() { } @Test - @Ignore public void shouldWorkWithTwoClosables() { this.verifier.assertLeavesNMutants(1, "TryWithTwoCloseableExample"); } @@ -51,4 +51,14 @@ public void doesNotFilterProgrammerAddedCloseCalls() { this.verifier.assertLeavesNMutants(3, "SimpleCloseCall"); } + @Test + public void filtersMultiResourceTries() { + this.verifier.assertLeavesNMutants(6, "LargeTryWithResources"); + } + + @Test + public void filtersMultiResourceTriesCurrentCompiler() { + this.verifier.assertLeavesNMutants(6, LargeTryWithResources.class); + } + } diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac.class.bin new file mode 100644 index 0000000000000000000000000000000000000000..a6a1e13b040a0fb9d5d36e2ea3543842b0f2b392 GIT binary patch literal 1986 zcmb7EO-~zF6g>}rjBRXeY)qK6l$i8`0FDDVZ8}iW&=ja-OcDrH)YV`nFx2)~<1qns zSE;I0DN?&=mfg}#rKSrawNkx7UPOwrth%YxAJJ{-d1C`6fl9UHci%nl+;i`{@7hma z{Q5h9EM`LJ!jOVXA#`9!F2g~LU^IYq2$zwO$Q1=+Ap|k5U?PN0WEEUh@UDP=$}F4C zw18*m(u#ohBWukNh~~_)k*}3j4SQK%EfR_6tb$%#(QQ-uEzs+1m{oynu3(ii#sP_4cn+%HM?L`GdbN}H_~QOOD5}!HT{Y0aISB`LF|P&HfEwIGXctp0 zEK4k?5v^+>8IWoZ=+QbE(CG#Q+yFIHSA9~}FJ+n1l~q)~JauK8SXN_tjc8p9%YamS zK#vw-K%^xr1JrOm-M}r?+dwZbL(!X>Pj&A^(Q8d;jF3+wSl7D99%y4Hv?$q8S5c8{ zwW~hT!0c!P2{wp&llf8N6dP~)Bl+|~x~|2@>L)ANMB`fbsM_6>k%$_r?;?=nHvE|H zbsHT;-Zj#UfdCPc^(q8!f4q|;*QuBUyx^rqM%RYq4<{ny&gG7 zxDSB&2nkh*Nx~a6spM2k&;K&<~gDPc^(C%#!Y??_%PjVht1#<3L#g4of!mX P6ujkSXWu5H1MmC`5GH|H literal 0 HcmV?d00001 diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_javac11.class.bin new file mode 100644 index 0000000000000000000000000000000000000000..783a828d5130ff2eb1c0d6872db3159bc79e934b GIT binary patch literal 1458 zcmb7EO>Yxd6g|(L@v|or;yNUaDFNIPnvf4SXWFNKS%ql!8D)&(07}YJ05l zxM0yrl~{MzE{a$pg+*D=1xT$#_WYx42MaN%N#R@H{yjN0{HRZed+%XzK*=-0+=SyBS>pWD|dd0~Ge(Ql7 zlzpe+HT{y)$mW&5;jH?tA8Eb0XPx0)Rpr(M`iJl2H`UK7TT!)*Y$5R7+Qy8o#VjhX zF)eVmO}k!pp%pmuzOPy<&7j^4={ucmKj zRjj@DBbN(w7XnrKX-UZ3hP=r(NhASo~ywsc(0HpiBf_{PS! zxJ2!XUPj`wjqi}NF{9&q48PpyKL~;3!Bs28O{Wx4 z!26YEqx=C?Xf*<-%38f99S&5z!Fe4_qFpv?J7gA+b?sE)u=|FOnRTh+H7IPiT3;_T z>vg_84QE|o;IRL`5aH|56DAD@wd$*qgABU)L(rcH_zEM*^%I`5L_st!zJ_=i(kZU7 z&=^B6_jcO>C(*}K0{!@u76wP=nM~65;y*-gzeY6Q!KDr^cQ9v+zJf8vXPi&k;PMa5 zRO}U`KE$_>&Y7K^or|T-G$|`>ZquFQKu*VTV{5OpsX1et>1gU(ZetgHbOvjDn|G1u zU20=ud5po<*!C9Ud7A8(Y$1aj7Km4I9eLsaZs8tL!9kgrM1V)cU+^0q6XW Date: Tue, 17 May 2022 09:45:18 +0100 Subject: [PATCH 05/10] add additional ecj test --- .../org/pitest/sequence/SequenceQuery.java | 26 +++++++++--------- .../TryWithResourcesFilterTest.java | 1 - .../LargeTryWithResources_ecj.class.bin | Bin 0 -> 1408 bytes 3 files changed, 13 insertions(+), 14 deletions(-) create mode 100644 pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index 1690ec378..f9e84d2ec 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -178,11 +178,6 @@ private Set> run(List sequence, Context initialContext) { addState(currentState, new StateContext<>(this.start, initialContext)); for (final T t : sequence) { - // only initial context used in ignore checks - if (this.ignore.test(initialContext, t).result()) { - continue; - } - final Set> nextStates = step(currentState, t); currentState = nextStates; @@ -210,17 +205,22 @@ private static void addState(Set> set, StateContext state } - private static Set> step(Set> currentState, T c) { + private Set> step(Set> currentState, T c) { final Set> nextStates = new HashSet<>(); for (final StateContext each : currentState) { - if (each.state instanceof Consume) { - final Consume consume = (Consume) each.state; - - final Result result = consume.c.test(each.context, c); - if (result.result()) { - // note, context updated here - addState(nextStates, new StateContext<>(consume.out, result.context())); + // not allowing ignore to update the context + if (this.ignore.test(each.context, c).result()) { + nextStates.add(each); + } else { + if (each.state instanceof Consume) { + final Consume consume = (Consume) each.state; + + final Result result = consume.c.test(each.context, c); + if (result.result()) { + // note, context updated here + addState(nextStates, new StateContext<>(consume.out, result.context())); + } } } } diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java index 0f5f8a7f5..153788cfd 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/intercept/javafeatures/TryWithResourcesFilterTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import com.example.trywithresources.LargeTryWithResources; -import org.junit.Ignore; import org.junit.Test; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.engine.gregor.config.Mutator; diff --git a/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin b/pitest-entry/src/test/resources/sampleClasses/trywithresources/LargeTryWithResources_ecj.class.bin new file mode 100644 index 0000000000000000000000000000000000000000..4be028e33dcd09bbb2cfb3ea52f2a071f2c8ea33 GIT binary patch literal 1408 zcmb7EO-~b16g{s_J8hjxDHZ`m#4m~iRKEP6h*6@6W&mRwjM2@sj4;}En(0*NPw)@8 zb!AxNq9mF~++gC)U*NCM1t#KoW2>0dg`LU0_q}`WdH3CWr=NbjeGgy?*Hr`rCJIg| zZEcvPO3_MtZhhVM)?BOV)ZBtqO&g}WYUSMe1Mc2$?W#}&;!n-zX1ZvWSJO-Rr&hre z2+i7M+nWW&D3;8J=1)?Xp03>XGp$!>s4p<<-pavWiZDe&1m`>6>-Wy5YKJeW~VEY93LR zS&AZ#h!jUPBxTycC&gNIErMg{Rnha;&9n8YXO%Sc;W%&Y)L4Y%uVcx6k7qVHGhy@- zkaq^77{H*4QvzMBEDqo_oW>aewIV}{yhbwB?iMn;a~jV3*%gXTm1R!hf{Ni5t>2g2 zn(M5~!fMblA`mlIR?DUy0uZTDxU3>HH`b+ z^pnhbX5m@RH4E&f!eL?Ky4qBpRm_`CbzETJcYWGcpB?m(6HPtY)Kdb2NBpVofrJQ8;QlHo>5vxuw{fs8|dm)qzFn8qIiSu z#EAw@_J(%RBX`bjB5uf4*k9>8y}@1RGJu{l3;IWdM~qFx^pV#H>mv;eNkG^K5UfZr zkN78;fUpnHcSd(H&y)zAZO2QuD&6`{JFmWD=naf+u}DL>i6xfdBp$=2PT~b#Qfp}7 z1GR!LeB|#dpE349mUx1GAWRuyM3m8o$rR%hAHhYIc$)J+gHCcM@C{wqL$}|KIiARp zGk%gO_MW;SGYt=A(M^Ew1VWD#1&d0GAOu8K7kCQmeIY>lvNe8DpLL%SJ8YS lxUq+*FsLJuJwy&zcbj|a4d2)&A>p%9j451PBYOaIKLNIsKSlrm literal 0 HcmV?d00001 From 3e1dbfa0f6d144228346b3ebf70857ed9da03da1 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Tue, 17 May 2022 11:25:02 +0100 Subject: [PATCH 06/10] performance hacks --- .../javafeatures/ForEachLoopFilter.java | 32 ++++++++++++------- .../timeout/AvoidForLoopCounterFilter.java | 7 ++-- .../org/pitest/sequence/SequenceQuery.java | 31 ++++++++++-------- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index 7a78b5878..e845017c7 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -19,17 +19,16 @@ import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.LabelNode; import org.pitest.bytecode.analysis.ClassTree; -import org.pitest.bytecode.analysis.MethodMatchers; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -79,7 +78,7 @@ private static SequenceQuery conditionalAtEnd() { .zeroOrMore(QueryStart.match(anyInstruction())) .then(labelNode(loopEnd.read())) .then(opCode(Opcodes.ALOAD)) - .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext").and(mutationPoint())) + .then(hasNextMethodCall().and(mutationPoint())) .then(aConditionalJumpTo(loopStart).and(mutationPoint())) .zeroOrMore(QueryStart.match(anyInstruction())); } @@ -95,7 +94,7 @@ private static SequenceQuery conditionalAtStart() { .then(opCode(Opcodes.ASTORE)) .then(aLabelNode(loopStart.write())) .then(opCode(Opcodes.ALOAD)) - .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext").and(mutationPoint())) + .then(hasNextMethodCall().and(mutationPoint())) .then(aConditionalJump().and(jumpsTo(loopEnd.write())).and(mutationPoint())) .then(opCode(Opcodes.ALOAD)) .then(methodCallTo(ClassName.fromString("java/util/Iterator"), "next").and(mutationPoint())) @@ -105,7 +104,6 @@ private static SequenceQuery conditionalAtStart() { .zeroOrMore(QueryStart.match(anyInstruction())); } - private static SequenceQuery arrayConditionalAtEnd() { final Slot loopStart = Slot.create(LabelNode.class); final Slot loopEnd = Slot.create(LabelNode.class); @@ -149,6 +147,9 @@ private static SequenceQuery arrayConditionalAtStart() { .zeroOrMore(QueryStart.match(anyInstruction())); } + private static Match hasNextMethodCall() { + return methodCallTo(ClassName.fromString("java/util/Iterator"), "hasNext"); + } private static Match aMethodCallReturningAnIterator() { return methodCallThatReturns(ClassName.fromClass(Iterator.class)); @@ -176,16 +177,20 @@ public void begin(ClassTree clazz) { @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(mutatesIteratorLoopPlumbing())); + return mutations.stream().filter(mutatesIteratorLoopPlumbing().negate()) + .collect(Collectors.toList()); } private Predicate mutatesIteratorLoopPlumbing() { return a -> { final int instruction = a.getInstructionIndex(); - final MethodTree method = currentClass.methods().stream() - .filter(MethodMatchers.forLocation(a.getId().getLocation())) - .findFirst() - .get(); + final MethodTree method = currentClass.method(a.getId().getLocation()).get(); + + //performance hack + if (!mightContainForLoop(method.instructions())) { + return false; + } + final AbstractInsnNode mutatedInstruction = method.instruction(instruction); Context context = Context.start(DEBUG); @@ -194,6 +199,11 @@ private Predicate mutatesIteratorLoopPlumbing() { }; } + private boolean mightContainForLoop(List instructions) { + return instructions.stream() + .anyMatch(i -> hasNextMethodCall().or(opCode(Opcodes.ARRAYLENGTH)).test(null, i).result()); + } + @Override public void end() { this.currentClass = null; diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index 7df79f992..56c58d164 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -28,8 +29,6 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.pitest.bytecode.analysis.ClassTree; import org.pitest.bytecode.analysis.MethodTree; -import org.pitest.functional.FCollection; -import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.build.InterceptorType; import org.pitest.mutationtest.build.MutationInterceptor; import org.pitest.mutationtest.engine.Mutater; @@ -147,7 +146,9 @@ public void begin(ClassTree clazz) { @Override public Collection intercept( Collection mutations, Mutater m) { - return FCollection.filter(mutations, Prelude.not(mutatesAForLoopCounter())); + return mutations.stream() + .filter(mutatesAForLoopCounter().negate()) + .collect(Collectors.toList()); } private Predicate mutatesAForLoopCounter() { diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index f9e84d2ec..32f2dd2f7 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -178,6 +178,11 @@ private Set> run(List sequence, Context initialContext) { addState(currentState, new StateContext<>(this.start, initialContext)); for (final T t : sequence) { + // only initial context used in ignore checks + if (this.ignore.test(initialContext, t).result()) { + continue; + } + final Set> nextStates = step(currentState, t); currentState = nextStates; @@ -205,22 +210,20 @@ private static void addState(Set> set, StateContext state } - private Set> step(Set> currentState, T c) { + private static Set> step(Set> currentState, T c) { + + // adhoc testing suggests setting the initial HashSet size saves 15% of analysis + // execution time + final Set> nextStates = new HashSet<>(currentState.size()); - final Set> nextStates = new HashSet<>(); for (final StateContext each : currentState) { - // not allowing ignore to update the context - if (this.ignore.test(each.context, c).result()) { - nextStates.add(each); - } else { - if (each.state instanceof Consume) { - final Consume consume = (Consume) each.state; - - final Result result = consume.c.test(each.context, c); - if (result.result()) { - // note, context updated here - addState(nextStates, new StateContext<>(consume.out, result.context())); - } + if (each.state instanceof Consume) { + final Consume consume = (Consume) each.state; + + final Result result = consume.c.test(each.context, c); + if (result.result()) { + // note, context updated here + addState(nextStates, new StateContext<>(consume.out, result.context())); } } } From bd820bbb200a163bc3ffb52a469039df6508b642 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Tue, 17 May 2022 11:59:03 +0100 Subject: [PATCH 07/10] optimise static analysis --- .../EqualsPerformanceShortcutFilter.java | 2 +- .../javafeatures/ForEachLoopFilter.java | 32 +++++++++++------ .../timeout/AvoidForLoopCounterFilter.java | 36 ++++++++++++++----- .../timeout/InfiniteForLoopFilter.java | 5 +-- .../timeout/InfiniteIteratorLoopFilter.java | 5 +-- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java index a21b98b6f..0d5d51d75 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/equivalent/EqualsPerformanceShortcutFilter.java @@ -30,7 +30,7 @@ public class EqualsPerformanceShortcutFilter implements MutationInterceptor { private static final boolean DEBUG = false; - // Looks fairly specifically for a conditional mutated to a unconditional + // Looks fairly specifically for a conditional mutated to an unconditional // rather than any always false condition static final SequenceMatcher ALWAYS_FALSE = QueryStart .any(AbstractInsnNode.class) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index e845017c7..55ad31017 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -14,12 +14,15 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.methodCallTo; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; -import static org.pitest.bytecode.analysis.InstructionMatchers.recordTarget; import static org.pitest.sequence.Result.result; +import java.util.ArrayList; import java.util.Collection; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -45,22 +48,20 @@ public class ForEachLoopFilter implements MutationInterceptor { private static final boolean DEBUG = false; - private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - private static final Slot FOUND = Slot.create(Boolean.class); - + private static final Slot> LOOP_INSTRUCTIONS = Slot.createList(AbstractInsnNode.class); private static final SequenceMatcher ITERATOR_LOOP = conditionalAtStart() .or(conditionalAtEnd()) .or(arrayConditionalAtEnd()) .or(arrayConditionalAtStart()) - .then(containMutation(FOUND)) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) .withDebug(DEBUG) ); private ClassTree currentClass; + private Map> cache; private static SequenceQuery conditionalAtEnd() { @@ -156,10 +157,12 @@ private static Match aMethodCallReturningAnIterator() { } private static Match mutationPoint() { - return recordTarget(MUTATED_INSTRUCTION.read(), FOUND.write()).and(debug("Mutation point")); + return (c,t) -> { + c.retrieve(LOOP_INSTRUCTIONS.read()).get().add(t); + return result(true, c); + }; } - private static Match containMutation(final Slot found) { return (c, t) -> result(c.retrieve(found.read()).isPresent(), c); } @@ -172,6 +175,7 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override @@ -193,12 +197,19 @@ private Predicate mutatesIteratorLoopPlumbing() { final AbstractInsnNode mutatedInstruction = method.instruction(instruction); - Context context = Context.start(DEBUG); - context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return ITERATOR_LOOP.matches(method.instructions(), context); + Set toAvoid = cache.computeIfAbsent(method, this::findLoopInstructions); + + return toAvoid.contains(mutatedInstruction); }; } + private Set findLoopInstructions(MethodTree method) { + Context context = Context.start(DEBUG).store(LOOP_INSTRUCTIONS.write(), new ArrayList<>()); + return ITERATOR_LOOP.contextMatches(method.instructions(), context).stream() + .flatMap(c -> c.retrieve(LOOP_INSTRUCTIONS.read()).get().stream()) + .collect(Collectors.toSet()); + } + private boolean mightContainForLoop(List instructions) { return instructions.stream() .anyMatch(i -> hasNextMethodCall().or(opCode(Opcodes.ARRAYLENGTH)).test(null, i).result()); @@ -207,5 +218,6 @@ private boolean mightContainForLoop(List instructions) { @Override public void end() { this.currentClass = null; + this.cache = null; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index 56c58d164..f808a276d 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -12,13 +12,17 @@ import static org.pitest.bytecode.analysis.InstructionMatchers.gotoLabel; import static org.pitest.bytecode.analysis.InstructionMatchers.incrementsVariable; import static org.pitest.bytecode.analysis.InstructionMatchers.isA; -import static org.pitest.bytecode.analysis.InstructionMatchers.isInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.jumpsTo; import static org.pitest.bytecode.analysis.InstructionMatchers.labelNode; import static org.pitest.bytecode.analysis.InstructionMatchers.notAnInstruction; import static org.pitest.bytecode.analysis.InstructionMatchers.opCode; +import static org.pitest.sequence.Result.result; import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -40,6 +44,7 @@ import org.pitest.sequence.SequenceMatcher; import org.pitest.sequence.SequenceQuery; import org.pitest.sequence.Slot; +import org.pitest.sequence.SlotWrite; /** * Removes mutants that affect for loop counters as these have @@ -56,9 +61,7 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { private static final Slot MUTATED_INSTRUCTION = Slot.create(AbstractInsnNode.class); - static final SequenceMatcher MUTATED_FOR_COUNTER = QueryStart - .match(Match.never()) - .or(conditionalAtEnd()) + static final SequenceMatcher MUTATED_FOR_COUNTER = conditionalAtEnd() .or(conditionalAtStart()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(IGNORE) @@ -67,6 +70,7 @@ public class AvoidForLoopCounterFilter implements MutationInterceptor { private ClassTree currentClass; + private Map> cache; private static SequenceQuery conditionalAtEnd() { @@ -130,7 +134,12 @@ private static Match integerMethodCall() { } private static Match targetInstruction(Slot counterVariable) { - return incrementsVariable(counterVariable.read()).and(isInstruction(MUTATED_INSTRUCTION.read())); + return incrementsVariable(counterVariable.read()) + .and(recordInstruction(MUTATED_INSTRUCTION.write())); + } + + private static Match recordInstruction(SlotWrite slot) { + return (c,t) -> result(true, c.store(slot, t)); } @Override @@ -141,6 +150,7 @@ public InterceptorType type() { @Override public void begin(ClassTree clazz) { this.currentClass = clazz; + this.cache = new IdentityHashMap<>(); } @Override @@ -162,15 +172,25 @@ private Predicate mutatesAForLoopCounter() { return false; } - Context context = Context.start(DEBUG); - context = context.store(MUTATED_INSTRUCTION.write(), mutatedInstruction); - return MUTATED_FOR_COUNTER.matches(method.instructions(), context); + Set loopIncrements = cache.computeIfAbsent(method, this::findLoopCounters); + + return loopIncrements.contains(mutatedInstruction); }; } + private Set findLoopCounters(MethodTree method) { + Context context = Context.start(DEBUG); + return MUTATED_FOR_COUNTER.contextMatches(method.instructions(), context).stream() + .map(c -> c.retrieve(MUTATED_INSTRUCTION.read())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + } + @Override public void end() { this.currentClass = null; + this.cache = null; } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java index 0d0172bb9..ff39e6d90 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteForLoopFilter.java @@ -22,7 +22,6 @@ import org.objectweb.asm.tree.LabelNode; import org.pitest.bytecode.analysis.MethodTree; import org.pitest.mutationtest.engine.MutationDetails; -import org.pitest.sequence.Match; import org.pitest.sequence.QueryParams; import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; @@ -37,9 +36,7 @@ public class InfiniteForLoopFilter extends InfiniteLoopFilter { private static final boolean DEBUG = false; - static final SequenceMatcher INFINITE_LOOP = QueryStart - .match(Match.never()) - .or(countingLoopWithoutWriteConditionalAtStart()) + static final SequenceMatcher INFINITE_LOOP = countingLoopWithoutWriteConditionalAtStart() .or(countingLoopWithoutWriteConditionAtEnd()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java index 0f8e41d2d..7f43a6e09 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteIteratorLoopFilter.java @@ -18,7 +18,6 @@ import org.pitest.bytecode.analysis.MethodTree; import org.pitest.classinfo.ClassName; import org.pitest.mutationtest.engine.MutationDetails; -import org.pitest.sequence.Match; import org.pitest.sequence.QueryParams; import org.pitest.sequence.QueryStart; import org.pitest.sequence.SequenceMatcher; @@ -32,9 +31,7 @@ public class InfiniteIteratorLoopFilter extends InfiniteLoopFilter { private static final boolean DEBUG = false; - static final SequenceMatcher INFINITE_LOOP = QueryStart - .match(Match.never()) - .or(inifniteIteratorLoop()) + static final SequenceMatcher INFINITE_LOOP = inifniteIteratorLoop() .or(infiniteIteratorLoopJavac()) .compile(QueryParams.params(AbstractInsnNode.class) .withIgnores(notAnInstruction()) From fbc3215ea0e74350e471c7e8b3c1e42efa17c5b3 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Tue, 17 May 2022 16:26:06 +0100 Subject: [PATCH 08/10] do not consider null in equality checks --- .../java/org/pitest/sequence/Context.java | 7 ++- .../org/pitest/sequence/SequenceQuery.java | 46 +++---------------- .../org/pitest/sequence/StateContext.java | 33 +++++++++++++ .../java/org/pitest/sequence/ContextTest.java | 16 +++++++ .../org/pitest/sequence/StateContextTest.java | 13 ++++++ 5 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/StateContext.java create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context.java b/pitest-entry/src/main/java/org/pitest/sequence/Context.java index 1e00cc3f4..70b69452a 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/Context.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context.java @@ -3,10 +3,9 @@ import java.util.IdentityHashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; -public class Context { +public final class Context { private final boolean debug; private final Map slots; @@ -46,11 +45,11 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof Context)) { return false; } Context context = (Context) o; - return debug == context.debug && Objects.equals(slots, context.slots); + return slots.equals(context.slots); } @Override diff --git a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java index 32f2dd2f7..06271e90f 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/SequenceQuery.java @@ -2,7 +2,6 @@ import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -192,18 +191,15 @@ private Set> run(List sequence, Context initialContext) { private static void addState(Set> set, StateContext state) { - if (state == null) { - return; - } - - if (state.state == null) { - return; - } if (state.state instanceof Split) { final Split split = (Split) state.state; - addState(set, new StateContext(split.out1, state.context)); - addState(set, new StateContext(split.out2, state.context)); + if (split.out1 != null) { + addState(set, new StateContext(split.out1, state.context)); + } + if (split.out2 != null) { + addState(set, new StateContext(split.out2, state.context)); + } } else { set.add(state); } @@ -261,33 +257,3 @@ class Split implements State { enum EndMatch implements State { MATCH } - -/** - * Pair class to hold state and context. - */ -class StateContext { - - StateContext(State state, Context context) { - this.state = state; - this.context = context; - } - final State state; - final Context context; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - StateContext that = (StateContext) o; - return Objects.equals(state, that.state) && Objects.equals(context, that.context); - } - - @Override - public int hashCode() { - return Objects.hash(state, context); - } -} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java b/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java new file mode 100644 index 000000000..d5c06c12f --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/StateContext.java @@ -0,0 +1,33 @@ +package org.pitest.sequence; + +import java.util.Objects; + +/** + * Pair class to hold state and context. + */ +final class StateContext { + + StateContext(State state, Context context) { + this.state = state; + this.context = context; + } + final State state; + final Context context; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof StateContext)) { + return false; + } + StateContext that = (StateContext) o; + return state.equals(that.state) && context.equals(that.context); + } + + @Override + public int hashCode() { + return Objects.hash(state, context); + } +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java new file mode 100644 index 000000000..e4eacda42 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java @@ -0,0 +1,16 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class ContextTest { + + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(Context.class) + .withNonnullFields("slots") + .withIgnoredFields("debug") + .verify(); + } + +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java new file mode 100644 index 000000000..552eb9243 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/StateContextTest.java @@ -0,0 +1,13 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class StateContextTest { + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(StateContext.class) + .withNonnullFields("state", "context") + .verify(); + } +} \ No newline at end of file From dfc8d5dde2eacfd04c5dbe8b76f228ade845ad0f Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 18 May 2022 10:22:45 +0100 Subject: [PATCH 09/10] Specialise context class A large amount of static analysis time is spent computing hashcodes for maps. A significant performance improvement can be made by creating specialised classes for the common cases of contexts with 0 or 1 values. --- .../tooling/MutationCoverage.java | 2 +- .../java/org/pitest/sequence/Context.java | 55 ++++---------- .../java/org/pitest/sequence/Context1.java | 60 ++++++++++++++++ .../org/pitest/sequence/EmptyContext.java | 35 +++++++++ .../org/pitest/sequence/MultiContext.java | 55 ++++++++++++++ .../org/pitest/sequence/Context1Test.java | 15 ++++ .../java/org/pitest/sequence/ContextTest.java | 72 +++++++++++++++++-- .../org/pitest/sequence/MultiContextTest.java | 16 +++++ 8 files changed, 262 insertions(+), 48 deletions(-) create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/Context1.java create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java create mode 100644 pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java create mode 100644 pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java 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 d80ec4e2e..4312a78fa 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 @@ -162,7 +162,7 @@ private CombinedStatistics runAnalysis(Runtime runtime, long t0, EngineArguments engine, args, allInterceptors()); this.timings.registerEnd(Timings.Stage.BUILD_MUTATION_TESTS); - LOG.info("Created " + tus.size() + " mutation test units"); + LOG.info("Created " + tus.size() + " mutation test units" ); recordClassPath(history, coverageData); diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context.java b/pitest-entry/src/main/java/org/pitest/sequence/Context.java index 70b69452a..f4a2efd4b 100644 --- a/pitest-entry/src/main/java/org/pitest/sequence/Context.java +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context.java @@ -1,59 +1,32 @@ package org.pitest.sequence; -import java.util.IdentityHashMap; - -import java.util.Map; import java.util.Optional; -public final class Context { - - private final boolean debug; - private final Map slots; - - Context(Map slots, boolean debug) { - this.slots = slots; - this.debug = debug; - } +public interface Context { - public static Context start() { + static Context start() { return start(false); } - public static Context start(boolean debug) { - return new Context(new IdentityHashMap<>(), debug); + static Context start(boolean debug) { + if (debug) { + return EmptyContext.WITH_DEBUG; + } + return EmptyContext.WITHOUT_DEBUG; } - public Context store(SlotWrite slot, S value) { - Map mutatedSlots = new IdentityHashMap<>(slots); - mutatedSlots.put(slot.slot(), value); - return new Context(mutatedSlots, debug); - } + Context store(SlotWrite slot, S value); @SuppressWarnings("unchecked") - public Optional retrieve(SlotRead slot) { - return Optional.ofNullable((S)slots.get(slot.slot())); - } + Optional retrieve(SlotRead slot); - public void debug(String msg, T t) { - if (this.debug) { - System.out.println(msg + " for " + t); - } + default boolean debug() { + return false; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Context)) { - return false; + default void debug(String msg, T t) { + if (debug()) { + System.out.println(msg + " for " + t); } - Context context = (Context) o; - return slots.equals(context.slots); - } - - @Override - public int hashCode() { - return slots.hashCode(); } } diff --git a/pitest-entry/src/main/java/org/pitest/sequence/Context1.java b/pitest-entry/src/main/java/org/pitest/sequence/Context1.java new file mode 100644 index 000000000..bc523131c --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/Context1.java @@ -0,0 +1,60 @@ +package org.pitest.sequence; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Specialisation of context for single values + */ +final class Context1 implements Context { + private final boolean debug; + private final Slot slot; + private final Object value; + + Context1(Slot slot, Object value, boolean debug) { + this.slot = slot; + this.value = value; + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + Map mutatedSlots = new IdentityHashMap<>(); + mutatedSlots.put(this.slot, this.value); + mutatedSlots.put(slot.slot(), value); + return new MultiContext(mutatedSlots, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead read) { + if (read.slot().equals(slot)) { + return (Optional) Optional.ofNullable(value); + } + return Optional.empty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Context1)) { + return false; + } + Context1 context1 = (Context1) o; + return slot.equals(context1.slot) && Objects.equals(value, context1.value); + } + + @Override + public int hashCode() { + return Objects.hash(slot, value); + } +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java b/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java new file mode 100644 index 000000000..9b04564e9 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/EmptyContext.java @@ -0,0 +1,35 @@ +package org.pitest.sequence; + +import java.util.Optional; + +/** + * Specialisation of context with no data + */ +enum EmptyContext implements Context { + + WITHOUT_DEBUG(false), + WITH_DEBUG(true); + + private final boolean debug; + + EmptyContext(boolean debug) { + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + return new Context1(slot.slot(), value, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead slot) { + return Optional.empty(); + } + +} diff --git a/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java b/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java new file mode 100644 index 000000000..ea2cd4a99 --- /dev/null +++ b/pitest-entry/src/main/java/org/pitest/sequence/MultiContext.java @@ -0,0 +1,55 @@ +package org.pitest.sequence; + +import java.util.IdentityHashMap; + +import java.util.Map; +import java.util.Optional; + +/** + * Specialisation of context for unlimited values + */ +final class MultiContext implements Context { + + private final boolean debug; + private final Map slots; + + MultiContext(Map slots, boolean debug) { + this.slots = slots; + this.debug = debug; + } + + @Override + public boolean debug() { + return debug; + } + + @Override + public Context store(SlotWrite slot, S value) { + Map mutatedSlots = new IdentityHashMap<>(slots); + mutatedSlots.put(slot.slot(), value); + return new MultiContext(mutatedSlots, debug); + } + + @SuppressWarnings("unchecked") + @Override + public Optional retrieve(SlotRead slot) { + return Optional.ofNullable((S)slots.get(slot.slot())); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MultiContext)) { + return false; + } + MultiContext context = (MultiContext) o; + return slots.equals(context.slots); + } + + @Override + public int hashCode() { + return slots.hashCode(); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java b/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java new file mode 100644 index 000000000..bdfc80d42 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/Context1Test.java @@ -0,0 +1,15 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class Context1Test { + + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(Context1.class) + .withNonnullFields("slot") + .withIgnoredFields("debug") + .verify(); + } +} \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java index e4eacda42..b4c24b739 100644 --- a/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java +++ b/pitest-entry/src/test/java/org/pitest/sequence/ContextTest.java @@ -1,16 +1,76 @@ package org.pitest.sequence; -import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.Test; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + public class ContextTest { @Test - public void obeysHashCodeEqualsContract() { - EqualsVerifier.forClass(Context.class) - .withNonnullFields("slots") - .withIgnoredFields("debug") - .verify(); + public void retrieveIsStartsEmpty() { + SlotRead slot = Slot.create(Integer.class).read(); + assertThat(Context.start().retrieve(slot)).isEmpty(); + } + + @Test + public void canStoreThenRetrieve() { + SlotRead slot = Slot.create(Integer.class).read(); + Context underTest = Context.start().store(slot.slot().write(), 42); + + Optional actual = underTest.retrieve(slot); + assertThat(actual).contains(42); + } + + @Test + public void canStoreAndRetrieveTwoValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 42) + .store(slot2.write(), 101); + + assertThat(underTest.retrieve(slot1.read())).contains(42); + assertThat(underTest.retrieve(slot2.read())).contains(101); + } + + @Test + public void canStoreAndRetrieveThreeValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Slot slot3 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 42) + .store(slot2.write(), 101) + .store(slot3.write(), 8); + + assertThat(underTest.retrieve(slot1.read())).contains(42); + assertThat(underTest.retrieve(slot2.read())).contains(101); + assertThat(underTest.retrieve(slot3.read())).contains(8); + } + + @Test + public void canStoreAndRetrieveMultipleValues() { + Slot slot1 = Slot.create(Integer.class); + Slot slot2 = Slot.create(Integer.class); + Slot slot3 = Slot.create(Integer.class); + Slot slot4 = Slot.create(Integer.class); + Slot slot5 = Slot.create(Integer.class); + Context underTest = Context.start() + .store(slot1.write(), 1) + .store(slot2.write(), 2) + .store(slot3.write(), 3) + .store(slot4.write(), 4) + .store(slot5.write(), 5); + + + assertThat(underTest.retrieve(slot1.read())).contains(1); + assertThat(underTest.retrieve(slot2.read())).contains(2); + assertThat(underTest.retrieve(slot3.read())).contains(3); + assertThat(underTest.retrieve(slot4.read())).contains(4); + assertThat(underTest.retrieve(slot5.read())).contains(5); } + } \ No newline at end of file diff --git a/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java b/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java new file mode 100644 index 000000000..a474bef31 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/sequence/MultiContextTest.java @@ -0,0 +1,16 @@ +package org.pitest.sequence; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +public class MultiContextTest { + + @Test + public void obeysHashCodeEqualsContract() { + EqualsVerifier.forClass(MultiContext.class) + .withNonnullFields("slots") + .withIgnoredFields("debug") + .verify(); + } + +} \ No newline at end of file From fcb95e6b3f860833e4ae3e98e2b1b1b7d75a6112 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 18 May 2022 12:56:14 +0100 Subject: [PATCH 10/10] move example try with resources classes to entry module --- .../src/test/java/twr/example1/TryExample.java | 0 .../src/test/java/twr/example2/TryCatchExample.java | 0 .../src/test/java/twr/example3/TryCatchFinallyExample.java | 0 .../src/test/java/twr/example4/TryFinallyExample.java | 0 .../src/test/java/twr/example5/TryWithTwoCloseableExample.java | 0 .../src/test/java/twr/example6/TryWithNestedTryExample.java | 0 .../src/test/java/twr/example7/TryWithInterfaceExample.java | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {pitest => pitest-entry}/src/test/java/twr/example1/TryExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example2/TryCatchExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example3/TryCatchFinallyExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example4/TryFinallyExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example5/TryWithTwoCloseableExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example6/TryWithNestedTryExample.java (100%) rename {pitest => pitest-entry}/src/test/java/twr/example7/TryWithInterfaceExample.java (100%) diff --git a/pitest/src/test/java/twr/example1/TryExample.java b/pitest-entry/src/test/java/twr/example1/TryExample.java similarity index 100% rename from pitest/src/test/java/twr/example1/TryExample.java rename to pitest-entry/src/test/java/twr/example1/TryExample.java diff --git a/pitest/src/test/java/twr/example2/TryCatchExample.java b/pitest-entry/src/test/java/twr/example2/TryCatchExample.java similarity index 100% rename from pitest/src/test/java/twr/example2/TryCatchExample.java rename to pitest-entry/src/test/java/twr/example2/TryCatchExample.java diff --git a/pitest/src/test/java/twr/example3/TryCatchFinallyExample.java b/pitest-entry/src/test/java/twr/example3/TryCatchFinallyExample.java similarity index 100% rename from pitest/src/test/java/twr/example3/TryCatchFinallyExample.java rename to pitest-entry/src/test/java/twr/example3/TryCatchFinallyExample.java diff --git a/pitest/src/test/java/twr/example4/TryFinallyExample.java b/pitest-entry/src/test/java/twr/example4/TryFinallyExample.java similarity index 100% rename from pitest/src/test/java/twr/example4/TryFinallyExample.java rename to pitest-entry/src/test/java/twr/example4/TryFinallyExample.java diff --git a/pitest/src/test/java/twr/example5/TryWithTwoCloseableExample.java b/pitest-entry/src/test/java/twr/example5/TryWithTwoCloseableExample.java similarity index 100% rename from pitest/src/test/java/twr/example5/TryWithTwoCloseableExample.java rename to pitest-entry/src/test/java/twr/example5/TryWithTwoCloseableExample.java diff --git a/pitest/src/test/java/twr/example6/TryWithNestedTryExample.java b/pitest-entry/src/test/java/twr/example6/TryWithNestedTryExample.java similarity index 100% rename from pitest/src/test/java/twr/example6/TryWithNestedTryExample.java rename to pitest-entry/src/test/java/twr/example6/TryWithNestedTryExample.java diff --git a/pitest/src/test/java/twr/example7/TryWithInterfaceExample.java b/pitest-entry/src/test/java/twr/example7/TryWithInterfaceExample.java similarity index 100% rename from pitest/src/test/java/twr/example7/TryWithInterfaceExample.java rename to pitest-entry/src/test/java/twr/example7/TryWithInterfaceExample.java