Skip to content

Commit e4804cb

Browse files
committed
Disallow duplicate registration via @RegisterExtension and @ExtendWith
Prior to this commit, @ExtendWith and @RegisterExtension could be declared on a field to register duplicate extensions of the same time, but that scenario likely does not make sense. This commit ensures that @RegisterExtension and @ExtendWith cannot be used to register an extension of the same type for a given field, by throwing a PreconditionViolationException if the user attempts to do so. See #2680
1 parent f149ad6 commit e4804cb

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import static java.util.stream.Collectors.toList;
1314
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
1415
import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations;
1516
import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;
@@ -95,17 +96,29 @@ static void registerExtensionsFromFields(ExtensionRegistrar registrar, Class<?>
9596
findFields(clazz, predicate, TOP_DOWN).stream()//
9697
.sorted(orderComparator)//
9798
.forEach(field -> {
98-
List<ExtendWith> extendWithAnnotations = findRepeatableAnnotations(field, ExtendWith.class);
99-
boolean isExtendWithPresent = !extendWithAnnotations.isEmpty();
99+
List<Class<? extends Extension>> extensionTypes = streamExtensionTypes(field).collect(toList());
100+
boolean isExtendWithPresent = !extensionTypes.isEmpty();
100101
boolean isRegisterExtensionPresent = isAnnotated(field, RegisterExtension.class);
101102
if (isExtendWithPresent) {
102-
streamExtensionTypes(extendWithAnnotations).forEach(registrar::registerExtension);
103+
extensionTypes.forEach(registrar::registerExtension);
103104
}
104105
if (isRegisterExtensionPresent) {
105106
tryToReadFieldValue(field, instance).ifSuccess(value -> {
106107
Preconditions.condition(value instanceof Extension, () -> String.format(
107108
"Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.",
108109
field, (value != null ? value.getClass().getName() : null), Extension.class.getName()));
110+
111+
if (isExtendWithPresent) {
112+
Class<?> valueType = value.getClass();
113+
extensionTypes.forEach(extensionType -> {
114+
Preconditions.condition(!extensionType.equals(valueType),
115+
() -> String.format("Failed to register extension via field [%s]. "
116+
+ "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, "
117+
+ "but only one registration of a given extension type is permitted.",
118+
field, valueType.getName()));
119+
});
120+
}
121+
109122
registrar.registerExtension((Extension) value, field);
110123
});
111124
}

junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
1717
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields;
1818
import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible;
19+
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
20+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
21+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
1922

2023
import java.lang.annotation.Annotation;
2124
import java.lang.annotation.ElementType;
@@ -61,13 +64,14 @@
6164
import org.junit.jupiter.api.fixtures.TrackLogRecords;
6265
import org.junit.jupiter.engine.AbstractJupiterTestEngineTests;
6366
import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver;
67+
import org.junit.platform.commons.PreconditionViolationException;
6468
import org.junit.platform.commons.logging.LogRecordListener;
6569
import org.junit.platform.commons.util.ExceptionUtils;
6670
import org.junit.platform.commons.util.ReflectionUtils;
6771

6872
/**
6973
* Integration tests that verify support for extension registration via
70-
* {@link ExtendWith @ExtendWith} on annotations on parameters and fields.
74+
* {@link ExtendWith @ExtendWith} on parameters and fields.
7175
*
7276
* @since 5.8
7377
*/
@@ -133,6 +137,26 @@ void fieldsWithTestInstancePerClass() {
133137
assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class);
134138
}
135139

140+
@Test
141+
@TrackLogRecords
142+
void multipleRegistrationsViaField(LogRecordListener listener) {
143+
assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class);
144+
assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension");
145+
}
146+
147+
@Test
148+
void duplicateRegistrationViaField() {
149+
Class<?> testClass = DuplicateRegistrationViaFieldTestCase.class;
150+
String expectedMessage = "Failed to register extension via field "
151+
+ "[org.junit.jupiter.api.extension.Extension "
152+
+ "org.junit.jupiter.engine.extension.ExtensionRegistrationViaParametersAndFieldsTests$DuplicateRegistrationViaFieldTestCase.dummy]. "
153+
+ "The field registers an extension of type [org.junit.jupiter.engine.extension.DummyExtension] "
154+
+ "via @RegisterExtension and @ExtendWith, but only one registration of a given extension type is permitted.";
155+
156+
executeTestsForClass(testClass).testEvents().assertThatEvents().haveExactly(1,
157+
finishedWithFailure(instanceOf(PreconditionViolationException.class), message(expectedMessage)));
158+
}
159+
136160
@Test
137161
@TrackLogRecords
138162
void registrationOrder(LogRecordListener listener) {
@@ -531,6 +555,28 @@ private static TestTemplateInvocationContext emptyTestTemplateInvocationContext(
531555
}
532556
}
533557

558+
static class MultipleRegistrationsViaFieldTestCase {
559+
560+
@ExtendWith(LongParameterResolver.class)
561+
@RegisterExtension
562+
Extension dummy = new DummyExtension();
563+
564+
@Test
565+
void test() {
566+
}
567+
}
568+
569+
static class DuplicateRegistrationViaFieldTestCase {
570+
571+
@ExtendWith(DummyExtension.class)
572+
@RegisterExtension
573+
Extension dummy = new DummyExtension();
574+
575+
@Test
576+
void test() {
577+
}
578+
}
579+
534580
/**
535581
* The {@link MagicField.Extension} is registered via a static field.
536582
*/
@@ -791,6 +837,9 @@ class Extension extends BaseParameterExtension<TestParameter> {
791837
}
792838
}
793839

840+
class DummyExtension implements Extension {
841+
}
842+
794843
class BaseFieldExtension<T extends Annotation> implements BeforeAllCallback, BeforeEachCallback {
795844

796845
private final Class<T> annotationType;

0 commit comments

Comments
 (0)