From 963fd1ebb65fcc9d24bbe2c94b8b4d1a0338bb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Fri, 12 May 2023 19:21:41 +0200 Subject: [PATCH] Serialize tests during coverage calculation This generically fixes hcoles/pitest#760 and #73 for all platform engines, removing the Jupiter specific work-around from #74 and serializing test execution during coverage calculation using locks. This way the tests can also properly run in parallel later on during mutant hunting. --- .../junit5/JUnit5TestPluginFactory.java | 5 ++- .../pitest/junit5/JUnit5TestUnitFinder.java | 34 ++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/pitest/junit5/JUnit5TestPluginFactory.java b/src/main/java/org/pitest/junit5/JUnit5TestPluginFactory.java index 2cb3a19..fa8dc6c 100755 --- a/src/main/java/org/pitest/junit5/JUnit5TestPluginFactory.java +++ b/src/main/java/org/pitest/junit5/JUnit5TestPluginFactory.java @@ -27,11 +27,10 @@ public class JUnit5TestPluginFactory implements TestPluginFactory { @Override - public Configuration createTestFrameworkConfiguration(TestGroupConfig config, - ClassByteArraySource source, + public Configuration createTestFrameworkConfiguration(TestGroupConfig config, + ClassByteArraySource source, Collection excludedRunners, Collection includedTestMethods) { - System.setProperty("junit.jupiter.execution.parallel.enabled", "false"); return new JUnit5Configuration(config, includedTestMethods); } diff --git a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java index 8d9b79f..d52e1db 100755 --- a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java +++ b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java @@ -17,6 +17,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.emptyList; import static java.util.Collections.synchronizedList; @@ -26,6 +29,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.Filter; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.launcher.Launcher; @@ -35,6 +39,7 @@ import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.pitest.testapi.Description; +import org.pitest.testapi.NullExecutionListener; import org.pitest.testapi.TestGroupConfig; import org.pitest.testapi.TestUnit; import org.pitest.testapi.TestUnitExecutionListener; @@ -97,10 +102,15 @@ private class TestIdentifierListener implements TestExecutionListener { private final Class testClass; private final TestUnitExecutionListener l; private final List identifiers = synchronizedList(new ArrayList<>()); + private final boolean serializeExecution; + private final Map parentCoverageSerializers = new ConcurrentHashMap<>(); + private final Map coverageSerializers = new ConcurrentHashMap<>(); + private final ReentrantLock rootCoverageSerializer = new ReentrantLock(); public TestIdentifierListener(Class testClass, TestUnitExecutionListener l) { this.testClass = testClass; this.l = l; + serializeExecution = !(l instanceof NullExecutionListener); } List getIdentifiers() { @@ -117,12 +127,26 @@ public void executionStarted(TestIdentifier testIdentifier) { && !includedTestMethods.contains(((MethodSource)testIdentifier.getSource().get()).getMethodName())) { return; } + + if (serializeExecution) { + coverageSerializers.compute(testIdentifier.getUniqueIdObject(), (uniqueId, lock) -> { + if (lock != null) { + throw new AssertionError("No lock should be present"); + } + + return testIdentifier + .getParentIdObject() + .map(parentCoverageSerializers::get) + .orElse(rootCoverageSerializer); + }).lock(); + parentCoverageSerializers.put(testIdentifier.getUniqueIdObject(), new ReentrantLock()); + } + l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass)); identifiers.add(testIdentifier); } } - @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { // Classes with failing BeforeAlls never start execution and identify as 'containers' not 'tests' @@ -134,6 +158,14 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult } else if (testIdentifier.isTest()) { l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), true); } + + if (serializeExecution) { + parentCoverageSerializers.remove(testIdentifier.getUniqueIdObject()); + ReentrantLock lock = coverageSerializers.remove(testIdentifier.getUniqueIdObject()); + if (lock != null) { + lock.unlock(); + } + } } }