Skip to content

Commit c048779

Browse files
authored
Introduce ParameterInfo support for extensions (#4370)
`Extensions` can get it from `ExtensionContext.Store` and access all indexed parameter declarations as well as the arguments for the current invocation. Resolves #1139.
1 parent a1ee2d5 commit c048779

18 files changed

+333
-112
lines changed

junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void formatTestNames(Blackhole blackhole) throws Exception {
5050
var method = TestCase.class.getDeclaredMethod("parameterizedTest", int.class);
5151
var formatter = new ParameterizedInvocationNameFormatter(
5252
DISPLAY_NAME_PLACEHOLDER + " " + DEFAULT_DISPLAY_NAME + " ({0})", "displayName",
53-
new ParameterizedTestContext(method, method.getAnnotation(ParameterizedTest.class)), 512);
53+
new ParameterizedTestContext(TestCase.class, method, method.getAnnotation(ParameterizedTest.class)), 512);
5454
for (int i = 0; i < argumentsList.size(); i++) {
5555
Arguments arguments = argumentsList.get(i);
5656
blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments)));
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.params;
12+
13+
import org.junit.jupiter.api.extension.ExtensionContext;
14+
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
15+
import org.junit.jupiter.params.support.ParameterDeclarations;
16+
import org.junit.jupiter.params.support.ParameterInfo;
17+
18+
/**
19+
* @since 5.13
20+
*/
21+
class DefaultParameterInfo implements ParameterInfo {
22+
23+
private final ParameterDeclarations declarations;
24+
private final ArgumentsAccessor arguments;
25+
26+
DefaultParameterInfo(ParameterDeclarations declarations, ArgumentsAccessor arguments) {
27+
this.declarations = declarations;
28+
this.arguments = arguments;
29+
}
30+
31+
@Override
32+
public ParameterDeclarations getDeclarations() {
33+
return this.declarations;
34+
}
35+
36+
@Override
37+
public ArgumentsAccessor getArguments() {
38+
return this.arguments;
39+
}
40+
41+
void store(ExtensionContext context) {
42+
context.getStore(NAMESPACE).put(KEY, this);
43+
}
44+
}

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,36 @@
4040

4141
class ParameterizedClassContext implements ParameterizedDeclarationContext<ClassTemplateInvocationContext> {
4242

43-
private final Class<?> clazz;
43+
private final Class<?> testClass;
4444
private final ParameterizedClass annotation;
4545
private final TestInstance.Lifecycle testInstanceLifecycle;
4646
private final ResolverFacade resolverFacade;
4747
private final InjectionType injectionType;
4848
private final List<ArgumentSetLifecycleMethod> beforeMethods;
4949
private final List<ArgumentSetLifecycleMethod> afterMethods;
5050

51-
ParameterizedClassContext(Class<?> clazz, ParameterizedClass annotation,
51+
ParameterizedClassContext(Class<?> testClass, ParameterizedClass annotation,
5252
TestInstance.Lifecycle testInstanceLifecycle) {
53-
this.clazz = clazz;
53+
this.testClass = testClass;
5454
this.annotation = annotation;
5555
this.testInstanceLifecycle = testInstanceLifecycle;
5656

57-
List<Field> fields = findParameterAnnotatedFields(clazz);
57+
List<Field> fields = findParameterAnnotatedFields(testClass);
5858
if (fields.isEmpty()) {
59-
this.resolverFacade = ResolverFacade.create(ReflectionUtils.getDeclaredConstructor(clazz), annotation);
59+
this.resolverFacade = ResolverFacade.create(ReflectionUtils.getDeclaredConstructor(testClass), annotation);
6060
this.injectionType = InjectionType.CONSTRUCTOR;
6161
}
6262
else {
63-
this.resolverFacade = ResolverFacade.create(clazz, fields);
63+
this.resolverFacade = ResolverFacade.create(testClass, fields);
6464
this.injectionType = InjectionType.FIELDS;
6565
}
6666

67-
this.beforeMethods = findLifecycleMethodsAndAssertStaticAndNonPrivate(clazz, testInstanceLifecycle, TOP_DOWN,
68-
BeforeParameterizedClassInvocation.class, BeforeParameterizedClassInvocation::injectArguments,
67+
this.beforeMethods = findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass, testInstanceLifecycle,
68+
TOP_DOWN, BeforeParameterizedClassInvocation.class, BeforeParameterizedClassInvocation::injectArguments,
6969
this.resolverFacade);
7070

7171
// Make a local copy since findAnnotatedMethods() returns an immutable list.
72-
this.afterMethods = new ArrayList<>(findLifecycleMethodsAndAssertStaticAndNonPrivate(clazz,
72+
this.afterMethods = new ArrayList<>(findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass,
7373
testInstanceLifecycle, BOTTOM_UP, AfterParameterizedClassInvocation.class,
7474
AfterParameterizedClassInvocation::injectArguments, this.resolverFacade));
7575

@@ -87,14 +87,19 @@ private static List<Field> findParameterAnnotatedFields(Class<?> clazz) {
8787
return findFields(clazz, it -> isAnnotated(it, Parameter.class), BOTTOM_UP);
8888
}
8989

90+
@Override
91+
public Class<?> getTestClass() {
92+
return this.testClass;
93+
}
94+
9095
@Override
9196
public ParameterizedClass getAnnotation() {
9297
return this.annotation;
9398
}
9499

95100
@Override
96101
public Class<?> getAnnotatedElement() {
97-
return this.clazz;
102+
return this.testClass;
98103
}
99104

100105
@Override

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*/
2121
interface ParameterizedDeclarationContext<C> {
2222

23+
Class<?> getTestClass();
24+
2325
Annotation getAnnotation();
2426

2527
AnnotatedElement getAnnotatedElement();

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@
1010

1111
package org.junit.jupiter.params;
1212

13+
import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader;
14+
1315
import java.util.Arrays;
1416
import java.util.concurrent.atomic.AtomicInteger;
1517

1618
import org.junit.jupiter.api.extension.ExtensionContext;
1719
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
20+
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
21+
import org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor;
1822
import org.junit.jupiter.params.provider.Arguments;
23+
import org.junit.jupiter.params.support.ParameterDeclarations;
1924

2025
class ParameterizedInvocationContext<T extends ParameterizedDeclarationContext<?>> {
2126

@@ -44,7 +49,8 @@ public void prepareInvocation(ExtensionContext context) {
4449
if (this.declarationContext.isAutoClosingArguments()) {
4550
registerAutoCloseableArgumentsInStoreForClosing(context);
4651
}
47-
new ArgumentCountValidator(this.declarationContext, this.arguments).validate(context);
52+
validateArgumentCount(context);
53+
storeParameterInfo(context);
4854
}
4955

5056
private void registerAutoCloseableArgumentsInStoreForClosing(ExtensionContext context) {
@@ -58,6 +64,18 @@ private void registerAutoCloseableArgumentsInStoreForClosing(ExtensionContext co
5864
.forEach(closeable -> store.put(argumentIndex.incrementAndGet(), closeable));
5965
}
6066

67+
private void validateArgumentCount(ExtensionContext context) {
68+
new ArgumentCountValidator(this.declarationContext, this.arguments).validate(context);
69+
}
70+
71+
private void storeParameterInfo(ExtensionContext context) {
72+
ParameterDeclarations declarations = this.declarationContext.getResolverFacade().getIndexedParameterDeclarations();
73+
ClassLoader classLoader = getClassLoader(this.declarationContext.getTestClass());
74+
Object[] arguments = this.arguments.getConsumedPayloads();
75+
ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments);
76+
new DefaultParameterInfo(declarations, accessor).store(context);
77+
}
78+
6179
private static class CloseableArgument implements ExtensionContext.Store.CloseableResource {
6280

6381
private final AutoCloseable autoCloseable;

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,23 @@
2424
*/
2525
class ParameterizedTestContext implements ParameterizedDeclarationContext<TestTemplateInvocationContext> {
2626

27+
private final Class<?> testClass;
2728
private final Method method;
2829
private final ParameterizedTest annotation;
2930
private final ResolverFacade resolverFacade;
3031

31-
ParameterizedTestContext(Method method, ParameterizedTest annotation) {
32+
ParameterizedTestContext(Class<?> testClass, Method method, ParameterizedTest annotation) {
33+
this.testClass = testClass;
3234
this.method = Preconditions.notNull(method, "method must not be null");
3335
this.annotation = Preconditions.notNull(annotation, "annotation must not be null");
3436
this.resolverFacade = ResolverFacade.create(method, annotation);
3537
}
3638

39+
@Override
40+
public Class<?> getTestClass() {
41+
return this.testClass;
42+
}
43+
3744
@Override
3845
public ParameterizedTest getAnnotation() {
3946
return this.annotation;

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ public boolean supportsTestTemplate(ExtensionContext context) {
3535
return false;
3636
}
3737

38-
ParameterizedTestContext methodContext = new ParameterizedTestContext(context.getRequiredTestMethod(),
39-
annotation.get());
38+
ParameterizedTestContext methodContext = new ParameterizedTestContext(context.getRequiredTestClass(),
39+
context.getRequiredTestMethod(), annotation.get());
4040

4141
getStore(context).put(DECLARATION_CONTEXT_KEY, methodContext);
4242

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
4848
import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
4949
import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
50-
import org.junit.jupiter.params.aggregator.DefaultArgumentsAccessor;
5150
import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator;
5251
import org.junit.jupiter.params.converter.ArgumentConverter;
5352
import org.junit.jupiter.params.converter.ConvertWith;
@@ -56,6 +55,7 @@
5655
import org.junit.jupiter.params.support.FieldContext;
5756
import org.junit.jupiter.params.support.ParameterDeclaration;
5857
import org.junit.jupiter.params.support.ParameterDeclarations;
58+
import org.junit.jupiter.params.support.ParameterInfo;
5959
import org.junit.platform.commons.JUnitException;
6060
import org.junit.platform.commons.PreconditionViolationException;
6161
import org.junit.platform.commons.function.Try;
@@ -457,10 +457,11 @@ private static ParameterResolutionException parameterResolutionException(String
457457

458458
private interface Resolver {
459459

460-
Object resolve(ParameterContext parameterContext, int parameterIndex, EvaluatedArgumentSet arguments,
461-
int invocationIndex);
460+
Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext,
461+
EvaluatedArgumentSet arguments, int invocationIndex);
462462

463-
Object resolve(FieldContext fieldContext, EvaluatedArgumentSet arguments, int invocationIndex);
463+
Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments,
464+
int invocationIndex);
464465

465466
}
466467

@@ -475,8 +476,8 @@ private static class Converter implements Resolver {
475476
}
476477

477478
@Override
478-
public Object resolve(ParameterContext parameterContext, int parameterIndex, EvaluatedArgumentSet arguments,
479-
int invocationIndex) {
479+
public Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext,
480+
EvaluatedArgumentSet arguments, int invocationIndex) {
480481
Object argument = arguments.getConsumedPayload(parameterIndex);
481482
try {
482483
return this.argumentConverter.convert(argument, parameterContext);
@@ -487,7 +488,8 @@ public Object resolve(ParameterContext parameterContext, int parameterIndex, Eva
487488
}
488489

489490
@Override
490-
public Object resolve(FieldContext fieldContext, EvaluatedArgumentSet arguments, int invocationIndex) {
491+
public Object resolve(FieldContext fieldContext, ExtensionContext extensionContext,
492+
EvaluatedArgumentSet arguments, int invocationIndex) {
491493
Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex());
492494
try {
493495
return this.argumentConverter.convert(argument, fieldContext);
@@ -515,10 +517,9 @@ protected Object aggregateArguments(ArgumentsAccessor accessor, Class<?> targetT
515517
}
516518

517519
@Override
518-
public Object resolve(ParameterContext parameterContext, int parameterIndex, EvaluatedArgumentSet arguments,
519-
int invocationIndex) {
520-
ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(parameterContext, invocationIndex,
521-
arguments.getConsumedPayloads());
520+
public Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext,
521+
EvaluatedArgumentSet arguments, int invocationIndex) {
522+
ArgumentsAccessor accessor = ParameterInfo.get(extensionContext).getArguments();
522523
try {
523524
return this.argumentsAggregator.aggregateArguments(accessor, parameterContext);
524525
}
@@ -529,9 +530,9 @@ public Object resolve(ParameterContext parameterContext, int parameterIndex, Eva
529530
}
530531

531532
@Override
532-
public Object resolve(FieldContext fieldContext, EvaluatedArgumentSet arguments, int invocationIndex) {
533-
ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(fieldContext, invocationIndex,
534-
arguments.getConsumedPayloads());
533+
public Object resolve(FieldContext fieldContext, ExtensionContext extensionContext,
534+
EvaluatedArgumentSet arguments, int invocationIndex) {
535+
ArgumentsAccessor accessor = ParameterInfo.get(extensionContext).getArguments();
535536
try {
536537
return this.argumentsAggregator.aggregateArguments(accessor, fieldContext);
537538
}
@@ -650,7 +651,7 @@ public Optional<String> getParameterName() {
650651
@Override
651652
public Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments,
652653
int invocationIndex, Optional<ParameterContext> originalParameterContext) {
653-
return resolver.resolve(this, arguments, invocationIndex);
654+
return resolver.resolve(this, extensionContext, arguments, invocationIndex);
654655
}
655656
}
656657

@@ -692,7 +693,8 @@ public Object resolve(Resolver resolver, ExtensionContext extensionContext, Eval
692693
ParameterContext parameterContext = originalParameterContext //
693694
.filter(it -> it.getParameter().equals(this.parameter)) //
694695
.orElseGet(() -> toParameterContext(extensionContext, originalParameterContext));
695-
return resolver.resolve(parameterContext, getParameterIndex(), arguments, invocationIndex);
696+
return resolver.resolve(parameterContext, getParameterIndex(), extensionContext, arguments,
697+
invocationIndex);
696698
}
697699

698700
private ParameterContext toParameterContext(ExtensionContext extensionContext,

junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.util.function.BiFunction;
2020

2121
import org.apiguardian.api.API;
22-
import org.junit.jupiter.api.extension.ParameterContext;
2322
import org.junit.jupiter.params.converter.DefaultArgumentConverter;
24-
import org.junit.jupiter.params.support.FieldContext;
2523
import org.junit.platform.commons.util.ClassUtils;
2624
import org.junit.platform.commons.util.Preconditions;
2725

@@ -42,20 +40,11 @@ public class DefaultArgumentsAccessor implements ArgumentsAccessor {
4240
private final Object[] arguments;
4341
private final BiFunction<Object, Class<?>, Object> converter;
4442

45-
public static DefaultArgumentsAccessor create(ParameterContext parameterContext, int invocationIndex,
46-
Object... arguments) {
47-
48-
Preconditions.notNull(parameterContext, "ParameterContext must not be null");
49-
BiFunction<Object, Class<?>, Object> converter = (source, targetType) -> DefaultArgumentConverter.INSTANCE //
50-
.convert(source, targetType, parameterContext);
51-
return new DefaultArgumentsAccessor(converter, invocationIndex, arguments);
52-
}
53-
54-
public static DefaultArgumentsAccessor create(FieldContext fieldContext, int invocationIndex, Object... arguments) {
43+
public static DefaultArgumentsAccessor create(int invocationIndex, ClassLoader classLoader, Object[] arguments) {
44+
Preconditions.notNull(classLoader, "ClassLoader must not be null");
5545

56-
Preconditions.notNull(fieldContext, "FieldContext must not be null");
5746
BiFunction<Object, Class<?>, Object> converter = (source, targetType) -> DefaultArgumentConverter.INSTANCE //
58-
.convert(source, targetType, fieldContext);
47+
.convert(source, targetType, classLoader);
5948
return new DefaultArgumentsAccessor(converter, invocationIndex, arguments);
6049
}
6150

0 commit comments

Comments
 (0)