Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hcoles/pitest
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.16.2
Choose a base ref
...
head repository: hcoles/pitest
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 1.16.3
Choose a head ref
  • 11 commits
  • 31 files changed
  • 2 contributors

Commits on Aug 28, 2024

  1. update for 1.16.2

    hcoles committed Aug 28, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    chaudum Christian Haudum
    Copy the full SHA
    54dd01a View commit details

Commits on Aug 29, 2024

  1. identify fields with a prescan

    hcoles committed Aug 29, 2024
    Copy the full SHA
    45d9f34 View commit details
  2. scan for functional annotation

    hcoles committed Aug 29, 2024
    Copy the full SHA
    c0a2548 View commit details
  3. expand list of recognised function types

    hcoles committed Aug 29, 2024
    Copy the full SHA
    a7d9d90 View commit details

Commits on Aug 30, 2024

  1. add kotlin function types

    hcoles committed Aug 30, 2024
    Copy the full SHA
    4c86cb4 View commit details
  2. fix comment and remove unneccesary logging of common scenario

    hcoles committed Aug 30, 2024
    Copy the full SHA
    376d386 View commit details
  3. disable functional interface scanning

    hcoles committed Aug 30, 2024
    Copy the full SHA
    b374736 View commit details
  4. Merge pull request #1345 from hcoles/feature/improved_delayed_execution

    Feature/improved delayed execution
    hcoles authored Aug 30, 2024
    Copy the full SHA
    5b8e2c1 View commit details

Commits on Sep 2, 2024

  1. Auto add standard kotlin source dirs if present

    src/main/kotlin and src/test/kotlin are now auto added to the a maven project by the mojo if present.
    
    This is necessary as, for maven builds, there directories are often added at runtime by the build helper or kotlin plugins. If they
    pitest goal is run directly (as is common) these plugins will not fire and the config will be missing causing confusing failures.
    
    This replaces an earlier hack where there directories were aded at a lower level.
    hcoles committed Sep 2, 2024
    Copy the full SHA
    c7b9fa4 View commit details
  2. Merge pull request #1347 from hcoles/feature/auto_configure_kotlin

    Auto add standard kotlin source dirs if present
    hcoles authored Sep 2, 2024
    Copy the full SHA
    8714b60 View commit details
  3. backwards compatibility

    hcoles committed Sep 2, 2024
    Copy the full SHA
    18c4989 View commit details
Showing with 735 additions and 147 deletions.
  1. +6 −0 README.md
  2. +8 −5 pitest-entry/src/main/java/org/pitest/mutationtest/build/CompoundInterceptorFactory.java
  3. +22 −2 pitest-entry/src/main/java/org/pitest/mutationtest/build/InterceptorParameters.java
  4. +30 −0 ...n/java/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScanner.java
  5. +57 −23 ...java/org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptor.java
  6. +34 −14 ...g/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorFactory.java
  7. +3 −6 pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java
  8. +5 −0 pitest-entry/src/main/java/org/pitest/sequence/Slot.java
  9. +158 −0 pitest-entry/src/main/resources/functional_interfaces.txt
  10. +6 −0 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunction.java
  11. +6 −0 ...try/src/test/java/com/example/staticinitializers/delayedexecution/CustomFunctionNotAnnotated.java
  12. +21 −0 pitest-entry/src/test/java/com/example/staticinitializers/delayedexecution/EnumListOfSuppliers.java
  13. +19 −0 ...rc/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctionalInterface.java
  14. +19 −0 ...st-entry/src/test/java/com/example/staticinitializers/delayedexecution/StaticListOfFunctions.java
  15. +18 −0 .../test/java/com/example/staticinitializers/delayedexecution/StaticListOfUnannotatedInterfaces.java
  16. +89 −0 pitest-entry/src/test/java/org/pitest/mutationtest/FixedCodeSource.java
  17. +2 −2 pitest-entry/src/test/java/org/pitest/mutationtest/build/InterceptorParametersTest.java
  18. +2 −1 pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationDiscoveryTest.java
  19. +85 −0 ...va/org/pitest/mutationtest/build/intercept/staticinitializers/FunctionalInterfaceScannerTest.java
  20. +52 −1 .../org/pitest/mutationtest/build/intercept/staticinitializers/StaticInitializerInterceptorTest.java
  21. +1 −1 pitest-entry/src/test/java/org/pitest/verifier/interceptors/FactoryVerifier.java
  22. +7 −1 pitest-entry/src/test/java/org/pitest/verifier/interceptors/VerifierStart.java
  23. +0 −18 pitest-maven-verification/src/test/resources/pit-kotlin-multi-module/sub-module-1/pom.xml
  24. +0 −18 pitest-maven-verification/src/test/resources/pit-kotlin-multi-module/sub-module-2/pom.xml
  25. +0 −18 pitest-maven-verification/src/test/resources/pit-kotlin-multi-module/sub-module-3/pom.xml
  26. +28 −0 pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java
  27. +0 −14 pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java
  28. +0 −22 pitest-maven/src/test/java/org/pitest/maven/MojoToReportOptionsConverterTest.java
  29. +1 −1 pitest-maven/src/test/java/org/pitest/maven/PitMojoTest.java
  30. +31 −0 pitest/src/main/java/org/pitest/bytecode/SignatureParser.java
  31. +25 −0 pitest/src/test/java/org/pitest/bytecode/SignatureParserTest.java
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,12 @@ Read all about it at https://pitest.org

## Releases

## 1.16.2

#1340 Show covering tests for surviving mutants in html report (thanks @vivganes)
#1342/#1343 Fix misleading logging of history paths (thanks @vivganes)
#1344 Mutate delayed execution code in enums and singletons

## 1.16.1

* #1329 Prevent version clashes with aggregate goal when plugins use jackson
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.pitest.mutationtest.build;

import org.pitest.classinfo.ClassByteArraySource;
import org.pitest.classpath.CodeSource;
import org.pitest.coverage.CoverageDatabase;
import org.pitest.mutationtest.config.ReportOptions;
import org.pitest.plugin.FeatureSelector;
@@ -24,10 +25,11 @@ public CompoundMutationInterceptor createInterceptor(
ReportOptions data,
CoverageDatabase coverage,
ClassByteArraySource source,
TestPrioritiser testPrioritiser
TestPrioritiser testPrioritiser,
CodeSource code
) {
final List<MutationInterceptor> interceptors = this.features.getActiveFeatures().stream()
.map(toInterceptor(this.features, data, coverage, source, testPrioritiser))
List<MutationInterceptor> interceptors = this.features.getActiveFeatures().stream()
.map(toInterceptor(this.features, data, coverage, source, testPrioritiser, code))
.collect(Collectors.toList());
return new CompoundMutationInterceptor(interceptors);
}
@@ -38,10 +40,11 @@ private static Function<MutationInterceptorFactory, MutationInterceptor> toInter
ReportOptions data,
CoverageDatabase coverage,
ClassByteArraySource source,
TestPrioritiser testPrioritiser
TestPrioritiser testPrioritiser,
CodeSource code
) {

return a -> a.createInterceptor(new InterceptorParameters(features.getSettingForFeature(a.provides().name()), data, coverage, source, testPrioritiser));
return a -> a.createInterceptor(new InterceptorParameters(features.getSettingForFeature(a.provides().name()), data, coverage, source, testPrioritiser, code));

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.pitest.mutationtest.build;

import org.pitest.classinfo.ClassByteArraySource;
import org.pitest.classpath.CodeSource;
import org.pitest.coverage.CoverageDatabase;
import org.pitest.mutationtest.config.ReportOptions;
import org.pitest.plugin.FeatureParameter;
@@ -16,17 +17,32 @@ public final class InterceptorParameters {
private final ReportOptions data;
private final ClassByteArraySource source;
private final CoverageDatabase coverage;
private final CodeSource code;

private final TestPrioritiser testPrioritiser;


public InterceptorParameters(FeatureSetting conf, ReportOptions data, CoverageDatabase coverage,
ClassByteArraySource source, TestPrioritiser testPrioritiser) {
@Deprecated
public InterceptorParameters(FeatureSetting conf,
ReportOptions data,
CoverageDatabase coverage,
ClassByteArraySource source,
TestPrioritiser testPrioritiser) {
this(conf, data, coverage, source, testPrioritiser, null);
}

public InterceptorParameters(FeatureSetting conf,
ReportOptions data,
CoverageDatabase coverage,
ClassByteArraySource source,
TestPrioritiser testPrioritiser,
CodeSource code) {
this.conf = conf;
this.data = data;
this.coverage = coverage;
this.source = source;
this.testPrioritiser = testPrioritiser;
this.code = code;
}

public ReportOptions data() {
@@ -50,6 +66,10 @@ public TestPrioritiser testPrioritiser() {
return this.testPrioritiser;
}

public CodeSource code() {
return code;
}

public Optional<String> getString(FeatureParameter limit) {
if (this.conf == null) {
return Optional.empty();
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.pitest.mutationtest.build.intercept.staticinitializers;

import org.objectweb.asm.tree.AnnotationNode;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.classpath.CodeSource;

import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class FunctionalInterfaceScanner implements Function<CodeSource, Set<String>> {
@Override
public Set<String> apply(CodeSource codeSource) {
return codeSource.codeTrees()
.filter(this::isFunctionalInterface)
.map(c -> c.rawNode().name)
.collect(Collectors.toSet());
}

private boolean isFunctionalInterface(ClassTree classTree) {
List<AnnotationNode> annotations = classTree.rawNode().visibleAnnotations;
if (annotations == null) {
return false;
}

return annotations.stream()
.anyMatch(a -> a.desc.equals("Ljava/lang/FunctionalInterface;"));
}
}
Original file line number Diff line number Diff line change
@@ -2,10 +2,13 @@

import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.pitest.bytecode.SignatureParser;
import org.pitest.bytecode.analysis.ClassTree;
import org.pitest.bytecode.analysis.MethodTree;
import org.pitest.classinfo.ClassName;
@@ -21,6 +24,7 @@
import org.pitest.sequence.SequenceMatcher;
import org.pitest.sequence.SequenceQuery;
import org.pitest.sequence.Slot;
import org.pitest.sequence.SlotRead;
import org.pitest.sequence.SlotWrite;

import java.util.Arrays;
@@ -53,47 +57,63 @@
* 1. In a static initializer (i.e <clinit>)
* 2. In a private method or constructor called from <clinit> or another private method in the call tree
*
*
*/
class StaticInitializerInterceptor implements MutationInterceptor {

static final Slot<AbstractInsnNode> START = Slot.create(AbstractInsnNode.class);
private final Set<String> delayedExecutionTypes;

private static final Slot<AbstractInsnNode> START = Slot.create(AbstractInsnNode.class);
private static final Slot<Set<String>> DELAYED_EXECUTION_FIELDS = Slot.createSet(String.class);

static final SequenceMatcher<AbstractInsnNode> DELAYED_EXECUTION = QueryStart
private final SequenceMatcher<AbstractInsnNode> delayedExecution = QueryStart
.any(AbstractInsnNode.class)
// look for calls returning delayed execution types. Unfortunately this is not guarantee that we
// store the result to an appropriate type
.then(returnsDeferredExecutionCode().or(dynamicallyReturnsDeferredExecutionCode()).and(store(START.write())))
// allow for other method calls etc
.zeroOrMore(QueryStart.match(anyInstruction()))
.then(enumConstructorCallAndStore().or(QueryStart.match(delayedExecutionField())))
.then(enumConstructorCallAndStore().or(QueryStart.match(delayedExecutionField(DELAYED_EXECUTION_FIELDS.read()))))
.zeroOrMore(QueryStart.match(anyInstruction()))
.compile(QueryParams.params(AbstractInsnNode.class)
.withIgnores(notAnInstruction())
);

private static Match<AbstractInsnNode> delayedExecutionField() {
return PUTSTATIC.and(isAUtilFunctionField());
StaticInitializerInterceptor(Set<String> delayedExecutionTypes) {
this.delayedExecutionTypes = delayedExecutionTypes;
}

private static Match<AbstractInsnNode> delayedExecutionField(SlotRead<Set<String>> delayedFields) {
return PUTSTATIC.and(isADelayedExecutionField(delayedFields));
}

private static Match<AbstractInsnNode> isAUtilFunctionField() {
private static Match<AbstractInsnNode> isADelayedExecutionField(SlotRead<Set<String>> delayedFields) {
return (c,n) -> {
FieldInsnNode fieldNode = ((FieldInsnNode) n);
return result( fieldNode.desc.startsWith("Ljava/util/function/"), c);
return result( c.retrieve(delayedFields).get().contains(fieldNode.name), c);
};
}

private static Match<AbstractInsnNode> dynamicallyReturnsDeferredExecutionCode() {
return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c);
private Match<AbstractInsnNode> dynamicallyReturnsDeferredExecutionCode() {
return (c,n) -> result(n.getOpcode() == Opcodes.INVOKEDYNAMIC && returnsDelayedExecutionType(((InvokeDynamicInsnNode) n).desc), c);
}

private static Match<AbstractInsnNode> returnsDeferredExecutionCode() {
return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnDelayedExecutionType(((MethodInsnNode) n).desc), c);
private Match<AbstractInsnNode> returnsDeferredExecutionCode() {
return (c,n) -> result(n.getOpcode() == Opcodes.INVOKESTATIC && returnsDelayedExecutionType(((MethodInsnNode) n).desc), c);
}

private boolean returnsDelayedExecutionType(String desc) {
Type returnType = Type.getReturnType(desc);
// fixme Arrays?
if (returnType.getSort() != Type.OBJECT) {
return false;
}

return isADelayedExecutionType(returnType.getInternalName());
}

private static boolean returnDelayedExecutionType(String desc) {
int endOfParams = desc.indexOf(')');
return endOfParams <= 0 || desc.substring(endOfParams + 1).startsWith("Ljava/util/function/");

private boolean isADelayedExecutionType(String type) {
return delayedExecutionTypes.contains(type);
}

private static SequenceQuery<AbstractInsnNode> enumConstructorCallAndStore() {
@@ -126,14 +146,23 @@ private void analyseClass(ClassTree tree) {
final Optional<MethodTree> clinit = tree.methods().stream().filter(nameEquals("<clinit>")).findFirst();

if (clinit.isPresent()) {

// Find delayed execution fields (Function, Supplier, List<Supplier> etc). Full generic signature is available in the
// declaration, but not on call.
Set<String> delayedExecutionFields = tree.rawNode().fields.stream()
.filter(this::isDelayedExecutionField)
.map(n -> n.name)
.collect(Collectors.toSet());


// We can't see if a method *call* is private from the call site
// so collect a set of private methods within the class first
Set<Location> privateMethods = tree.methods().stream()
.filter(MethodTree::isPrivate)
.map(MethodTree::asLocation)
.collect(Collectors.toSet());

Set<Call> storedToSupplier = findCallsStoredToDelayedExecutionCode(tree);
Set<Call> storedToSupplier = findCallsStoredToDelayedExecutionCode(tree, delayedExecutionFields);

// Get map of each private method to the private methods it calls
// Any call to a non private method breaks the chain
@@ -153,21 +182,26 @@ private void analyseClass(ClassTree tree) {
}
}

private Set<Call> findCallsStoredToDelayedExecutionCode(ClassTree tree) {
return new HashSet<>(privateAndClinitCallsToDelayedExecutionCode(tree));
private boolean isDelayedExecutionField(FieldNode fieldNode) {
return SignatureParser.extractTypes(fieldNode.signature).stream()
.anyMatch(this::isADelayedExecutionType);
}

private Set<Call> findCallsStoredToDelayedExecutionCode(ClassTree tree, Set<String> delayedExecutionFields) {
return new HashSet<>(privateAndClinitCallsToDelayedExecutionCode(tree, delayedExecutionFields));
}


private Set<Call> privateAndClinitCallsToDelayedExecutionCode(ClassTree tree) {
private Set<Call> privateAndClinitCallsToDelayedExecutionCode(ClassTree tree, Set<String> delayedExecutionFields) {
return tree.methods().stream()
.filter(m -> m.isPrivate() || m.rawNode().name.equals("<clinit>"))
.flatMap(m -> delayedExecutionCall(m).stream().map(c -> new Call(m.asLocation(), c)))
.flatMap(m -> delayedExecutionCall(m, delayedExecutionFields).stream().map(c -> new Call(m.asLocation(), c)))
.collect(Collectors.toSet());
}

private List<Location> delayedExecutionCall(MethodTree method) {
Context context = Context.start();
return DELAYED_EXECUTION.contextMatches(method.instructions(), context).stream()
private List<Location> delayedExecutionCall(MethodTree method, Set<String> delayedExecutionFields) {
Context context = Context.start().store(DELAYED_EXECUTION_FIELDS.write(), delayedExecutionFields);
return delayedExecution.contextMatches(method.instructions(), context).stream()
.map(c -> c.retrieve(START.read()).get())
.flatMap(this::nodeToLocation)
.collect(Collectors.toList());
Original file line number Diff line number Diff line change
@@ -5,22 +5,42 @@
import org.pitest.mutationtest.build.MutationInterceptorFactory;
import org.pitest.plugin.Feature;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;

public class StaticInitializerInterceptorFactory implements MutationInterceptorFactory {

@Override
public String description() {
return "Static initializer code detector plugin";
}
@Override
public String description() {
return "Static initializer code detector plugin";
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new StaticInitializerInterceptor(functionalInterfaces());
}

@Override
public MutationInterceptor createInterceptor(InterceptorParameters params) {
return new StaticInitializerInterceptor();
}
@Override
public Feature provides() {
return Feature.named("FSTATI")
.withOnByDefault(true)
.withDescription("Filters mutations in static initializers and code called only from them");
}

@Override
public Feature provides() {
return Feature.named("FSTATI")
.withOnByDefault(true)
.withDescription("Filters mutations in static initializers and code called only from them");
}
private Set<String> functionalInterfaces() {
Set<String> classes = new HashSet<>();
try (BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/functional_interfaces.txt")))) {
String line = r.readLine();
while (line != null) {
classes.add(line);
line = r.readLine();
}
return classes;
} catch (IOException e) {
throw new RuntimeException("Could not read embedded list of functional interfaces!");
}
}
}
Original file line number Diff line number Diff line change
@@ -65,7 +65,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

@@ -337,7 +336,7 @@ private List<MutationAnalysisUnit> buildMutationTests(CoverageDatabase coverageD
coverageData);

final MutationInterceptor interceptor = this.settings.getInterceptor()
.createInterceptor(this.data, coverageData, bas, testPrioritiser)
.createInterceptor(this.data, coverageData, bas, testPrioritiser, code)
.filter(interceptorFilter);

interceptor.initialise(this.code);
@@ -377,17 +376,15 @@ private CoverageGenerator coverage() {
return this.strategies.coverage();
}

// For reasons not yet understood classes from rt.jar are not resolved for some
// projects during static analysis phase. For now fall back to the classloader when
// a class not provided by project classpath
// Since java 9 rt.jar is no longer on the classpath so jdk classes will not resolve from
// the filesystem and must be pulled out via the classloader
private ClassByteArraySource fallbackToClassLoader(final ClassByteArraySource bas) {
final ClassByteArraySource clSource = ClassloaderByteArraySource.fromContext();
return clazz -> {
final Optional<byte[]> maybeBytes = bas.getBytes(clazz);
if (maybeBytes.isPresent()) {
return maybeBytes;
}
LOG.log(Level.FINE, "Could not find " + clazz + " on classpath for analysis. Falling back to classloader");
return clSource.getBytes(clazz);
};
}
Loading