diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index d4059380111f..a5ab2e1abc4d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -43,8 +43,10 @@ *

Inheritance and Execution Order

* *

{@code @AfterAll} methods are inherited from superclasses as long as - * they are not hidden or overridden. Furthermore, - * {@code @AfterAll} methods from superclasses will be executed after + * they are not hidden (default mode with {@code static} modifier), or + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterAll} methods from superclasses will be executed before * {@code @AfterAll} methods in subclasses. * *

Similarly, {@code @AfterAll} methods declared in an interface are diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java index 3595b3fab594..c6340076e0f2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java @@ -36,8 +36,10 @@ *

Inheritance and Execution Order

* *

{@code @AfterEach} methods are inherited from superclasses as long as - * they are not overridden. Furthermore, {@code @AfterEach} methods from - * superclasses will be executed after {@code @AfterEach} methods in subclasses. + * they are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @AfterEach} methods from superclasses will be executed after + * {@code @AfterEach} methods in subclasses. * *

Similarly, {@code @AfterEach} methods declared as interface default * methods are inherited as long as they are not overridden, and diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java index 5023334c614e..5c85c196d262 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java @@ -43,7 +43,9 @@ *

Inheritance and Execution Order

* *

{@code @BeforeAll} methods are inherited from superclasses as long as - * they are not hidden or overridden. Furthermore, + * they are not hidden (default mode with {@code static} modifier), or + * overridden, or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, * {@code @BeforeAll} methods from superclasses will be executed before * {@code @BeforeAll} methods in subclasses. * diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java index eb8fa9aaf20e..b6247315b40f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java @@ -36,8 +36,10 @@ *

Inheritance and Execution Order

* *

{@code @BeforeEach} methods are inherited from superclasses as long as - * they are not overridden. Furthermore, {@code @BeforeEach} methods from - * superclasses will be executed before {@code @BeforeEach} methods in subclasses. + * they are not overridden or superseded (i.e., replaced based on + * signature only, irrespective of Java's visibility rules). Furthermore, + * {@code @BeforeEach} methods from superclasses will be executed before + * {@code @BeforeEach} methods in subclasses. * *

Similarly, {@code @BeforeEach} methods declared as interface default * methods are inherited as long as they are not overridden, and diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java index 53a2a5c5360a..9b9a43a73a20 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java @@ -37,19 +37,21 @@ private LifecycleMethodUtils() { } static List findBeforeAllMethods(Class testClass, boolean requireStatic) { - return findMethodsAndAssertStatic(testClass, requireStatic, BeforeAll.class, HierarchyTraversalMode.TOP_DOWN); + return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, BeforeAll.class, + HierarchyTraversalMode.TOP_DOWN); } static List findAfterAllMethods(Class testClass, boolean requireStatic) { - return findMethodsAndAssertStatic(testClass, requireStatic, AfterAll.class, HierarchyTraversalMode.BOTTOM_UP); + return findMethodsAndAssertStaticAndNonPrivate(testClass, requireStatic, AfterAll.class, + HierarchyTraversalMode.BOTTOM_UP); } static List findBeforeEachMethods(Class testClass) { - return findMethodsAndAssertNonStatic(testClass, BeforeEach.class, HierarchyTraversalMode.TOP_DOWN); + return findMethodsAndAssertNonStaticAndNonPrivate(testClass, BeforeEach.class, HierarchyTraversalMode.TOP_DOWN); } static List findAfterEachMethods(Class testClass) { - return findMethodsAndAssertNonStatic(testClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP); + return findMethodsAndAssertNonStaticAndNonPrivate(testClass, AfterEach.class, HierarchyTraversalMode.BOTTOM_UP); } private static void assertStatic(Class annotationType, Method method) { @@ -67,6 +69,13 @@ private static void assertNonStatic(Class annotationType, } } + private static void assertNonPrivate(Class annotationType, Method method) { + if (ReflectionUtils.isPrivate(method)) { + throw new JUnitException(String.format("@%s method '%s' must not be private.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + private static void assertVoid(Class annotationType, Method method) { if (!returnsVoid(method)) { throw new JUnitException(String.format("@%s method '%s' must not return a value.", @@ -74,19 +83,25 @@ private static void assertVoid(Class annotationType, Metho } } - private static List findMethodsAndAssertStatic(Class testClass, boolean requireStatic, + private static List findMethodsAndAssertStaticAndNonPrivate(Class testClass, boolean requireStatic, Class annotationType, HierarchyTraversalMode traversalMode) { List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); if (requireStatic) { methods.forEach(method -> assertStatic(annotationType, method)); } + methods.forEach(method -> assertNonPrivate(annotationType, method)); + return methods; } - private static List findMethodsAndAssertNonStatic(Class testClass, + private static List findMethodsAndAssertNonStaticAndNonPrivate(Class testClass, Class annotationType, HierarchyTraversalMode traversalMode) { List methods = findMethodsAndCheckVoidReturnType(testClass, annotationType, traversalMode); - methods.forEach(method -> assertNonStatic(annotationType, method)); + methods.forEach(method -> { + assertNonStatic(annotationType, method); + assertNonPrivate(annotationType, method); + }); + return methods; } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java index 1b5fd04b4e15..a4ef6b34f3b7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java @@ -23,7 +23,7 @@ import org.junit.platform.testkit.engine.Events; /** - * Integration tests that very proper handling of invalid configuration for + * Integration tests that verify proper handling of invalid configuration for * lifecycle methods in conjunction with the {@link JupiterTestEngine}. * *

In general, configuration errors should not be thrown until the @@ -34,23 +34,43 @@ class InvalidLifecycleMethodConfigurationTests extends AbstractJupiterTestEngineTests { @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidBeforeAllDeclaration() { - assertExecutionResults(TestCaseWithInvalidBeforeAllMethod.class); + void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticBeforeAllDeclaration() { + assertExecutionResults(TestCaseWithInvalidNonStaticBeforeAllMethod.class); } @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidAfterAllDeclaration() { - assertExecutionResults(TestCaseWithInvalidAfterAllMethod.class); + void executeValidTestCaseAlongsideTestCaseWithInvalidPrivateBeforeAllDeclaration() { + assertExecutionResults(TestCaseWithInvalidPrivateBeforeAllMethod.class); } @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidBeforeEachDeclaration() { - assertExecutionResults(TestCaseWithInvalidBeforeEachMethod.class); + void executeValidTestCaseAlongsideTestCaseWithInvalidNonStaticAfterAllDeclaration() { + assertExecutionResults(TestCaseWithInvalidNonStaticAfterAllMethod.class); } @Test - void executeValidTestCaseAlongsideTestCaseWithInvalidAfterEachDeclaration() { - assertExecutionResults(TestCaseWithInvalidAfterEachMethod.class); + void executeValidTestCaseAlongsideTestCaseWithInvalidPrivateAfterAllDeclaration() { + assertExecutionResults(TestCaseWithInvalidPrivateAfterAllMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidStaticBeforeEachDeclaration() { + assertExecutionResults(TestCaseWithInvalidStaticBeforeEachMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidPrivateBeforeEachDeclaration() { + assertExecutionResults(TestCaseWithInvalidPrivateBeforeEachMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidStaticAfterEachDeclaration() { + assertExecutionResults(TestCaseWithInvalidStaticAfterEachMethod.class); + } + + @Test + void executeValidTestCaseAlongsideTestCaseWithInvalidPrivateAfterEachDeclaration() { + assertExecutionResults(TestCaseWithInvalidPrivateAfterEachMethod.class); } private void assertExecutionResults(Class invalidTestClass) { @@ -80,7 +100,7 @@ void test() { } } - static class TestCaseWithInvalidBeforeAllMethod { + static class TestCaseWithInvalidNonStaticBeforeAllMethod { // must be static @BeforeAll @@ -92,7 +112,19 @@ void test() { } } - static class TestCaseWithInvalidAfterAllMethod { + static class TestCaseWithInvalidPrivateBeforeAllMethod { + + // must not be private + @BeforeAll + private static void beforeAll() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidNonStaticAfterAllMethod { // must be static @AfterAll @@ -104,7 +136,19 @@ void test() { } } - static class TestCaseWithInvalidBeforeEachMethod { + static class TestCaseWithInvalidPrivateAfterAllMethod { + + // must not be private + @AfterAll + private static void afterAll() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidStaticBeforeEachMethod { // must NOT be static @BeforeEach @@ -116,7 +160,19 @@ void test() { } } - static class TestCaseWithInvalidAfterEachMethod { + static class TestCaseWithInvalidPrivateBeforeEachMethod { + + // must NOT be private + @BeforeEach + private void beforeEach() { + } + + @Test + void test() { + } + } + + static class TestCaseWithInvalidStaticAfterEachMethod { // must NOT be static @AfterEach @@ -128,4 +184,16 @@ void test() { } } + static class TestCaseWithInvalidPrivateAfterEachMethod { + + // must NOT be private + @AfterEach + private void afterEach() { + } + + @Test + void test() { + } + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodSupersedingTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodSupersedingTests.java new file mode 100644 index 000000000000..cfc92c17974d --- /dev/null +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodSupersedingTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +/* + * Copyright 2015-2022 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Integration tests that explicitly demonstrate the shadowing/overriding + * rules for lifecycle methods in the {@link JupiterTestEngine}. + * + * @since 5.9 + */ +class LifecycleMethodSupersedingTests { + + @Nested + @DisplayName("A package-private lifecycle super-method can be superseded by") + class PackagePrivateSuperClassTests { + + @Nested + @DisplayName("a protected lifecycle method in the derived class") + class ProtectedExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @BeforeEach + protected void beforeEach() { + } + + } + + @Nested + @DisplayName("a package-private lifecycle method in the derived class") + class PackagePrivateExtendsPackagePrivateLifecycleMethod + extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @BeforeEach + void beforeEach() { + } + + } + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivateLifecycleMethod extends SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @BeforeEach + public void beforeEach() { + } + + } + } + + @Nested + @DisplayName("A protected lifecycle super-method can be superseded by") + class ProtectedSuperClassTests { + + @Nested + @DisplayName("a protected lifecycle method in the derived class") + class ProtectedExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { + + @BeforeEach + protected void beforeEach() { + } + + } + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivate extends SuperClassWithProtectedLifecycleMethodTestCase { + + @BeforeEach + public void beforeEach() { + } + + } + } + + @Nested + @DisplayName("A public lifecycle super-method can be superseded by") + class PublicSuperClassTests { + + @Nested + @DisplayName("a public lifecycle method in the derived class") + class PublicExtendsPackagePrivate extends SuperClassWithPublicLifecycleMethodTestCase { + + @BeforeEach + public void beforeEach() { + } + + } + } + +} + +// ------------------------------------------------------------------------- + +class SuperClassWithPackagePrivateLifecycleMethodTestCase { + + @BeforeEach + void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} + +class SuperClassWithProtectedLifecycleMethodTestCase { + + @BeforeEach + protected void beforeEach() { + fail(); + } + + @Test + void test() { + } + +} + +class SuperClassWithPublicLifecycleMethodTestCase { + + @BeforeEach + public void beforeEach() { + fail(); + } + + @Test + void test() { + } + +}