diff --git a/pom.xml b/pom.xml
index 6a9876f..df4dbbe 100755
--- a/pom.xml
+++ b/pom.xml
@@ -23,9 +23,10 @@
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
4.0.11
@@ -199,6 +200,12 @@
spock-core
test
+
+ junit
+ junit
+ ${junit4.version}
+ test
+
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..43ead63 100755
--- a/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java
+++ b/src/main/java/org/pitest/junit5/JUnit5TestUnitFinder.java
@@ -1,141 +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 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.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.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<>());
-
- public TestIdentifierListener(Class> testClass, TestUnitExecutionListener l) {
- this.testClass = testClass;
- this.l = l;
- }
-
- 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;
- }
- 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);
- }
- }
-
- }
-
-}
+/*
+ * 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 extends T>) 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), true);
+ 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), true);
+ 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 extends Annotation> annotation) {
+ return annotatedElement.isAnnotationPresent(annotation);
+ }
+
+ private boolean hasAnnotation(Set extends AnnotatedElement> methods, Class extends Annotation> 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);