Skip to content

Commit 3ac9dec

Browse files
authored
Merge pull request #1409 from hcoles/bug/quarkus_classloader_leak
Support quarkus 3.22.2 and above
2 parents 18ade98 + a807064 commit 3ac9dec

File tree

5 files changed

+64
-13
lines changed

5 files changed

+64
-13
lines changed

pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,23 +380,48 @@ public void shouldWorkWithGWTMockito() throws Exception {
380380
@Test
381381
// note this test depends on the junit5 plugin
382382
public void shouldWorkWithQuarkus() throws Exception {
383-
assumeTrue(CurrentRuntime.version() >= 11);
383+
assumeTrue(CurrentRuntime.version() >= 17);
384384

385385
File testDir = prepare("/pit-quarkus");
386386
verifier.executeGoal("test");
387387
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
388388

389389
String actual = readResults(testDir);
390+
391+
assertThat(actual)
392+
.contains(
393+
"<mutation detected='true' status='KILLED' numberOfTestsRun='1'><sourceFile>ExampleController.java</sourceFile>");
394+
395+
assertThat(actual)
396+
.contains(
397+
"status='SURVIVED'");
398+
390399
// Test is flaky. Needs investigation
391400
//assertThat(actual)
392401
// .contains(
393402
// "<mutation detected='false' status='SURVIVED' numberOfTestsRun='2'>" +
394403
// "<sourceFile>ExampleController.java</sourceFile>");
395404

405+
}
406+
407+
@Test
408+
public void shouldWorkWithOlderQuarkusVersions() throws Exception {
409+
assumeTrue(CurrentRuntime.version() >= 17);
410+
411+
File testDir = prepare("/pit-quarkus", "-Dquarkus.platform.version=3.21.4");
412+
verifier.executeGoal("test");
413+
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
414+
415+
String actual = readResults(testDir);
416+
396417
assertThat(actual)
397418
.contains(
398419
"<mutation detected='true' status='KILLED' numberOfTestsRun='1'><sourceFile>ExampleController.java</sourceFile>");
399420

421+
assertThat(actual)
422+
.contains(
423+
"status='SURVIVED'");
424+
400425
}
401426

402427

@@ -517,7 +542,7 @@ private static String readCoverage(File testDir) throws IOException {
517542
return FileUtils.readFileToString(coverage);
518543
}
519544

520-
private File prepare(String testPath) throws IOException,
545+
private File prepare(String testPath, String ... options) throws IOException,
521546
VerificationException {
522547
String path = ResourceExtractor.extractResourcePath(getClass(), testPath,
523548
testFolder.getRoot(), true).getAbsolutePath();
@@ -527,6 +552,11 @@ private File prepare(String testPath) throws IOException,
527552
verifier.setDebug(true);
528553
verifier.getCliOptions().add("-Dverbose=true");
529554
verifier.getCliOptions().add("-Dpit.version=" + VERSION);
555+
556+
for (String option : options) {
557+
verifier.getCliOptions().add(option);
558+
}
559+
530560
verifier.getCliOptions().add(
531561
"-Dthreads=" + (Runtime.getRuntime().availableProcessors()));
532562

pitest-maven-verification/src/test/resources/pit-quarkus/pom.xml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
1313
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
1414
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
15-
<quarkus.platform.version>3.4.3</quarkus.platform.version>
15+
<quarkus.platform.version>3.22.3</quarkus.platform.version>
1616
<surefire-plugin.version>3.1.2</surefire-plugin.version>
17-
<pitest.junit5.version>1.0.0</pitest.junit5.version>
17+
<pitest.junit5.version>1.2.3</pitest.junit5.version>
1818
</properties>
1919
<dependencyManagement>
2020
<dependencies>
@@ -28,9 +28,9 @@
2828
</dependencies>
2929
</dependencyManagement>
3030
<dependencies>
31-
<dependency>
31+
<dependency>
3232
<groupId>io.quarkus</groupId>
33-
<artifactId>quarkus-resteasy-reactive</artifactId>
33+
<artifactId>quarkus-rest</artifactId>
3434
</dependency>
3535
<dependency>
3636
<groupId>io.quarkus</groupId>
@@ -47,10 +47,7 @@
4747
<artifactId>rest-assured</artifactId>
4848
<scope>test</scope>
4949
</dependency>
50-
<dependency>
51-
<groupId>io.quarkus</groupId>
52-
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
53-
</dependency>
50+
5451
</dependencies>
5552
<build>
5653
<plugins>

pitest-maven-verification/src/test/resources/pit-quarkus/src/main/java/com/example/service/ExampleService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
public class ExampleService {
88

99
public boolean doStuff(String s) {
10+
System.out.println("this survives");
1011
return s.equals("foo");
1112
}
1213

pitest/src/main/java/org/pitest/coverage/execute/CoverageMinion.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,12 @@ private static List<TestUnit> getTestsFromParent(
164164
private static List<TestUnit> discoverTests(Configuration testPlugin, List<ClassName> classes, CoveragePipe invokeQueue) {
165165
TestUnitExecutionListener listener = new CoverageTestExecutionListener(invokeQueue);
166166
FindTestUnits finder = new FindTestUnits(testPlugin, listener);
167+
167168
List<TestUnit> tus = finder
168169
.findTestUnitsForAllSuppliedClasses(classes.stream()
169170
.flatMap(ClassName.nameToClass())
170171
.collect(Collectors.toList()));
172+
171173
LOG.info(() -> "Found " + tus.size() + " tests");
172174
return tus;
173175
}

pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Map;
1111
import java.util.WeakHashMap;
1212
import java.util.logging.Logger;
13+
import java.util.stream.Collectors;
1314

1415
/**
1516
* Pitest mainly inserts mutants by calling Instrumentation.redefineClasses using
@@ -35,8 +36,14 @@ public class CatchNewClassLoadersTransformer implements ClassFileTransformer {
3536
private static String targetClass;
3637
private static byte[] currentMutant;
3738

39+
//
40+
// Storage was introduced to support Quarkus, however they changed their classloading strategy in
41+
// 3.22.2 so it is no longer necessary. As storage runs the risk of performance degradation if too
42+
// many classloaders are encountered removing this shouldn be investigated once older versions of
43+
// quarkus are out of circulation.
44+
//
3845
// What we want is a ConcurrentWeakHasSet, since that doesn't exist without writing one ourselves
39-
// we'll abuse a WeakHashMap and live with the synchronization
46+
// we'll abuse a WeakHashMap and live with the synchronization.
4047
static final Map<ClassLoader, Object> CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<>());
4148

4249
public static synchronized void setMutant(String className, byte[] mutant) {
@@ -59,7 +66,9 @@ public byte[] transform(final ClassLoader loader, final String className,
5966
final ProtectionDomain protectionDomain, final byte[] classfileBuffer) {
6067

6168
if (className.equals(targetClass) && shouldTransform(loader)) {
62-
CLASS_LOADERS.put(loader, null);
69+
if (shouldStore(loader)) {
70+
CLASS_LOADERS.put(loader, null);
71+
}
6372
// we might be mid-mutation so return the mutated bytes
6473
return currentMutant;
6574
}
@@ -84,16 +93,28 @@ private static Class<?> checkClassForLoader(ClassLoader loader, String className
8493

8594
}
8695

96+
private boolean shouldStore(ClassLoader loader) {
97+
// quarkus generates large numbers of ParentLastURLClassLoaders. They need to be
98+
// transformed when first encountered, but do not seem to be used to load the mutated
99+
// class in subsequent tests. Re-transforming them would introduce a significant performance hit.
100+
return !loader.getClass().getName().equals("io.quarkus.test.junit.classloading.ParentLastURLClassLoader");
101+
}
102+
87103
private boolean shouldTransform(ClassLoader loader) {
88104
// Only gwtmockito has been identified so far as a loader not to transform
89105
// but there will be others
90106
return !loader.getClass().getName().startsWith("com.google.gwtmockito.");
91107
}
92108

93109
private static void logClassloaders() {
94-
if (CLASS_LOADERS.size() > 1) {
110+
int count = CLASS_LOADERS.size();
111+
if (count > 30) {
112+
LOG.warning("Accumulated " + CLASS_LOADERS.size() + " classloaders, this will likely degrade performance. Please report this as an issue.");
113+
LOG.warning("Accumulated classloaders = " + CLASS_LOADERS.keySet().stream().map(Object::toString).collect(Collectors.joining("\n")));
114+
} else if (count > 1) {
95115
LOG.fine("Accumulated " + CLASS_LOADERS.size() + " classloaders");
96116
}
117+
97118
}
98119

99120
}

0 commit comments

Comments
 (0)