You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2021/12/07 17:33:48 UTC

[ignite-3] branch main updated: IGNITE-16058 Create annotation to load test parameters from a static variable (#491)

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

sdanilov 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 ecfe921  IGNITE-16058 Create annotation to load test parameters from a static variable (#491)
ecfe921 is described below

commit ecfe9210989c21f037ce29c2cb93642bea359482
Author: Alexander Polovtcev <al...@gmail.com>
AuthorDate: Tue Dec 7 20:31:54 2021 +0300

    IGNITE-16058 Create annotation to load test parameters from a static variable (#491)
---
 modules/core/pom.xml                               |   6 ++
 .../testframework/JunitExtensionTestUtils.java     |  69 ++++++++++++++
 .../testframework/VariableArgumentsProvider.java   |  86 +++++++++++++++++
 .../internal/testframework/VariableSource.java     |  43 +++++++++
 .../internal/testframework/VariableSourceTest.java | 104 +++++++++++++++++++++
 .../testframework/WorkDirectoryExtensionTest.java  |  68 ++++----------
 6 files changed, 324 insertions(+), 52 deletions(-)

diff --git a/modules/core/pom.xml b/modules/core/pom.xml
index cf3ccbc..b0182ec 100644
--- a/modules/core/pom.xml
+++ b/modules/core/pom.xml
@@ -64,6 +64,12 @@
         </dependency>
 
         <dependency>
+          <groupId>org.junit.jupiter</groupId>
+          <artifactId>junit-jupiter-params</artifactId>
+          <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.junit.platform</groupId>
             <artifactId>junit-platform-testkit</artifactId>
             <scope>test</scope>
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/testframework/JunitExtensionTestUtils.java b/modules/core/src/test/java/org/apache/ignite/internal/testframework/JunitExtensionTestUtils.java
new file mode 100644
index 0000000..d2f67ad
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/testframework/JunitExtensionTestUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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 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 org.assertj.core.api.Condition;
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.EventType;
+
+/**
+ * Utilities for writing tests for JUnit extensions.
+ *
+ * @see <a href="https://junit.org/junit5/docs/current/user-guide/#testkit">JUnit Platform Test Kit</a>
+ */
+class JunitExtensionTestUtils {
+    /**
+     * Executes the given test class on the Jupiter 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.
+     */
+    static void assertExecutesSuccessfully(Class<?> testClass) {
+        execute(testClass)
+                .allEvents()
+                .assertThatEvents()
+                .filteredOn(type(EventType.FINISHED))
+                .isNotEmpty()
+                .are(finishedSuccessfully());
+    }
+
+    /**
+     * Executes the given test class and checks that it fails with matching error conditions.
+     */
+    @SafeVarargs
+    static void assertExecutesWithFailure(Class<?> testClass, Condition<Throwable>... conditions) {
+        execute(testClass)
+                .allEvents()
+                .assertThatEvents()
+                .filteredOn(finishedWithFailure())
+                .isNotEmpty()
+                .are(finishedWithFailure(conditions));
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableArgumentsProvider.java b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableArgumentsProvider.java
new file mode 100644
index 0000000..a5b5260
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableArgumentsProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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 static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.junit.platform.commons.util.CollectionUtils.toStream;
+import static org.junit.platform.commons.util.ReflectionUtils.isStatic;
+
+import java.lang.reflect.Field;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.support.AnnotationConsumer;
+import org.junit.platform.commons.util.ReflectionUtils;
+
+/**
+ * {@link ArgumentsProvider} implementation that extracts arguments from a static variable, which name is specified by the
+ * {@link VariableSource} annotation.
+ */
+class VariableArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<VariableSource> {
+    private String variableName;
+
+    @Override
+    public void accept(VariableSource variableSource) {
+        variableName = variableSource.value();
+    }
+
+    @Override
+    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
+        Class<?> testClass = context.getRequiredTestClass();
+
+        Field source = testClass.getDeclaredField(variableName);
+
+        if (!isStatic(source)) {
+            throw new IllegalArgumentException("Variable marked with @VariableSource must be static: " + variableName);
+        }
+
+        source.setAccessible(true);
+
+        Object sourceValue = source.get(null);
+
+        return toStream(sourceValue).map(VariableArgumentsProvider::toArguments);
+    }
+
+    /**
+     * Converts the given value to {@link Arguments}.
+     * Copy-pasted from {@code org.junit.jupiter.params.provider.MethodArgumentsProvider#toArguments}
+     */
+    private static Arguments toArguments(Object item) {
+        // Nothing to do except cast.
+        if (item instanceof Arguments) {
+            return (Arguments) item;
+        }
+
+        // Pass all multidimensional arrays "as is", in contrast to Object[].
+        // See https://github.com/junit-team/junit5/issues/1665
+        if (ReflectionUtils.isMultidimensionalArray(item)) {
+            return arguments(item);
+        }
+
+        // Special treatment for one-dimensional reference arrays.
+        // See https://github.com/junit-team/junit5/issues/1665
+        if (item instanceof Object[]) {
+            return arguments((Object[]) item);
+        }
+
+        // Pass everything else "as is".
+        return arguments(item);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSource.java b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSource.java
new file mode 100644
index 0000000..9547c34
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSource.java
@@ -0,0 +1,43 @@
+/*
+ * 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.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * When placed on a test method, this annotation will be used to resolve its parameters using a static variable declared in the enclosing
+ * test class.
+ *
+ * <p>This annotation is conceptually similar to the {@link MethodSource} annotation and uses the same parameter resolution procedures.
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@ArgumentsSource(VariableArgumentsProvider.class)
+public @interface VariableSource {
+    /**
+     * The name of the static variable.
+     */
+    String value();
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSourceTest.java b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSourceTest.java
new file mode 100644
index 0000000..247c5e1
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/testframework/VariableSourceTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 static org.apache.ignite.internal.testframework.JunitExtensionTestUtils.assertExecutesSuccessfully;
+import static org.apache.ignite.internal.testframework.JunitExtensionTestUtils.assertExecutesWithFailure;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
+import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+
+class VariableSourceTest {
+    /**
+     * Test class for the {@link #testStaticFieldInjection()} test.
+     */
+    static class NormalVariableSourceTest {
+        private static final List<Object[]> arguments = List.of(
+                new Object[] {0, "foo"},
+                new Object[] {1, "bar"},
+                new Object[] {2, "baz"}
+        );
+
+        @ParameterizedTest
+        @VariableSource("arguments")
+        public void test(int i, String argument) {
+            assertEquals(arguments.get(i)[1], argument);
+        }
+    }
+
+    /**
+     * Tests the happy case of the {@link VariableSource} usage.
+     */
+    @Test
+    void testStaticFieldInjection() {
+        assertExecutesSuccessfully(NormalVariableSourceTest.class);
+    }
+
+    /**
+     * Test class for the {@link #testNonStaticFieldInjection()} test.
+     */
+    static class NonStaticVariableSourceTest {
+        private final String arguments = "foo";
+
+        @ParameterizedTest
+        @VariableSource("arguments")
+        public void test(String argument) {
+            fail("Should not reach here");
+        }
+    }
+
+    /**
+     * Tests the case when the {@code VariableSource} variable is not static.
+     */
+    @Test
+    void testNonStaticFieldInjection() {
+        assertExecutesWithFailure(
+                NonStaticVariableSourceTest.class,
+                instanceOf(IllegalArgumentException.class),
+                message(m -> m.contains("must be static"))
+        );
+    }
+
+    /**
+     * Test class for the {@link #testMissingVariableSource()} test.
+     */
+    static class MissingVariableSourceTest {
+        @ParameterizedTest
+        @VariableSource("missing")
+        public void test(String argument) {
+            fail("Should not reach here");
+        }
+    }
+
+    /**
+     * Tests the case when the {@code VariableSource} variable is not declared.
+     */
+    @Test
+    void testMissingVariableSource() {
+        assertExecutesWithFailure(
+                MissingVariableSourceTest.class,
+                instanceOf(NoSuchFieldException.class),
+                message(m -> m.contains("missing"))
+        );
+    }
+}
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
index 003f5ff..52dea1c 100644
--- 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
@@ -17,16 +17,14 @@
 
 package org.apache.ignite.internal.testframework;
 
+import static org.apache.ignite.internal.testframework.JunitExtensionTestUtils.assertExecutesSuccessfully;
+import static org.apache.ignite.internal.testframework.JunitExtensionTestUtils.assertExecutesWithFailure;
 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;
 
@@ -43,19 +41,14 @@ 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;
 
 /**
  * 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 {
+class WorkDirectoryExtensionTest {
     /**
      * Test class for the {@link #testStaticFieldInjection()} test.
      */
@@ -165,15 +158,11 @@ public class WorkDirectoryExtensionTest {
      */
     @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"))
-                ));
+        assertExecutesWithFailure(
+                ErrorParameterResolutionTest.class,
+                instanceOf(ParameterResolutionException.class),
+                message(m -> m.contains("there exists a field annotated with @WorkDirectory"))
+        );
     }
 
     /**
@@ -198,15 +187,11 @@ public class WorkDirectoryExtensionTest {
      */
     @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"))
-                ));
+        assertExecutesWithFailure(
+                ErrorFieldInjectionTest.class,
+                instanceOf(IllegalStateException.class),
+                message(m -> m.contains("Test class must have a single field of type"))
+        );
     }
 
     /**
@@ -259,12 +244,12 @@ public class WorkDirectoryExtensionTest {
 
         @Disabled
         @Test
-        void test() {}
+        void test() {
+        }
     }
 
     /**
-     * Tests {@code WorkDirectoryExtension} lifecycle works correctly on a test class with all test methods being
-     * disabled.
+     * Tests {@code WorkDirectoryExtension} lifecycle works correctly on a test class with all test methods being disabled.
      *
      * @see <a href="https://issues.apache.org/jira/browse/IGNITE-15799">IGNITE-15799</a>
      */
@@ -272,25 +257,4 @@ public class WorkDirectoryExtensionTest {
     void testEmptyClass() {
         assertExecutesSuccessfully(TestEmptyClass.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());
-    }
 }