From 6483624cefe9b723d31f3f3024d24dd915321888 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 28 Jul 2022 18:11:55 +0100 Subject: [PATCH 1/5] Capture all classloaders loading mutant Quarkus, Roboelectric (and possibly other frameworks) load the class under test in a classloader that is not set as the context loader when pitest starts. A previous change got things working with Quarkus, but Roboelectric complicates things further by persisting the classloaders between tests. We must therefore keep track of all classloaders used to load a mutated class, and insert each mutant into all of them. This would have been complicated in earlier version of pitest by the possibility that the same JVM might be used to mutate multiple classes. This would requite the bytes of each class to be stored so that unmutated class could be restored. At some point, a change seems to have been made such that only 1 class is ever mutated per jvm. Experiments to reintroduce multiple class mutations (inners, lambdas etc) did not result in a performance increase in sample code bases. 1 class per jvm is therefore now made a guarantee to allow the code to be much simpler. --- .../mutationtest/build/DefaultGrouper.java | 4 +- .../classloaders/MuteeInOtherClassloader.java | 17 +++++ .../MuteeInOtherClassloaderPooledTest.java | 22 ++++++ .../MuteeInOtherClassloaderTest.java | 20 +++++ .../MutationCoverageReportSystemTest.java | 31 ++++++++ .../pitest/mutationtest/ReportTestBase.java | 2 +- .../src/test/java/org/pitest/PitMojoIT.java | 8 +- pitest/pom.xml | 2 + .../CatchNewClassLoadersTransformer.java | 73 ++++--------------- .../pitest/mutationtest/execute/HotSwap.java | 60 ++++----------- .../execute/MutationTestMinion.java | 29 +++----- .../execute/MutationTestWorker.java | 12 +-- .../execute/MutationTestWorkerTest.java | 9 +-- 13 files changed, 149 insertions(+), 140 deletions(-) create mode 100644 pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloader.java create mode 100644 pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderPooledTest.java create mode 100644 pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderTest.java diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultGrouper.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultGrouper.java index ffc70a25c..71356976a 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultGrouper.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultGrouper.java @@ -24,8 +24,8 @@ public List> groupMutations( final Map> bucketed = FCollection .bucket(mutations, MutationDetails::getClassName); final List> chunked = new ArrayList<>(); - for (final Collection each : bucketed.values()) { - shrinkToMaximumUnitSize(chunked, each); + for (final Map.Entry> each : bucketed.entrySet()) { + shrinkToMaximumUnitSize(chunked, each.getValue()); } return chunked; diff --git a/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloader.java b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloader.java new file mode 100644 index 000000000..db655621a --- /dev/null +++ b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloader.java @@ -0,0 +1,17 @@ +package com.example.classloaders; + +import java.util.function.IntSupplier; + +public class MuteeInOtherClassloader implements IntSupplier { + + @Override + public int getAsInt() { + pointless(); + System.out.println("Can't kill me"); + return 42; + } + + public void pointless() { + System.out.println("Can't kill me either"); + } +} diff --git a/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderPooledTest.java b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderPooledTest.java new file mode 100644 index 000000000..903f964d5 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderPooledTest.java @@ -0,0 +1,22 @@ +package com.example.classloaders; + +import org.junit.Test; +import org.pitest.classpath.ClassPath; +import org.pitest.mutationtest.execute.DefaultPITClassloader; + +import java.util.function.IntSupplier; + +import static org.junit.Assert.assertEquals; + +public class MuteeInOtherClassloaderPooledTest { + + // will persist between mutants + static DefaultPITClassloader otherLoader = new DefaultPITClassloader(new ClassPath(), null); + + @Test + public void returns42() throws Exception { + Class clz = otherLoader.loadClass(MuteeInOtherClassloader.class.getName()); + IntSupplier underTest = (IntSupplier) clz.getConstructor().newInstance(); + assertEquals(42, underTest.getAsInt()); + } +} diff --git a/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderTest.java b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderTest.java new file mode 100644 index 000000000..de8537326 --- /dev/null +++ b/pitest-entry/src/test/java/com/example/classloaders/MuteeInOtherClassloaderTest.java @@ -0,0 +1,20 @@ +package com.example.classloaders; + +import org.junit.Test; +import org.pitest.classpath.ClassPath; +import org.pitest.mutationtest.execute.DefaultPITClassloader; + +import java.util.function.IntSupplier; + +import static org.junit.Assert.assertEquals; + +public class MuteeInOtherClassloaderTest { + + @Test + public void returns42() throws Exception { + DefaultPITClassloader otherLoader = new DefaultPITClassloader(new ClassPath(), null); + Class clz = otherLoader.loadClass(MuteeInOtherClassloader.class.getName()); + IntSupplier underTest = (IntSupplier) clz.getConstructor().newInstance(); + assertEquals(42, underTest.getAsInt()); + } +} diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/MutationCoverageReportSystemTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/MutationCoverageReportSystemTest.java index db22fec1c..34dc85c70 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/MutationCoverageReportSystemTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/MutationCoverageReportSystemTest.java @@ -33,6 +33,9 @@ import java.util.Collections; import java.util.List; +import com.example.classloaders.MuteeInOtherClassloader; +import com.example.classloaders.MuteeInOtherClassloaderPooledTest; +import com.example.classloaders.MuteeInOtherClassloaderTest; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -427,6 +430,34 @@ public void shouldRecoverFromMinionThatDoesNotStart() { // just running without a hang then erroring is success } + @Test + public void handlesMutantsInOtherClassLoaders() { + setMutators("PRIMITIVE_RETURNS", "VOID_METHOD_CALLS"); + + this.data + .setTargetClasses(asGlobs(MuteeInOtherClassloader.class)); + this.data + .setTargetTests(predicateFor(MuteeInOtherClassloaderTest.class)); + + createAndRun(); + + verifyResults(KILLED, SURVIVED, SURVIVED, SURVIVED); + } + + @Test + public void handlesMutantsInPooledClassLoaders() { + setMutators("PRIMITIVE_RETURNS", "VOID_METHOD_CALLS"); + + this.data + .setTargetClasses(asGlobs(MuteeInOtherClassloader.class)); + this.data + .setTargetTests(predicateFor(MuteeInOtherClassloaderPooledTest.class)); + + createAndRun(); + + verifyResults(KILLED, SURVIVED, SURVIVED, SURVIVED); + } + @Generated public static class AnnotatedToAvoidAtClassLevel { public int mutateMe() { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java index e6694c2d0..638f0b381 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java @@ -142,7 +142,7 @@ private CoverageOptions createCoverageOptions(TestPluginArguments configuration) configuration, this.data.getVerbosity()); } - protected void setMutators(final String mutator) { + protected void setMutators(final String... mutator) { this.data.setMutators(Arrays.asList(mutator)); } diff --git a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java index 7346aa305..ab7614b16 100755 --- a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java +++ b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java @@ -400,10 +400,10 @@ public void shouldWorkWithQuarkus() throws Exception { String actual = readResults(testDir); // Test is flaky. Needs investigation - //assertThat(actual) - // .contains( - // "" + - // "ExampleController.java"); + assertThat(actual) + .contains( + "" + + "ExampleController.java"); assertThat(actual) .contains( diff --git a/pitest/pom.xml b/pitest/pom.xml index 5e11e6a3d..1bac30894 100644 --- a/pitest/pom.xml +++ b/pitest/pom.xml @@ -206,6 +206,8 @@ equalsverifier test + + diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java b/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java index 5965a1684..0a0b8f914 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java @@ -5,9 +5,9 @@ import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; -import java.util.Arrays; +import java.util.Collections; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.WeakHashMap; /** * Pitest mainly inserts mutants by calling Instrumentation.redefineClasses using @@ -22,83 +22,42 @@ * the byte array (modifying by rerunning the mutator might result in a different mutant for * the same id if the input has changed). * - * So, sometimes we want to transform the class, sometimes we don't. - * - * Not found a way to reliably work out which we need to do, so need to maintain - * a list. As mutants unexpectedly surviving are far more likely to be reported than - * ones unexpectedly killed, we list the loaders we should mutate for (currently just - * quarkus, but others likely exist). + * This transformer identifies loaders that try to load the target class, and stores + * a weak reference to them to ensure that all copies of the class are mutated. * */ public class CatchNewClassLoadersTransformer implements ClassFileTransformer { + private static String targetClass; private static byte[] currentMutant; - private static String currentClass; - - // The context classloader at the point pitest started. - // we do not want to transform classes from this as they are already handled - // by the primary mechanism - private static ClassLoader ignore; - // Map of loaders we have transformed the current class in, so we can restore them - static final Map ORIGINAL_LOADER_CLASSES = new ConcurrentHashMap<>(); - - public static synchronized void setLoader(ClassLoader loader) { - ignore = loader; - } + // What we want is a ConcurrentWeakHasSet, since that doesn't exist without writing one ourselves + // we'll abuse a WeakHashMap and live with the synchronization + static final Map CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<>()); public static synchronized void setMutant(String className, byte[] mutant) { - String toRestore = currentClass; + targetClass = className; currentMutant = mutant; - - // prevent transforming again when we restore if the same class is mutated twice - currentClass = null; - - restoreClasses(toRestore); - - currentClass = className; - } - - private static void restoreClasses(String toRestore) { - for (Map.Entry each : ORIGINAL_LOADER_CLASSES.entrySet()) { - final Class clazz = checkClassForLoader(each.getKey(), toRestore); + for (ClassLoader each : CLASS_LOADERS.keySet()) { + final Class clazz = checkClassForLoader(each, className); if (clazz != null) { - HotSwapAgent.hotSwap(clazz, each.getValue()); + HotSwapAgent.hotSwap(clazz, mutant); } } - ORIGINAL_LOADER_CLASSES.clear(); } @Override public byte[] transform(final ClassLoader loader, final String className, final Class classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) { - if (loader == null || ignore == loader) { - return null; - } - // Only loader identified so far where mutants must be inserted is Quarkus, but - // others likely exist. At least one loader (gwtmockito) results incorrectly - // killed mutants if we insert. - if (!loader.getClass().getName().startsWith("io.quarkus.bootstrap.classloading")) { - return null; - } - - if (className.equals(currentClass)) { - // skip if class already loaded - if (classBeingRedefined != null) { - return null; - } - - // avoid restoring an already mutated class - // Not clear if this situation is possible, but check left in - // out of fear. - if (!Arrays.equals(classfileBuffer, currentMutant)) { - ORIGINAL_LOADER_CLASSES.put(loader, classfileBuffer); - } + if (className.equals(targetClass)) { + CLASS_LOADERS.put(loader, null); + // we might be mid-mutation so return the mutated bytes return currentMutant; } + return null; } diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java b/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java index 85ca561d0..0833fe372 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java @@ -1,62 +1,32 @@ package org.pitest.mutationtest.execute; import org.pitest.boot.HotSwapAgent; -import org.pitest.classinfo.ClassByteArraySource; import org.pitest.classinfo.ClassName; -import org.pitest.functional.F3; import org.pitest.util.Unchecked; -class HotSwap implements F3 { +/** + * Since pitest 1.9.4 there is an implicit assumption that pitest will never mutate + * more than once class within the same jvm. If this assumption is ever broken, mutants + * from the previous class would remain active and invalidate results. + */ +class HotSwap { - private final ClassByteArraySource byteSource; - private byte[] lastClassPreMutation; - private ClassName lastMutatedClass; - private ClassLoader lastUsedLoader; - - HotSwap(final ClassByteArraySource byteSource) { - this.byteSource = byteSource; - } - - @Override - public Boolean apply(final ClassName clazzName, final ClassLoader loader, - final byte[] b) { + public Boolean insertClass(final ClassName clazzName, ClassLoader loader, final byte[] mutantBytes) { try { + // Some frameworks (eg quarkus) run tests in non delegating + // classloaders. Need to make sure these are transformed too + CatchNewClassLoadersTransformer.setMutant(clazzName.asInternalName(), mutantBytes); - System.out.println("Hotswap loader " + loader); - - restoreLastClass(this.byteSource, clazzName, loader); - this.lastUsedLoader = loader; + // trigger loading for the current loader Class clazz = Class.forName(clazzName.asJavaName(), false, loader); - return HotSwapAgent.hotSwap(clazz, b); - } catch (final ClassNotFoundException e) { - throw Unchecked.translateCheckedException(e); - } - } - - private void restoreLastClass(final ClassByteArraySource byteSource, - final ClassName clazzName, final ClassLoader loader) - throws ClassNotFoundException { - if ((this.lastMutatedClass != null) - && !this.lastMutatedClass.equals(clazzName)) { - restoreForLoader(this.lastUsedLoader); - restoreForLoader(loader); - } + // will still need to explicitly swap it... not clear why the transformed does not do this + return HotSwapAgent.hotSwap(clazz, mutantBytes); - if ((this.lastMutatedClass == null) - || !this.lastMutatedClass.equals(clazzName)) { - this.lastClassPreMutation = byteSource.getBytes(clazzName.asJavaName()) - .get(); + } catch (final ClassNotFoundException e) { + throw Unchecked.translateCheckedException(e); } - this.lastMutatedClass = clazzName; - } - - private void restoreForLoader(ClassLoader loader) - throws ClassNotFoundException { - final Class clazz = Class.forName(this.lastMutatedClass.asJavaName(), false, - loader); - HotSwapAgent.hotSwap(clazz, this.lastClassPreMutation); } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java index bf565a85f..1789eee83 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java @@ -14,24 +14,11 @@ */ package org.pitest.mutationtest.execute; -import java.io.IOException; -import java.lang.management.MemoryNotificationInfo; -import java.net.Socket; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import javax.management.NotificationListener; -import javax.management.openmbean.CompositeData; - import org.pitest.boot.HotSwapAgent; import org.pitest.classinfo.CachingByteArraySource; import org.pitest.classinfo.ClassByteArraySource; import org.pitest.classinfo.ClassName; import org.pitest.classpath.ClassloaderByteArraySource; -import org.pitest.functional.F3; import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.EngineArguments; import org.pitest.mutationtest.config.ClientPluginServices; @@ -50,6 +37,17 @@ import org.pitest.util.Log; import org.pitest.util.SafeDataInputStream; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; +import java.io.IOException; +import java.lang.management.MemoryNotificationInfo; +import java.net.Socket; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + public class MutationTestMinion { private static final Logger LOG = Log.getLogger(); @@ -79,13 +77,10 @@ public void run() { final ClassLoader loader = IsolationUtils.getContextClassLoader(); - CatchNewClassLoadersTransformer.setLoader(loader); - final ClassByteArraySource byteSource = new CachingByteArraySource(new ClassloaderByteArraySource( loader), CACHE_SIZE); - final F3 hotswap = new HotSwap( - byteSource); + final HotSwap hotswap = new HotSwap(); final MutationEngine engine = createEngine(paramsFromParent.engine, paramsFromParent.engineArgs); diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java index 084604250..a0e4e2569 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java @@ -14,8 +14,6 @@ */ package org.pitest.mutationtest.execute; -import org.pitest.classinfo.ClassName; -import org.pitest.functional.F3; import org.pitest.mutationtest.DetectionStatus; import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.engine.Mutant; @@ -56,11 +54,11 @@ public class MutationTestWorker { private final Mutater mutater; private final ClassLoader loader; - private final F3 hotswap; + private final HotSwap hotswap; private final boolean fullMutationMatrix; public MutationTestWorker( - final F3 hotswap, + final HotSwap hotswap, final Mutater mutater, final ClassLoader loader, final boolean fullMutationMatrix) { this.loader = loader; this.mutater = mutater; @@ -142,11 +140,7 @@ private MutationStatusTestPair handleCoveredMutation( final Container c = createNewContainer(); final long t0 = System.currentTimeMillis(); - // Some frameworks (eg quarkus) run tests in non delegating - // classloaders. Need to make sure these are transformed too - CatchNewClassLoadersTransformer.setMutant(mutatedClass.getDetails().getClassName().asInternalName(), mutatedClass.getBytes()); - - if (this.hotswap.apply(mutationId.getClassName(), this.loader, + if (this.hotswap.insertClass(mutationId.getClassName(), this.loader, mutatedClass.getBytes())) { if (DEBUG) { LOG.fine("replaced class with mutant in " diff --git a/pitest/src/test/java/org/pitest/mutationtest/execute/MutationTestWorkerTest.java b/pitest/src/test/java/org/pitest/mutationtest/execute/MutationTestWorkerTest.java index d4c251898..95cb40e18 100644 --- a/pitest/src/test/java/org/pitest/mutationtest/execute/MutationTestWorkerTest.java +++ b/pitest/src/test/java/org/pitest/mutationtest/execute/MutationTestWorkerTest.java @@ -19,7 +19,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.pitest.classinfo.ClassName; -import org.pitest.functional.F3; import org.pitest.mutationtest.DetectionStatus; import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.engine.Mutant; @@ -43,7 +42,7 @@ public class MutationTestWorkerTest { private Mutater mutater; @Mock - private F3 hotswapper; + private HotSwap hotswapper; @Mock private TimeOutDecoratedTestSource testSource; @@ -88,7 +87,7 @@ public void shouldReportWhenMutationNotDetected() throws IOException { when(this.testSource.translateTests(any(List.class))).thenReturn( Collections.singletonList(tu)); when( - this.hotswapper.apply(any(ClassName.class), any(ClassLoader.class), + this.hotswapper.insertClass(any(ClassName.class), any(ClassLoader.class), any(byte[].class))).thenReturn(true); this.testee.run(range, this.reporter, this.testSource); verify(this.reporter).report(mutantOne.getId(), @@ -104,7 +103,7 @@ public void shouldReportWhenMutationNotViable() throws IOException { when(this.testSource.translateTests(any(List.class))).thenReturn( Collections.singletonList(tu)); when( - this.hotswapper.apply(any(ClassName.class), any(ClassLoader.class), + this.hotswapper.insertClass(any(ClassName.class), any(ClassLoader.class), any(byte[].class))).thenReturn(false); this.testee.run(range, this.reporter, this.testSource); verify(this.reporter).report(mutantOne.getId(), @@ -119,7 +118,7 @@ public void shouldReportWhenMutationKilledByTest() throws IOException { when(this.testSource.translateTests(any(List.class))).thenReturn( Collections.singletonList(tu)); when( - this.hotswapper.apply(any(ClassName.class), any(ClassLoader.class), + this.hotswapper.insertClass(any(ClassName.class), any(ClassLoader.class), any(byte[].class))).thenReturn(true); this.testee.run(range, this.reporter, this.testSource); verify(this.reporter).report( From fc81d957cb5350084619f5727a651e542268089a Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 1 Aug 2022 09:11:33 +0100 Subject: [PATCH 2/5] exclude gwt classloader from transformation --- .../execute/CatchNewClassLoadersTransformer.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java b/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java index 0a0b8f914..5af7ec6fd 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java @@ -51,8 +51,7 @@ public byte[] transform(final ClassLoader loader, final String className, final Class classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) { - - if (className.equals(targetClass)) { + if (className.equals(targetClass) && shouldTransform(loader)) { CLASS_LOADERS.put(loader, null); // we might be mid-mutation so return the mutated bytes return currentMutant; @@ -78,4 +77,10 @@ private static Class checkClassForLoader(ClassLoader loader, String className } + private boolean shouldTransform(ClassLoader loader) { + // Only gwtmockito has been identified so far as a loader not to transform + // but there will be others + return !loader.getClass().getName().startsWith("com.google.gwtmockito."); + } + } From 3dd44bc56804dfc2872f7ee4bff282c6cfdbed01 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 1 Aug 2022 09:47:43 +0100 Subject: [PATCH 3/5] remove MutationGropuer extension point We now have an explicit assumption of 1 mutated class per jvm. Allowing arbritrary mutant groupings may violate this. --- .../org/pitest/mutationtest/config/SettingsFactory.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java index 86eb11299..fddf21edc 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java @@ -87,9 +87,11 @@ public JavaExecutableLocator getJavaExecutable() { } public MutationGrouperFactory getMutationGrouper() { - final Collection groupers = this.plugins - .findGroupers(); - return firstOrDefault(groupers, new DefaultMutationGrouperFactory()); + // Grouping behaviour is important. We cannot have more than 1 class mutated within + // a JVM or else the last mutation will poison the next. This restriction can only + // be removed if the hotswap functionality is reworked. + // Grouping behaviour is therefore hard coded for now. + return new DefaultMutationGrouperFactory(); } public void describeFeatures(Consumer enabled, Consumer disabled) { From f9b727ac775ad92f06439ca45c7a04ab124ab4d4 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Mon, 1 Aug 2022 09:49:12 +0100 Subject: [PATCH 4/5] quarkus test is still flaky --- .../src/test/java/org/pitest/PitMojoIT.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java index ab7614b16..6a9b2f38b 100755 --- a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java +++ b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java @@ -400,10 +400,10 @@ public void shouldWorkWithQuarkus() throws Exception { String actual = readResults(testDir); // Test is flaky. Needs investigation - assertThat(actual) - .contains( - "" + - "ExampleController.java"); + //assertThat(actual) + // .contains( + // "" + + // "ExampleController.java"); assertThat(actual) .contains( From e1ad19fb8d4687ef734812cd35f108aee4842aa7 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 17 Nov 2022 11:51:15 +0000 Subject: [PATCH 5/5] update readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 511ba5368..9a114fe3e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ Read all about it at http://pitest.org ## Releases +## 1.10.0 (unreleased) + +* #1067 Improved Quarkus and Roboelectric support + +As a result of #1067 it is important that mutations are only created for a single class for each JVM. The `MutationGrouper` extension point has therefore been removed as this allowed this constraint to be violated. Any third party plugins using this extension are no longer supported. + ### 1.9.11 * #1105 Aggregator resolves wrong file for out of package kotlin files with same name