This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new bc465f5  IGNITE-15543 Support injecting static variables with the 
WorkDirectory extension (#357)
bc465f5 is described below

commit bc465f5b2a8877efb08ad5ef39d2a861b53f021b
Author: Alexander Polovtcev <[email protected]>
AuthorDate: Thu Sep 30 15:42:33 2021 +0300

    IGNITE-15543 Support injecting static variables with the WorkDirectory 
extension (#357)
---
 .../CheckUnusedDependenciesAndPluginsInParent.sh   |   2 +-
 modules/core/pom.xml                               |   6 +
 .../testframework/SystemPropertiesExtension.java   |  45 +---
 .../internal/testframework/WithSystemProperty.java |  22 +-
 .../testframework/WorkDirectoryExtension.java      | 113 +++++---
 .../testframework/WorkDirectoryExtensionTest.java  | 293 +++++++++++++++++++++
 parent/pom.xml                                     |  24 +-
 7 files changed, 415 insertions(+), 90 deletions(-)

diff --git 
a/check-rules/maven-check-scripts/CheckUnusedDependenciesAndPluginsInParent.sh 
b/check-rules/maven-check-scripts/CheckUnusedDependenciesAndPluginsInParent.sh
index 02d2382..1c5a15a 100644
--- 
a/check-rules/maven-check-scripts/CheckUnusedDependenciesAndPluginsInParent.sh
+++ 
b/check-rules/maven-check-scripts/CheckUnusedDependenciesAndPluginsInParent.sh
@@ -17,7 +17,7 @@ set -o nounset; set -o errexit; set -o pipefail; set -o 
errtrace; set -o functra
 
 ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../.."
 POMS=$(find ${ROOT} -name pom.xml | grep -v parent)
-for xpath in 
"project/dependencyManagement/dependencies/dependency/artifactId/text()" \
+for xpath in 
"project/dependencyManagement/dependencies/dependency[not(scope='import')]/artifactId/text()"
 \
              
"project/build/pluginManagement/plugins/plugin/artifactId/text()"; do
        xpath -q -e "${xpath}" ${ROOT}/parent/pom.xml | \
       while read -r declaration; do
diff --git a/modules/core/pom.xml b/modules/core/pom.xml
index a869550..71c0145 100644
--- a/modules/core/pom.xml
+++ b/modules/core/pom.xml
@@ -57,6 +57,12 @@
             <artifactId>junit-jupiter-engine</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-testkit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesExtension.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesExtension.java
index a1c9caa..5e4db6e 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesExtension.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesExtension.java
@@ -29,7 +29,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.extension.ExtensionContext;
 
 /**
- * JUnit rule that manages usage of {@link WithSystemProperty} 
annotations.<br/>
+ * JUnit rule that manages usage of {@link WithSystemProperty} annotations.<br>
  * Should be used in {@link ExtendWith}.
  *
  * @see WithSystemProperty
@@ -37,7 +37,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
  */
 public class SystemPropertiesExtension implements
     BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback 
{
-    /** Class properties. */
+    /** Method properties. */
     @SuppressWarnings("InstanceVariableMayNotBeInitialized")
     private List<Prop<String, String>> testMethodSysProps;
 
@@ -75,26 +75,20 @@ public class SystemPropertiesExtension implements
      * @param testCls Current test class.
      * @return List of updated properties in reversed order.
      */
-    private List<Prop<String, String>> 
extractSystemPropertiesBeforeClass(Class<?> testCls) {
+    private static List<Prop<String, String>> 
extractSystemPropertiesBeforeClass(Class<?> testCls) {
         List<WithSystemProperty[]> allProps = new ArrayList<>();
 
         for (Class<?> cls = testCls; cls != null; cls = cls.getSuperclass()) {
-            SystemPropertiesList clsProps = 
cls.getAnnotation(SystemPropertiesList.class);
-
-            if (clsProps != null)
-                allProps.add(clsProps.value());
-            else {
-                WithSystemProperty clsProp = 
cls.getAnnotation(WithSystemProperty.class);
+            WithSystemProperty[] props = 
cls.getAnnotationsByType(WithSystemProperty.class);
 
-                if (clsProp != null)
-                    allProps.add(new WithSystemProperty[] {clsProp});
-            }
+            if (props.length > 0)
+                allProps.add(props);
         }
 
         Collections.reverse(allProps);
 
         // List of system properties to set when all tests in class are 
finished.
-        final List<Prop<String, String>> clsSysProps = new ArrayList<>();
+        List<Prop<String, String>> clsSysProps = new ArrayList<>();
 
         for (WithSystemProperty[] props : allProps) {
             for (WithSystemProperty prop : props) {
@@ -115,29 +109,16 @@ public class SystemPropertiesExtension implements
      * @param testMtd Current test method.
      * @return List of updated properties in reversed order.
      */
-    public List<Prop<String, String>> 
extractSystemPropertiesBeforeTestMethod(Method testMtd) {
-        WithSystemProperty[] allProps = null;
-
-        SystemPropertiesList testProps = 
testMtd.getAnnotation(SystemPropertiesList.class);
-
-        if (testProps != null)
-            allProps = testProps.value();
-        else {
-            WithSystemProperty testProp = 
testMtd.getAnnotation(WithSystemProperty.class);
-
-            if (testProp != null)
-                allProps = new WithSystemProperty[] {testProp};
-        }
+    private static List<Prop<String, String>> 
extractSystemPropertiesBeforeTestMethod(Method testMtd) {
+        WithSystemProperty[] allProps = 
testMtd.getAnnotationsByType(WithSystemProperty.class);
 
         // List of system properties to set when test is finished.
         List<Prop<String, String>> testSysProps = new ArrayList<>();
 
-        if (allProps != null) {
-            for (WithSystemProperty prop : allProps) {
-                String oldVal = System.setProperty(prop.key(), prop.value());
+        for (WithSystemProperty prop : allProps) {
+            String oldVal = System.setProperty(prop.key(), prop.value());
 
-                testSysProps.add(new Prop<>(prop.key(), oldVal));
-            }
+            testSysProps.add(new Prop<>(prop.key(), oldVal));
         }
 
         Collections.reverse(testSysProps);
@@ -150,7 +131,7 @@ public class SystemPropertiesExtension implements
      *
      * @param sysProps List of properties to clear.
      */
-    private void clearSystemProperties(List<Prop<String, String>> sysProps) {
+    private static void clearSystemProperties(List<Prop<String, String>> 
sysProps) {
         if (sysProps == null)
             return; // Nothing to do.
 
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/WithSystemProperty.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WithSystemProperty.java
index 24407ff..9f8102c 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/WithSystemProperty.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WithSystemProperty.java
@@ -26,12 +26,12 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 
 /**
- * Annotation that defines a scope with specific system property 
configured.<br/>
- * <br/>
- * Might be used on class level or on method level. Multiple annotations might 
be applied to the same class/method.<br/>
- * <br/>
- * In short, these two approaches are basically equivalent:<br/>
- * <br/>
+ * Annotation that defines a scope with specific system property 
configured.<br>
+ * <br>
+ * Might be used on class level or on method level. Multiple annotations might 
be applied to the same class/method.<br>
+ * <br>
+ * In short, these two approaches are basically equivalent:<br>
+ * <br>
  * Short:
  * <pre>{@code  @WithSystemProperty(key = "name", value = "val")
  *  public class SomeTest {
@@ -60,7 +60,7 @@ import org.junit.jupiter.api.BeforeEach;
  * <p>
  * Same applies to methods with the difference that annotation translates into 
something like {@link BeforeEach} and
  * {@link AfterEach}.
- * <br/><br/>
+ * <br><br>
  * <pre>{@code  public class SomeTest {
  *      @Test
  *      @WithSystemProperty(key = "name", value = "val")
@@ -89,10 +89,10 @@ import org.junit.jupiter.api.BeforeEach;
  * }</pre>
  * For class level annotation it applies system properties for the whole class 
hierarchy (ignoring interfaces, there's
  * no linearization implemented). More specific classes have higher priority 
and set their properties last. It all
- * starts with {@link Object} which, of course, is not annotated.<br/>
- * <br/>
- * Test methods do not inherit their annotations from overridden methods of 
super class.<br/>
- * <br/>
+ * starts with {@link Object} which, of course, is not annotated.<br>
+ * <br>
+ * Test methods do not inherit their annotations from overridden methods of 
super class.<br>
+ * <br>
  * If more than one annotation is presented on class/method then they will be 
applied in the same order as they
  * appear in code. It is achieved with the help of {@link Repeatable} feature 
of Java annotations -
  * {@link SystemPropertiesList} is automatically generated in such cases.
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtension.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtension.java
index 54791a9..0423dd0 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtension.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtension.java
@@ -19,13 +19,19 @@ package org.apache.ignite.internal.testframework;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.stream.Stream;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.lang.IgniteSystemProperties;
 import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.extension.AfterAllCallback;
 import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.api.extension.ParameterContext;
@@ -34,20 +40,33 @@ import org.junit.jupiter.api.extension.ParameterResolver;
 import org.junit.platform.commons.support.AnnotationSupport;
 import org.junit.platform.commons.support.HierarchyTraversalMode;
 
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace;
+
 /**
  * JUnit extension for injecting temporary folders into test classes.
  * <p>
  * This extension supports both field and parameter injection of {@link Path} 
parameters annotated with the
  * {@link WorkDirectory} annotation.
  * <p>
- * A new temporary folder is created for every test method and will be located 
relative to the module,
- * where the tests are being run, by the following path:
- * "target/work/{@literal 
<name-of-the-test-class>/<name-of-the-test-method>_<current_time_millis>}".
- * It is removed after a test has finished running, but this behaviour can be 
controlled by setting the
+ * A new temporary folder can be created for every test method (when used as a 
test parameter or as a member field)
+ * or a single time in a test class' lifetime (when used as a parameter in a 
{@link BeforeAll} hook or as a static
+ * field). Temporary folders are located relative to the module, where the 
tests are being run, and their paths depends
+ * on the lifecycle of the folder:
+ *
+ * <ol>
+ *     <li>For test methods: "target/work/{@literal 
<name-of-the-test-class>/<name-of-the-test-method>_<current_time_millis>}"</li>
+ *     <li>For test classes: "target/work/{@literal 
<name-of-the-test-class>/static_<current_time_millis>}"</li>
+ * </ol>
+ *
+ * Temporary folders are removed after tests have finished running, but this 
behaviour can be controlled by setting the
  * {@link WorkDirectoryExtension#KEEP_WORK_DIR_PROPERTY} property to {@code 
true}, in which case the created folder can
  * be kept intact for debugging purposes.
  */
-public class WorkDirectoryExtension implements BeforeEachCallback, 
AfterEachCallback, ParameterResolver {
+public class WorkDirectoryExtension
+    implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, 
AfterEachCallback, ParameterResolver {
+    /** JUnit namespace for the extension. */
+    private static final Namespace NAMESPACE = 
Namespace.create(WorkDirectoryExtension.class);
+
     /**
      * System property that, when set to {@code true}, will make the extension 
preserve the created directories.
      * Default is {@code false}.
@@ -57,31 +76,60 @@ public class WorkDirectoryExtension implements 
BeforeEachCallback, AfterEachCall
     /** Base path for all temporary folders in a module. */
     private static final Path BASE_PATH = Path.of("target", "work");
 
+    /** Name of the work directory that will be injected into {@link 
BeforeAll} methods or static members. */
+    private static final String STATIC_FOLDER_NAME = "static";
+
+    /**
+     * Creates and injects a temporary directory into a static field.
+     */
+    @Override public void beforeAll(ExtensionContext context) throws Exception 
{
+        Field workDirField = getWorkDirField(context);
+
+        if (workDirField == null || 
!Modifier.isStatic(workDirField.getModifiers()))
+            return;
+
+        workDirField.setAccessible(true);
+
+        workDirField.set(null, createWorkDir(context));
+    }
+
     /** {@inheritDoc} */
-    @Override public void beforeEach(ExtensionContext context) throws 
Exception {
-        Object testInstance = context.getRequiredTestInstance();
+    @Override public void afterAll(ExtensionContext context) throws Exception {
+        try (Stream<Path> list = Files.list(BASE_PATH)) {
+            if (list.findAny().isEmpty())
+                IgniteUtils.deleteIfExists(BASE_PATH);
+        }
+    }
 
-        Field workDirField = getWorkDirField(testInstance.getClass());
+    /**
+     * Creates and injects a temporary directory into a field.
+     */
+    @Override public void beforeEach(ExtensionContext context) throws 
Exception {
+        Field workDirField = getWorkDirField(context);
 
-        if (workDirField == null)
+        if (workDirField == null || 
Modifier.isStatic(workDirField.getModifiers()))
             return;
 
         workDirField.setAccessible(true);
 
-        workDirField.set(testInstance, createWorkDir(context));
+        workDirField.set(context.getRequiredTestInstance(), 
createWorkDir(context));
     }
 
     /** {@inheritDoc} */
     @Override public void afterEach(ExtensionContext context) throws Exception 
{
-        if (shouldRemoveDir())
-            IgniteUtils.deleteIfExists(BASE_PATH);
+        if (shouldRemoveDir()) {
+            Path workDir = 
context.getStore(NAMESPACE).get(context.getUniqueId(), Path.class);
+
+            if (workDir != null)
+                IgniteUtils.deleteIfExists(workDir);
+        }
     }
 
     /** {@inheritDoc} */
     @Override public boolean supportsParameter(
         ParameterContext parameterContext, ExtensionContext extensionContext
     ) throws ParameterResolutionException {
-        return getParameterType(parameterContext).equals(Path.class)
+        return parameterContext.getParameter().getType().equals(Path.class)
             && parameterContext.isAnnotated(WorkDirectory.class);
     }
 
@@ -89,6 +137,12 @@ public class WorkDirectoryExtension implements 
BeforeEachCallback, AfterEachCall
     @Override public Object resolveParameter(
         ParameterContext parameterContext, ExtensionContext extensionContext
     ) throws ParameterResolutionException {
+        if (getWorkDirField(extensionContext) != null) {
+            throw new IllegalStateException(
+                "Cannot perform parameter injection, because there exists a 
field annotated with @WorkDirectory"
+            );
+        }
+
         try {
             return createWorkDir(extensionContext);
         }
@@ -98,40 +152,39 @@ public class WorkDirectoryExtension implements 
BeforeEachCallback, AfterEachCall
     }
 
     /**
-     * Shortcut for extracting the method parameter type from a {@link 
ParameterContext}.
+     * Creates a temporary folder for the given test method.
      */
-    private static Class<?> getParameterType(ParameterContext 
parameterContext) {
-        return parameterContext.getParameter().getType();
-    }
+    private static Path createWorkDir(ExtensionContext context) throws 
IOException {
+        Path existingDir = 
context.getStore(NAMESPACE).get(context.getUniqueId(), Path.class);
 
-    /**
-     * Creates the temporary folder for the given test method.
-     */
-    private static Path createWorkDir(ExtensionContext extensionContext) 
throws IOException {
-        if (shouldRemoveDir())
-            IgniteUtils.deleteIfExists(BASE_PATH);
+        if (existingDir != null)
+            return existingDir;
 
-        String testClassDir = 
extensionContext.getRequiredTestClass().getSimpleName();
+        String testClassDir = context.getRequiredTestClass().getSimpleName();
 
-        String testMethodDir = 
extensionContext.getRequiredTestMethod().getName() + '_' + 
System.currentTimeMillis();
+        String testMethodDir = context.getTestMethod()
+            .map(Method::getName)
+            .orElse(STATIC_FOLDER_NAME);
 
-        Path workDir = BASE_PATH.resolve(testClassDir).resolve(testMethodDir);
+        Path workDir = BASE_PATH.resolve(testClassDir).resolve(testMethodDir + 
'_' + System.currentTimeMillis());
 
         Files.createDirectories(workDir);
 
+        context.getStore(NAMESPACE).put(context.getUniqueId(), workDir);
+
         return workDir;
     }
 
     /**
      * Looks for the annotated field inside the given test class.
      *
-     * @return annotated field or {@code null} if no fields have been found
-     * @throws IllegalStateException if more than one annotated fields have 
been found
+     * @return Annotated field or {@code null} if no fields have been found
+     * @throws IllegalStateException If more than one annotated fields have 
been found
      */
     @Nullable
-    private static Field getWorkDirField(Class<?> testClass) {
+    private static Field getWorkDirField(ExtensionContext context) {
         List<Field> fields = AnnotationSupport.findAnnotatedFields(
-            testClass,
+            context.getRequiredTestClass(),
             WorkDirectory.class,
             field -> field.getType().equals(Path.class),
             HierarchyTraversalMode.TOP_DOWN
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtensionTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtensionTest.java
new file mode 100644
index 0000000..911627a
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/testframework/WorkDirectoryExtensionTest.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.ignite.internal.testframework;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.EventType;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static 
org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static 
org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
+import static 
org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
+import static org.junit.platform.testkit.engine.EventConditions.type;
+import static 
org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
+import static 
org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
+
+/**
+ * Tests for the {@link WorkDirectoryExtension}.
+ * <p>
+ * This class uses an approach when several nested classes are executed 
manually on the JUnit test engine, because
+ * some test methods should fail as part of these meta-tests. Nested classes 
are skipped by the surefire plugin
+ * and must not be executed during the build.
+ *
+ * @see <a 
href="https://junit.org/junit5/docs/current/user-guide/#testkit";>JUnit Platform 
Test Kit</a>
+ */
+public class WorkDirectoryExtensionTest {
+
+    /**
+     * Test class for the {@link #testStaticFieldInjection()} test.
+     */
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class NormalStaticFieldInjectionTest {
+        /** */
+        @WorkDirectory
+        private static Path workDir;
+
+        /** */
+        private static Path testFile;
+
+        /** */
+        @BeforeAll
+        static void beforeAll() throws IOException {
+            testFile = Files.createFile(workDir.resolve("foo"));
+        }
+
+        /** */
+        @RepeatedTest(3)
+        public void test() {
+            assertTrue(Files.exists(testFile));
+        }
+    }
+
+    /**
+     * Tests temporary folder injection into a static field by running a test 
multiple times and checking
+     * that the folder persists between the runs.
+     */
+    @Test
+    void testStaticFieldInjection() {
+        assertExecutesSuccessfully(NormalStaticFieldInjectionTest.class);
+    }
+
+    /**
+     * Test class for the {@link #testFieldInjection()} test.
+     */
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class NormalFieldInjectionTest {
+        /** */
+        private static final Set<Path> paths = new HashSet<>();
+
+        /** */
+        @WorkDirectory
+        private Path workDir;
+
+        /** */
+        @RepeatedTest(3)
+        public void test() {
+            assertThat(paths, not(contains(workDir)));
+
+            for (Path path : paths)
+                assertTrue(Files.notExists(path));
+
+            paths.add(workDir);
+        }
+    }
+
+    /**
+     * Tests temporary folder injection into a field by running a test 
multiple times and checking
+     * that a new folder is created each time.
+     */
+    @Test
+    void testFieldInjection() {
+        assertExecutesSuccessfully(NormalFieldInjectionTest.class);
+    }
+
+    /**
+     * Test class for the {@link #testMultipleMethodsInjection()} test.
+     */
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class MultipleMethodsInjectionTest {
+        /** */
+        @BeforeEach
+        void setUp(@WorkDirectory Path workDir) throws IOException {
+            Files.createFile(workDir.resolve("foo"));
+        }
+
+        /** */
+        @Test
+        void test(@WorkDirectory Path workDir) {
+            assertTrue(Files.exists(workDir.resolve("foo")));
+        }
+    }
+
+    /**
+     * Tests a scenario when a folder is injected into both {@code BeforeEach} 
and test method and checks that it is
+     * the same folder, and it does not get re-created.
+     */
+    @Test
+    void testMultipleMethodsInjection() {
+        assertExecutesSuccessfully(MultipleMethodsInjectionTest.class);
+    }
+
+    /**
+     * Test class for the {@link #testDuplicateFieldAndParameterInjection()} 
test.
+     */
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class ErrorParameterResolutionTest {
+        /** */
+        @WorkDirectory
+        private static Path workDir;
+
+        /** */
+        @BeforeEach
+        void setUp(@WorkDirectory Path anotherWorkDir) {
+            fail("Should not reach here");
+        }
+
+        /** */
+        @Test
+        public void test() {
+            fail("Should not reach here");
+        }
+    }
+
+    /**
+     * Tests an error condition when the {@code @WorkDirectory} annotation is 
placed on multiple elements.
+     */
+    @Test
+    void testDuplicateFieldAndParameterInjection() {
+        execute(ErrorParameterResolutionTest.class)
+            .testEvents()
+            .assertThatEvents()
+            .filteredOn(type(EventType.FINISHED))
+            .isNotEmpty()
+            .are(finishedWithFailure(
+                instanceOf(ParameterResolutionException.class),
+                message(m -> m.contains("there exists a field annotated with 
@WorkDirectory"))
+            ));
+    }
+
+    /**
+     * Test class for the {@link #testDuplicateFieldInjection()} test.
+     */
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class ErrorFieldInjectionTest {
+        /** */
+        @WorkDirectory
+        private static Path workDir1;
+
+        /** */
+        @WorkDirectory
+        private Path workDir2;
+
+        /** */
+        @Test
+        public void test() {
+            fail("Should not reach here");
+        }
+    }
+
+    /**
+     * Tests an error condition when the {@code @WorkDirectory} annotation is 
placed on multiple fields.
+     */
+    @Test
+    void testDuplicateFieldInjection() {
+        execute(ErrorFieldInjectionTest.class)
+            .allEvents()
+            .assertThatEvents()
+            .filteredOn(finishedWithFailure())
+            .isNotEmpty()
+            .are(finishedWithFailure(
+                instanceOf(IllegalStateException.class),
+                message(m -> m.contains("Test class must have a single field 
of type"))
+            ));
+    }
+
+    /**
+     * Test class for the {@link #testSystemProperty()} test.
+     */
+    @ExtendWith(SystemPropertiesExtension.class)
+    @ExtendWith(WorkDirectoryExtension.class)
+    static class SystemPropertiesTest {
+        /** */
+        private static Path file1;
+
+        /** */
+        private static Path file2;
+
+        /** */
+        @AfterAll
+        static void verify() throws IOException {
+            assertTrue(Files.exists(file1));
+            assertFalse(Files.exists(file2));
+
+            Files.delete(file1);
+        }
+
+        /** */
+        @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
+        @WithSystemProperty(key = 
WorkDirectoryExtension.KEEP_WORK_DIR_PROPERTY, value = "true")
+        @Test
+        void test1(@WorkDirectory Path workDir) throws IOException {
+            file1 = Files.createFile(workDir.resolve("foo"));
+        }
+
+        /** */
+        @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
+        @Test
+        void test2(@WorkDirectory Path workDir) throws IOException {
+            file2 = Files.createFile(workDir.resolve("foo"));
+        }
+    }
+
+    /**
+     * Tests that a work directory can be preserved when a special system 
property is set.
+     */
+    @Test
+    void testSystemProperty() {
+        assertExecutesSuccessfully(SystemPropertiesTest.class);
+    }
+
+    /**
+     * Executes the given test class on the test engine.
+     */
+    private static EngineExecutionResults execute(Class<?> testClass) {
+        return EngineTestKit.engine("junit-jupiter")
+            .selectors(selectClass(testClass))
+            .execute();
+    }
+
+    /**
+     * Executes the given test class and checks that it has run all its tests 
successfully.
+     */
+    private static void assertExecutesSuccessfully(Class<?> testClass) {
+        execute(testClass)
+            .allEvents()
+            .assertThatEvents()
+            .filteredOn(type(EventType.FINISHED))
+            .isNotEmpty()
+            .are(finishedSuccessfully());
+    }
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index 1719dc9..c77ce62 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -58,7 +58,7 @@
         <javax.annotation.api.version>1.3.2</javax.annotation.api.version>
         <jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
         <jmh.framework.version>1.13</jmh.framework.version>
-        <junit.jupiter.version>5.7.0</junit.jupiter.version>
+        <junit.version>5.8.1</junit.version>
         <micronaut.version>2.1.2</micronaut.version>
         <micronaut.test.junit5.version>2.3.1</micronaut.test.junit5.version>
         <mockito.framework.version>3.8.0</mockito.framework.version>
@@ -485,21 +485,11 @@
             </dependency>
 
             <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-engine</artifactId>
-                <version>${junit.jupiter.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-api</artifactId>
-                <version>${junit.jupiter.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>org.junit.jupiter</groupId>
-                <artifactId>junit-jupiter-params</artifactId>
-                <version>${junit.jupiter.version}</version>
+                <groupId>org.junit</groupId>
+                <artifactId>junit-bom</artifactId>
+                <version>${junit.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
             </dependency>
 
             <dependency>
@@ -1012,6 +1002,8 @@
                     
<argLine>-Djava.util.logging.config.file=../../config/java.util.logging.properties</argLine>
                     <excludes>
                         <exclude>**/IT*.java</exclude>
+                        <!-- Exclude inner classes (preserve default 
behaviour) -->
+                        <exclude>**/*$*</exclude>
                     </excludes>
                 </configuration>
             </plugin>

Reply via email to