From 7b7d28e47bd51fb7a054e4798e81061eb68dbeb2 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 1/3] 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 | 71 ++++++++++++++++++- 2 files changed, 72 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..615afaf 100755 --- a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java +++ b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java @@ -17,6 +17,10 @@ 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.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; import static java.util.Collections.emptyList; import static java.util.Collections.synchronizedList; @@ -26,6 +30,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 +40,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 +103,43 @@ private class TestIdentifierListener implements TestExecutionListener { private final Class testClass; private final TestUnitExecutionListener l; private final List identifiers = synchronizedList(new ArrayList<>()); + private final boolean serializeExecution; + // This map holds the locks that child tests of locked parent tests should use. + // For example parallel data-driven Spock features start the feature execution which is CONTAINER_AND_TEST, + // then wait for the parallel iteration executions to be finished which are TEST, + // then finish the feature execution. + // Due to that we cannot lock the iteration executions on the same lock as the feature executions, + // as the feature execution is around all the subordinate iteration executions. + // + // This logic will of course break if there is some test engine that does strange setups like + // having CONTAINER_AND_TEST with child CONTAINER that have child TEST and similar. + // If those engines happen to be used, tests will start to deadlock, as the grand-child test + // would not find the parent serializer and thus use the root serializer on which the grand-parent + // CONTAINER_AND_TEST already locks. + // + // This setup would probably not make much sense, so should not be taken into account + // unless such an engine actually pops up. If it does and someone tries to use it with PIT, + // the logic should maybe be made more sophisticated like remembering the parent-child relationships + // to be able to find the grand-parent serializer which is not possible stateless, because we are + // only able to get the parent identifier directly, but not further up stateless. + private final Map> parentCoverageSerializers = new ConcurrentHashMap<>(); + // This map holds the actual lock used for a specific test to be able to easily and safely unlock + // without the need to recalculate which lock to use. + private final Map coverageSerializers = new ConcurrentHashMap<>(); + private final ReentrantLock rootCoverageSerializer = new ReentrantLock(); public TestIdentifierListener(Class testClass, TestUnitExecutionListener l) { this.testClass = testClass; this.l = l; + // PIT gives a coverage recording listener here during coverage recording + // At the later stage during minion hunting a NullExecutionListener is given + // as PIT is only interested in the resulting list of identifiers. + // Serialization of test execution is only necessary during coverage calculation + // currently. To be on the safe side serialize test execution for any listener + // type except listener types where we know tests can run in parallel safely, + // i.e. currently the NullExecutionListener which is the only other one besides + // the coverage recording listener. + serializeExecution = !(l instanceof NullExecutionListener); } List getIdentifiers() { @@ -117,12 +156,32 @@ 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"); + } + + // find the serializer to lock the test on + // if there is a parent test locked, use the lock for its children if not, + // use the root serializer + return testIdentifier + .getParentIdObject() + .map(parentCoverageSerializers::get) + .map(lockRef -> lockRef.updateAndGet(parentLock -> + parentLock == null ? new ReentrantLock() : parentLock)) + .orElse(rootCoverageSerializer); + }).lock(); + // record a potential serializer for child tests to lock on + parentCoverageSerializers.put(testIdentifier.getUniqueIdObject(), new AtomicReference<>()); + } + 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 +193,16 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult } else if (testIdentifier.isTest()) { l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), true); } + + if (serializeExecution) { + // forget the potential serializer for child tests + parentCoverageSerializers.remove(testIdentifier.getUniqueIdObject()); + // unlock the serializer for the finished tests to let the next test continue + ReentrantLock lock = coverageSerializers.remove(testIdentifier.getUniqueIdObject()); + if (lock != null) { + lock.unlock(); + } + } } } From 0b75b5c28bfe410e950f970898ae363e2307b0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Sun, 14 May 2023 01:08:56 +0200 Subject: [PATCH 2/3] Make Spock specs atomic if certain criteria are met --- pom.xml | 7 + .../pitest/junit5/JUnit5TestUnitFinder.java | 598 ++++++++++++------ .../repository/TestSpecWithAfterAll.groovy | 34 + .../repository/TestSpecWithAfterClass.groovy | 34 + .../repository/TestSpecWithBeforeAll.groovy | 34 + .../repository/TestSpecWithBeforeClass.groovy | 34 + .../TestSpecWithClassRuleField.groovy | 33 + .../TestSpecWithClassRuleMethod.groovy | 34 + .../TestSpecWithSetupSpecWithoutShared.groovy | 38 ++ .../repository/TestSpecWithShared.groovy | 33 + .../repository/TestSpecWithStepwise.groovy | 31 + .../TestSpecWithStepwiseFeature.groovy | 31 + .../junit5/JUnit5TestUnitFinderTest.java | 78 ++- 13 files changed, 800 insertions(+), 219 deletions(-) create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterAll.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterClass.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeAll.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeClass.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleField.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleMethod.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithSetupSpecWithoutShared.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithShared.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwise.groovy create mode 100644 src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwiseFeature.groovy diff --git a/pom.xml b/pom.xml index 6a9876f..0e8e269 100755 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ 1.9.0 5.0.0 2.3-groovy-4.0 + 4.13.2 4.0.11 @@ -199,6 +200,12 @@ spock-core test + + junit + junit + ${junit4.version} + test + diff --git a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java index 615afaf..e799d9d 100755 --- a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java +++ b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java @@ -1,210 +1,388 @@ -/* - * Copyright 2017 Tobias Stadler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. - */ -package org.pitest.junit5; - -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.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; - -import static java.util.Collections.emptyList; -import static java.util.Collections.synchronizedList; -import static java.util.Collections.unmodifiableList; -import static java.util.stream.Collectors.toList; - -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; -import org.junit.platform.launcher.TagFilter; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestIdentifier; -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; -import org.pitest.testapi.TestUnitFinder; - -/** - * - * @author Tobias Stadler - */ -public class JUnit5TestUnitFinder implements TestUnitFinder { - - private final TestGroupConfig testGroupConfig; - - private final Collection includedTestMethods; - - private final Launcher launcher; - - public JUnit5TestUnitFinder(TestGroupConfig testGroupConfig, Collection includedTestMethods) { - this.testGroupConfig = testGroupConfig; - this.includedTestMethods = includedTestMethods; - this.launcher = LauncherFactory.create(); - } - - @Override - public List findTestUnits(Class clazz, TestUnitExecutionListener executionListener) { - if(clazz.getEnclosingClass() != null) { - return emptyList(); - } - - List filters = new ArrayList<>(2); - try { - List excludedGroups = testGroupConfig.getExcludedGroups(); - if(excludedGroups != null && !excludedGroups.isEmpty()) { - filters.add(TagFilter.excludeTags(excludedGroups)); - } - - List includedGroups = testGroupConfig.getIncludedGroups(); - if(includedGroups != null && !includedGroups.isEmpty()) { - filters.add(TagFilter.includeTags(includedGroups)); - } - } catch(PreconditionViolationException e) { - throw new IllegalArgumentException("Error creating tag filter", e); - } - - TestIdentifierListener listener = new TestIdentifierListener(clazz, executionListener); - - launcher.execute(LauncherDiscoveryRequestBuilder - .request() - .selectors(DiscoverySelectors.selectClass(clazz)) - .filters(filters.toArray(new Filter[filters.size()])) - .build(), listener); - - return listener.getIdentifiers() - .stream() - .map(testIdentifier -> new JUnit5TestUnit(clazz, testIdentifier)) - .collect(toList()); - } - - private class TestIdentifierListener implements TestExecutionListener { - private final Class testClass; - private final TestUnitExecutionListener l; - private final List identifiers = synchronizedList(new ArrayList<>()); - private final boolean serializeExecution; - // This map holds the locks that child tests of locked parent tests should use. - // For example parallel data-driven Spock features start the feature execution which is CONTAINER_AND_TEST, - // then wait for the parallel iteration executions to be finished which are TEST, - // then finish the feature execution. - // Due to that we cannot lock the iteration executions on the same lock as the feature executions, - // as the feature execution is around all the subordinate iteration executions. - // - // This logic will of course break if there is some test engine that does strange setups like - // having CONTAINER_AND_TEST with child CONTAINER that have child TEST and similar. - // If those engines happen to be used, tests will start to deadlock, as the grand-child test - // would not find the parent serializer and thus use the root serializer on which the grand-parent - // CONTAINER_AND_TEST already locks. - // - // This setup would probably not make much sense, so should not be taken into account - // unless such an engine actually pops up. If it does and someone tries to use it with PIT, - // the logic should maybe be made more sophisticated like remembering the parent-child relationships - // to be able to find the grand-parent serializer which is not possible stateless, because we are - // only able to get the parent identifier directly, but not further up stateless. - private final Map> parentCoverageSerializers = new ConcurrentHashMap<>(); - // This map holds the actual lock used for a specific test to be able to easily and safely unlock - // without the need to recalculate which lock to use. - private final Map coverageSerializers = new ConcurrentHashMap<>(); - private final ReentrantLock rootCoverageSerializer = new ReentrantLock(); - - public TestIdentifierListener(Class testClass, TestUnitExecutionListener l) { - this.testClass = testClass; - this.l = l; - // PIT gives a coverage recording listener here during coverage recording - // At the later stage during minion hunting a NullExecutionListener is given - // as PIT is only interested in the resulting list of identifiers. - // Serialization of test execution is only necessary during coverage calculation - // currently. To be on the safe side serialize test execution for any listener - // type except listener types where we know tests can run in parallel safely, - // i.e. currently the NullExecutionListener which is the only other one besides - // the coverage recording listener. - serializeExecution = !(l instanceof NullExecutionListener); - } - - List getIdentifiers() { - return unmodifiableList(new ArrayList<>(identifiers)); - } - - @Override - public void executionStarted(TestIdentifier testIdentifier) { - if (testIdentifier.isTest()) { - // filter out testMethods - if (includedTestMethods != null && !includedTestMethods.isEmpty() - && testIdentifier.getSource().isPresent() - && testIdentifier.getSource().get() instanceof MethodSource - && !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"); - } - - // find the serializer to lock the test on - // if there is a parent test locked, use the lock for its children if not, - // use the root serializer - return testIdentifier - .getParentIdObject() - .map(parentCoverageSerializers::get) - .map(lockRef -> lockRef.updateAndGet(parentLock -> - parentLock == null ? new ReentrantLock() : parentLock)) - .orElse(rootCoverageSerializer); - }).lock(); - // record a potential serializer for child tests to lock on - parentCoverageSerializers.put(testIdentifier.getUniqueIdObject(), new AtomicReference<>()); - } - - 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' - if (testExecutionResult.getStatus() == TestExecutionResult.Status.FAILED) { - if (!identifiers.contains(testIdentifier)) { - identifiers.add(testIdentifier); - } - l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), false); - } else if (testIdentifier.isTest()) { - l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), true); - } - - if (serializeExecution) { - // forget the potential serializer for child tests - parentCoverageSerializers.remove(testIdentifier.getUniqueIdObject()); - // unlock the serializer for the finished tests to let the next test continue - ReentrantLock lock = coverageSerializers.remove(testIdentifier.getUniqueIdObject()); - if (lock != null) { - lock.unlock(); - } - } - } - - } - -} +/* + * Copyright 2017 Tobias Stadler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and limitations under the License. + */ +package org.pitest.junit5; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; + +import static java.util.Collections.emptyList; +import static java.util.Collections.synchronizedList; +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.toList; + +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.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.TagFilter; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +import org.pitest.functional.FCollection; +import org.pitest.reflection.Reflection; +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; +import org.pitest.testapi.TestUnitFinder; + +/** + * + * @author Tobias Stadler + */ +public class JUnit5TestUnitFinder implements TestUnitFinder { + private static final Optional> SPECIFICATION = + findClass("spock.lang.Specification"); + private static final Optional> BEFORE_ALL = + findClass("org.junit.jupiter.api.BeforeAll"); + private static final Optional> BEFORE_CLASS = + findClass("org.junit.BeforeClass"); + private static final Optional> AFTER_ALL = + findClass("org.junit.jupiter.api.AfterAll"); + private static final Optional> AFTER_CLASS = + findClass("org.junit.AfterClass"); + private static final Optional> CLASS_RULE = + findClass("org.junit.ClassRule"); + private static final Optional> SHARED = + findClass("spock.lang.Shared"); + private static final Optional> STEPWISE = + findClass("spock.lang.Stepwise"); + + private final TestGroupConfig testGroupConfig; + + private final Collection includedTestMethods; + + private final Launcher launcher; + + public JUnit5TestUnitFinder(TestGroupConfig testGroupConfig, Collection includedTestMethods) { + this.testGroupConfig = testGroupConfig; + this.includedTestMethods = includedTestMethods; + this.launcher = LauncherFactory.create(); + } + + @Override + public List findTestUnits(Class clazz, TestUnitExecutionListener executionListener) { + if(clazz.getEnclosingClass() != null) { + return emptyList(); + } + + List filters = new ArrayList<>(2); + try { + List excludedGroups = testGroupConfig.getExcludedGroups(); + if(excludedGroups != null && !excludedGroups.isEmpty()) { + filters.add(TagFilter.excludeTags(excludedGroups)); + } + + List includedGroups = testGroupConfig.getIncludedGroups(); + if(includedGroups != null && !includedGroups.isEmpty()) { + filters.add(TagFilter.includeTags(includedGroups)); + } + } catch(PreconditionViolationException e) { + throw new IllegalArgumentException("Error creating tag filter", e); + } + + TestIdentifierListener listener = new TestIdentifierListener(clazz, executionListener); + + launcher.execute(LauncherDiscoveryRequestBuilder + .request() + .selectors(DiscoverySelectors.selectClass(clazz)) + .filters(filters.toArray(new Filter[filters.size()])) + .build(), listener); + + return listener.getIdentifiers() + .stream() + .map(testIdentifier -> new JUnit5TestUnit(clazz, testIdentifier)) + .collect(toList()); + } + + @SuppressWarnings("unchecked") + private static Optional> findClass(String className) { + try { + return Optional.of(((Class) Class.forName(className))); + } catch (final ClassNotFoundException ex) { + return Optional.empty(); + } + } + + private class TestIdentifierListener implements TestExecutionListener { + private final Class testClass; + private final TestUnitExecutionListener l; + private final List identifiers = synchronizedList(new ArrayList<>()); + private final boolean serializeExecution; + // This map holds the locks that child tests of locked parent tests should use. + // For example parallel data-driven Spock features start the feature execution which is CONTAINER_AND_TEST, + // then wait for the parallel iteration executions to be finished which are TEST, + // then finish the feature execution. + // Due to that we cannot lock the iteration executions on the same lock as the feature executions, + // as the feature execution is around all the subordinate iteration executions. + // + // This logic will of course break if there is some test engine that does strange setups like + // having CONTAINER_AND_TEST with child CONTAINER that have child TEST and similar. + // If those engines happen to be used, tests will start to deadlock, as the grand-child test + // would not find the parent serializer and thus use the root serializer on which the grand-parent + // CONTAINER_AND_TEST already locks. + // + // This setup would probably not make much sense, so should not be taken into account + // unless such an engine actually pops up. If it does and someone tries to use it with PIT, + // the logic should maybe be made more sophisticated like remembering the parent-child relationships + // to be able to find the grand-parent serializer which is not possible stateless, because we are + // only able to get the parent identifier directly, but not further up stateless. + private final Map> parentCoverageSerializers = new ConcurrentHashMap<>(); + // This map holds the actual lock used for a specific test to be able to easily and safely unlock + // without the need to recalculate which lock to use. + private final Map coverageSerializers = new ConcurrentHashMap<>(); + private final ReentrantLock rootCoverageSerializer = new ReentrantLock(); + + public TestIdentifierListener(Class testClass, TestUnitExecutionListener l) { + this.testClass = testClass; + this.l = l; + // PIT gives a coverage recording listener here during coverage recording + // At the later stage during minion hunting a NullExecutionListener is given + // as PIT is only interested in the resulting list of identifiers. + // Serialization of test execution is only necessary during coverage calculation + // currently. To be on the safe side serialize test execution for any listener + // type except listener types where we know tests can run in parallel safely, + // i.e. currently the NullExecutionListener which is the only other one besides + // the coverage recording listener. + serializeExecution = !(l instanceof NullExecutionListener); + } + + List getIdentifiers() { + return unmodifiableList(new ArrayList<>(identifiers)); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + if (shouldTreatAsOneUnit(testIdentifier)) { + if (hasClassSource(testIdentifier)) { + if (serializeExecution) { + lock(testIdentifier); + } + + l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass)); + identifiers.add(testIdentifier); + } + return; + } + + if (testIdentifier.isTest()) { + // filter out testMethods + if (includedTestMethods != null && !includedTestMethods.isEmpty() + && hasMethodSource(testIdentifier) + && !includedTestMethods.contains(((MethodSource) testIdentifier.getSource().get()).getMethodName())) { + return; + } + + if (serializeExecution) { + lock(testIdentifier); + // record a potential serializer for child tests to lock on + parentCoverageSerializers.put(testIdentifier.getUniqueIdObject(), new AtomicReference<>()); + } + + l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass)); + identifiers.add(testIdentifier); + } + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + if (shouldTreatAsOneUnit(testIdentifier)) { + if (hasClassSource(testIdentifier)) { + l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), + testExecutionResult.getStatus() != TestExecutionResult.Status.FAILED); + // unlock the serializer for the finished tests to let the next test continue + unlock(testIdentifier); + } + return; + } + + // Jupiter classes with failing BeforeAlls never start execution and identify as 'containers' not 'tests' + if (testExecutionResult.getStatus() == TestExecutionResult.Status.FAILED) { + if (!identifiers.contains(testIdentifier)) { + identifiers.add(testIdentifier); + } + l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), false); + } else if (testIdentifier.isTest()) { + l.executionFinished(new Description(testIdentifier.getUniqueId(), testClass), true); + } + + if (serializeExecution) { + // forget the potential serializer for child tests + parentCoverageSerializers.remove(testIdentifier.getUniqueIdObject()); + // unlock the serializer for the finished tests to let the next test continue + unlock(testIdentifier); + } + } + + public void lock(TestIdentifier testIdentifier) { + coverageSerializers.compute(testIdentifier.getUniqueIdObject(), (uniqueId, lock) -> { + if (lock != null) { + throw new AssertionError("No lock should be present"); + } + + // find the serializer to lock the test on + // if there is a parent test locked, use the lock for its children if not, + // use the root serializer + return testIdentifier + .getParentIdObject() + .map(parentCoverageSerializers::get) + .map(lockRef -> lockRef.updateAndGet(parentLock -> + parentLock == null ? new ReentrantLock() : parentLock)) + .orElse(rootCoverageSerializer); + }).lock(); + } + + public void unlock(TestIdentifier testIdentifier) { + ReentrantLock lock = coverageSerializers.remove(testIdentifier.getUniqueIdObject()); + if (lock != null) { + lock.unlock(); + } + } + + private boolean hasClassSource(TestIdentifier testIdentifier) { + return testIdentifier.getSource().filter(ClassSource.class::isInstance).isPresent(); + } + + private boolean hasMethodSource(TestIdentifier testIdentifier) { + return testIdentifier.getSource().filter(MethodSource.class::isInstance).isPresent(); + } + + private boolean shouldTreatAsOneUnit(TestIdentifier testIdentifier) { + return shouldTreatSpockSpecificationAsOneUnit(testIdentifier); + } + + private boolean shouldTreatSpockSpecificationAsOneUnit(TestIdentifier testIdentifier) { + Optional> optionalTestClass = getTestClass(testIdentifier); + if (!optionalTestClass.isPresent()) { + return false; + } + + Class testClass = optionalTestClass.get(); + if (!isSpockSpecification(testClass)) { + return false; + } + + Set methods = Reflection.allMethods(testClass); + return hasBeforeAllAnnotations(methods) + || hasBeforeClassAnnotations(methods) + || hasAfterAllAnnotations(methods) + || hasAfterClassAnnotations(methods) + || hasClassRuleAnnotations(testClass, methods) + || hasAnnotation(testClass, STEPWISE.orElseThrow(AssertionError::new)) + || hasAnnotation(methods, STEPWISE.orElseThrow(AssertionError::new)) + || hasMethodNamed(methods, "setupSpec") + || hasMethodNamed(methods, "cleanupSpec") + || hasSharedField(testClass); + } + + private Optional> getTestClass(TestIdentifier testIdentifier) { + if (hasClassSource(testIdentifier)) { + return Optional.of( + testIdentifier + .getSource() + .map(ClassSource.class::cast) + .orElseThrow(AssertionError::new) + .getJavaClass()); + } + + if (hasMethodSource(testIdentifier)) { + return Optional.of( + testIdentifier + .getSource() + .map(MethodSource.class::cast) + .orElseThrow(AssertionError::new) + .getJavaClass()); + } + + return Optional.empty(); + } + + private boolean isSpockSpecification(Class clazz) { + return SPECIFICATION.filter(specification -> specification.isAssignableFrom(testClass)).isPresent(); + } + + private boolean hasBeforeAllAnnotations(Set methods) { + return BEFORE_ALL.filter(beforeAll -> hasAnnotation(methods, beforeAll)).isPresent(); + } + + private boolean hasBeforeClassAnnotations(Set methods) { + return BEFORE_CLASS.filter(beforeClass -> hasAnnotation(methods, beforeClass)).isPresent(); + } + + private boolean hasAfterAllAnnotations(Set methods) { + return AFTER_ALL.filter(afterAll -> hasAnnotation(methods, afterAll)).isPresent(); + } + + private boolean hasAfterClassAnnotations(Set methods) { + return AFTER_CLASS.filter(afterClass -> hasAnnotation(methods, afterClass)).isPresent(); + } + + private boolean hasClassRuleAnnotations(Class clazz, Set methods) { + return CLASS_RULE.filter(aClass -> hasAnnotation(methods, aClass) + || hasAnnotation(Reflection.publicFields(clazz), aClass)).isPresent(); + } + + private boolean hasAnnotation(AnnotatedElement annotatedElement, Class annotation) { + return annotatedElement.isAnnotationPresent(annotation); + } + + private boolean hasAnnotation(Set methods, Class annotation) { + return FCollection.contains(methods, annotatedElement -> annotatedElement.isAnnotationPresent(annotation)); + } + + private boolean hasMethodNamed(Set methods, String methodName) { + return FCollection.contains(methods, havingName(methodName)); + } + + private Predicate havingName(String methodName) { + return method -> method.getName().equals(methodName); + } + + private boolean hasSharedField(Class clazz) { + return hasAnnotation(allFields(clazz), SHARED.orElseThrow(AssertionError::new)); + } + + private Set allFields(Class clazz) { + final Set fields = new LinkedHashSet<>(); + if (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + fields.addAll(allFields(clazz.getSuperclass())); + } + return fields; + } + + } + +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterAll.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterAll.groovy new file mode 100644 index 0000000..e24a0d7 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterAll.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.jupiter.api.AfterAll +import spock.lang.Specification + +class TestSpecWithAfterAll extends Specification { + @AfterAll + static afterAll() { + } + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterClass.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterClass.groovy new file mode 100644 index 0000000..5e13586 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithAfterClass.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.AfterClass +import spock.lang.Specification + +class TestSpecWithAfterClass extends Specification { + @AfterClass + static afterClass() { + } + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeAll.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeAll.groovy new file mode 100644 index 0000000..2fff53d --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeAll.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.jupiter.api.BeforeAll +import spock.lang.Specification + +class TestSpecWithBeforeAll extends Specification { + @BeforeAll + static beforeAll() { + } + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeClass.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeClass.groovy new file mode 100644 index 0000000..e703288 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithBeforeClass.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.BeforeClass +import spock.lang.Specification + +class TestSpecWithBeforeClass extends Specification { + @BeforeClass + static beforeClass() { + } + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleField.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleField.groovy new file mode 100644 index 0000000..678b430 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleField.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.ClassRule +import spock.lang.Specification + +class TestSpecWithClassRuleField extends Specification { + @ClassRule + public static classRule + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleMethod.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleMethod.groovy new file mode 100644 index 0000000..7833c24 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithClassRuleMethod.groovy @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import org.junit.ClassRule +import spock.lang.Specification + +class TestSpecWithClassRuleMethod extends Specification { + @ClassRule + static afterClass() { + } + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithSetupSpecWithoutShared.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithSetupSpecWithoutShared.groovy new file mode 100644 index 0000000..23afd09 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithSetupSpecWithoutShared.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import spock.lang.Shared +import spock.lang.Specification + +class TestSpecWithSetupSpecWithoutShared extends Specification { + static fail = true + + def setupSpec() { + fail = false + } + + def aTest() { + expect: + !fail + } + + def anotherTest() { + expect: + !fail + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithShared.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithShared.groovy new file mode 100644 index 0000000..57b7644 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithShared.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import spock.lang.Shared +import spock.lang.Specification + +class TestSpecWithShared extends Specification { + @Shared + foo + + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwise.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwise.groovy new file mode 100644 index 0000000..8d27cd5 --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwise.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import spock.lang.Specification +import spock.lang.Stepwise + +@Stepwise +class TestSpecWithStepwise extends Specification { + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwiseFeature.groovy b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwiseFeature.groovy new file mode 100644 index 0000000..a94803a --- /dev/null +++ b/src/test/groovy/org/pitest/junit5/repository/TestSpecWithStepwiseFeature.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.pitest.junit5.repository + +import spock.lang.Specification +import spock.lang.Stepwise + +class TestSpecWithStepwiseFeature extends Specification { + @Stepwise + def test() { + expect: + true + + where: + i << (1..2) + } +} diff --git a/src/test/java/org/pitest/junit5/JUnit5TestUnitFinderTest.java b/src/test/java/org/pitest/junit5/JUnit5TestUnitFinderTest.java index 1f17e48..bb202c0 100644 --- a/src/test/java/org/pitest/junit5/JUnit5TestUnitFinderTest.java +++ b/src/test/java/org/pitest/junit5/JUnit5TestUnitFinderTest.java @@ -47,8 +47,14 @@ import org.pitest.junit5.repository.TestClassWithTestFactoryAnnotation; import org.pitest.junit5.repository.TestClassWithTestTemplateAnnotation; import org.pitest.junit5.repository.TestClassWithoutAnnotations; -import org.pitest.junit5.repository.TestSpecWithCleanupSpec; import org.pitest.junit5.repository.TestSpecWithAbortingFeature; +import org.pitest.junit5.repository.TestSpecWithAfterAll; +import org.pitest.junit5.repository.TestSpecWithAfterClass; +import org.pitest.junit5.repository.TestSpecWithBeforeAll; +import org.pitest.junit5.repository.TestSpecWithBeforeClass; +import org.pitest.junit5.repository.TestSpecWithClassRuleField; +import org.pitest.junit5.repository.TestSpecWithClassRuleMethod; +import org.pitest.junit5.repository.TestSpecWithCleanupSpec; import org.pitest.junit5.repository.TestSpecWithDataDrivenFeature; import org.pitest.junit5.repository.TestSpecWithFailingCleanupSpec; import org.pitest.junit5.repository.TestSpecWithFailingFeature; @@ -58,7 +64,11 @@ import org.pitest.junit5.repository.TestSpecWithMixedPassAndFail; import org.pitest.junit5.repository.TestSpecWithMultiplePassingFeatures; import org.pitest.junit5.repository.TestSpecWithSetupSpec; +import org.pitest.junit5.repository.TestSpecWithSetupSpecWithoutShared; +import org.pitest.junit5.repository.TestSpecWithShared; import org.pitest.junit5.repository.TestSpecWithSimpleFeature; +import org.pitest.junit5.repository.TestSpecWithStepwise; +import org.pitest.junit5.repository.TestSpecWithStepwiseFeature; import org.pitest.junit5.repository.TestSpecWithTags; import org.pitest.junit5.repository.TestSpecWithoutFeatures; import org.pitest.testapi.Description; @@ -305,8 +315,8 @@ void findsAndRunsTestsWithAfterAll() { } @Test - void findsAndRunsTestsWithCleanupSpec() { - findsAndRunsNTests(2, TestSpecWithCleanupSpec.class); + void findsAndRunsAtomicTestWithCleanupSpec() { + findsAndRunsNTests(1, TestSpecWithCleanupSpec.class); } @Test @@ -315,8 +325,8 @@ void findsAndRunsTestsWithBeforeAll() { } @Test - void findsAndRunsTestsWithSetupSpec() { - findsAndRunsNTests(2, TestSpecWithSetupSpec.class); + void findsAndRunsAtomicTestWithSetupSpec() { + findsAndRunsNTests(1, TestSpecWithSetupSpec.class); } @Test @@ -325,8 +335,8 @@ void findsAndRunsTestsWithFailingAfterAll() { } @Test - void findsAndRunsTestsWithFailingCleanupSpec() { - findsAndRunsNTests(2, TestSpecWithFailingCleanupSpec.class); + void findsAndRunsAtomicTestWithFailingCleanupSpec() { + findsAndRunsNTests(1, TestSpecWithFailingCleanupSpec.class); } @Test @@ -335,8 +345,8 @@ void findsNoTestsWithFailingBeforeAll() { } @Test - void findsNoTestsWithFailingSetupSpec() { - findsAndRunsNTests(0, TestSpecWithFailingSetupSpec.class); + void findsAndRunsAtomicTestWithFailingSetupSpec() { + findsAndRunsNTests(1, TestSpecWithFailingSetupSpec.class); } @Test @@ -344,6 +354,56 @@ void findsNoTestsWithNestedTestClassWithoutAnnotations() { findsAndRunsNTests(0, TestClassWithNestedClassWithoutAnnotations.class); } + @Test + void findsAndRunsAtomicTestWithAfterAll() { + findsAndRunsNTests(1, TestSpecWithAfterAll.class); + } + + @Test + void findsAndRunsAtomicTestWithAfterClass() { + findsAndRunsNTests(1, TestSpecWithAfterClass.class); + } + + @Test + void findsAndRunsAtomicTestWithBeforeAll() { + findsAndRunsNTests(1, TestSpecWithBeforeAll.class); + } + + @Test + void findsAndRunsAtomicTestWithBeforeClass() { + findsAndRunsNTests(1, TestSpecWithBeforeClass.class); + } + + @Test + void findsAndRunsAtomicTestWithClassRuleField() { + findsAndRunsNTests(1, TestSpecWithClassRuleField.class); + } + + @Test + void findsAndRunsAtomicTestWithClassRuleMethod() { + findsAndRunsNTests(1, TestSpecWithClassRuleMethod.class); + } + + @Test + void findsAndRunsAtomicTestWithSetupSpecWithoutShared() { + findsAndRunsNTests(1, TestSpecWithSetupSpecWithoutShared.class); + } + + @Test + void findsAndRunsAtomicTestWithShared() { + findsAndRunsNTests(1, TestSpecWithShared.class); + } + + @Test + void findsAndRunsAtomicTestWithStepwise() { + findsAndRunsNTests(1, TestSpecWithStepwise.class); + } + + @Test + void findsAndRunsAtomicTestWithStepwiseFeature() { + findsAndRunsNTests(1, TestSpecWithStepwiseFeature.class); + } + @Test void findsAndRunsCucumberTests() { findsAndRunsNTests(1, RunCucumberTest.class); From 8e6bfa476cceafdbb4d6135fbce14ef10deb91d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Fri, 12 May 2023 20:29:27 +0200 Subject: [PATCH 3/3] Suppress the parallel warning during coverage calculation As we properly serialize the coverage calculation, the warning that is logged after detecting multiple threads can be suppressed. --- pom.xml | 2 +- src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 0e8e269..df4dbbe 100755 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 1.9.2 5.9.2 2.7.6 - 1.9.0 + 1.13.2 5.0.0 2.3-groovy-4.0 4.13.2 diff --git a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java index e799d9d..43ead63 100755 --- a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java +++ b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java @@ -191,7 +191,7 @@ public void executionStarted(TestIdentifier testIdentifier) { lock(testIdentifier); } - l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass)); + l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass), true); identifiers.add(testIdentifier); } return; @@ -211,7 +211,7 @@ && hasMethodSource(testIdentifier) parentCoverageSerializers.put(testIdentifier.getUniqueIdObject(), new AtomicReference<>()); } - l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass)); + l.executionStarted(new Description(testIdentifier.getUniqueId(), testClass), true); identifiers.add(testIdentifier); } }