From 8f45eeab5662706facaa327902dd02639ffe02e7 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:56:52 +0200 Subject: [PATCH] =?UTF-8?q?Find=20repeatable=20@=E2=81=A0ExtendWith=20meta?= =?UTF-8?q?-annotations=20on=20fields=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JUnit Jupiter 5.11 introduced a regression regarding extension registration via fields. Specifically, repeated @⁠ExtendWith annotations on composed annotations were no longer found. This commit fixes that regression by reintroducing support for finding @⁠ExtendWith on custom composed annotations when @⁠ExtendWith is used as a repeatable annotation. Fixes #4054 (cherry picked from commit d274794f7c14db5f5c8aa2d7a971015cc7e395de) --- .../release-notes/release-notes-5.11.3.adoc | 5 +- .../engine/descriptor/ExtensionUtils.java | 14 ++- .../descriptor/ExtensionsUtilsTests.java | 88 +++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc index 49d53befa49f..98c2238e5025 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc @@ -35,7 +35,10 @@ on GitHub. [[release-notes-5.11.3-junit-jupiter-bug-fixes]] ==== Bug Fixes -* ❓ +* Extensions can once again be registered via multiple `@ExtendWith` + meta-annotations on the same composed annotation on a field within a + test class. + [[release-notes-5.11.3-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java index 9cc9c64cf849..ad503e0e81dd 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java @@ -201,9 +201,7 @@ static void registerExtensionsFromExecutableParameters(ExtensionRegistrar regist * @since 5.11 */ private static Stream streamExtensionRegisteringFields(Class clazz, Predicate predicate) { - Predicate composedPredicate = predicate.and( - field -> isAnnotated(field, ExtendWith.class) || isAnnotated(field, RegisterExtension.class)); - return streamFields(clazz, composedPredicate, TOP_DOWN)// + return streamFields(clazz, predicate.and(registersExtension), TOP_DOWN)// .sorted(orderComparator); } @@ -236,4 +234,14 @@ private static int getOrder(Field field) { return findAnnotation(field, Order.class).map(Order::value).orElse(Order.DEFAULT); } + /** + * {@link Predicate} which determines if a {@link Field} registers an extension via + * {@link RegisterExtension @RegisterExtension} or {@link ExtendWith @ExtendWith}. + * + * @since 5.11.3 + */ + private static final Predicate registersExtension = // + field -> isAnnotated(field, RegisterExtension.class) + || !findRepeatableAnnotations(field, ExtendWith.class).isEmpty(); + } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java new file mode 100644 index 000000000000..b7afbb0c44b4 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2024 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.descriptor; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; + +/** + * Tests for {@link ExtensionUtils}. + * + * @since 5.11.3 + */ +class ExtensionsUtilsTests { + + @Test + void registerExtensionsViaStaticFields() throws Exception { + Field field = TestCase.class.getDeclaredField("staticField"); + ExtensionRegistrar registrar = mock(); + ExtensionUtils.registerExtensionsFromStaticFields(registrar, TestCase.class); + verify(registrar).registerExtension(Extension1.class); + verify(registrar).registerExtension(Extension2.class); + verify(registrar).registerExtension(TestCase.staticField, field); + } + + @Test + @SuppressWarnings("unchecked") + void registerExtensionsViaInstanceFields() throws Exception { + Class testClass = TestCase.class; + Field field = testClass.getDeclaredField("instanceField"); + ExtensionRegistrar registrar = mock(); + ExtensionUtils.registerExtensionsFromInstanceFields(registrar, testClass); + verify(registrar).registerExtension(Extension1.class); + verify(registrar).registerExtension(Extension2.class); + verify(registrar).registerUninitializedExtension(eq(testClass), eq(field), any(Function.class)); + } + + static class Extension1 implements Extension { + } + + static class Extension2 implements Extension { + } + + static class Extension3 implements Extension { + } + + static class Extension4 implements Extension { + } + + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(Extension1.class) + @ExtendWith(Extension2.class) + @interface UseCustomExtensions { + } + + static class TestCase { + + @UseCustomExtensions + @RegisterExtension + static Extension3 staticField = new Extension3(); + + @UseCustomExtensions + @RegisterExtension + Extension4 instanceField = new Extension4(); + + } + +}