Skip to content

Commit 3151b71

Browse files
committed
fix: validate argument count for ParameterizedClass field injection
1 parent 1c40f91 commit 3151b71

File tree

4 files changed

+42
-66
lines changed

4 files changed

+42
-66
lines changed

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

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@
2727
import java.util.List;
2828
import java.util.function.Predicate;
2929

30-
import org.jspecify.annotations.Nullable;
3130
import org.junit.jupiter.api.TestInstance;
32-
import org.junit.jupiter.api.extension.ParameterResolutionException;
3331
import org.junit.jupiter.params.provider.Arguments;
3432
import org.junit.platform.commons.JUnitException;
3533
import org.junit.platform.commons.support.HierarchyTraversalMode;
@@ -133,10 +131,6 @@ public ResolverFacade getResolverFacade() {
133131
public ParameterizedClassInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter,
134132
Arguments arguments, int invocationIndex) {
135133

136-
if (this.injectionType == InjectionType.FIELDS) {
137-
assertEnoughArgumentsForFieldInjection(arguments);
138-
}
139-
140134
return new ParameterizedClassInvocationContext(this, formatter, arguments, invocationIndex);
141135
}
142136

@@ -179,65 +173,6 @@ private static <A extends Annotation> A getAnnotation(Method method, Class<A> an
179173
.orElseThrow(() -> new JUnitException("Method not annotated with @" + annotationType.getSimpleName()));
180174
}
181175

182-
private void assertEnoughArgumentsForFieldInjection(Arguments arguments) {
183-
@SuppressWarnings("NullAway")
184-
final Object[] providedArguments = (arguments == null ? new Object[0] : arguments.get());
185-
final int providedArgumentCount = providedArguments.length;
186-
187-
final List<Field> parameterFields = findParameterAnnotatedFields(this.testClass);
188-
if (parameterFields.isEmpty()) {
189-
return;
190-
}
191-
192-
final int requiredArgumentCount = requiredArgumentCountForParameterFields(parameterFields);
193-
if (providedArgumentCount >= requiredArgumentCount) {
194-
return;
195-
}
196-
197-
final @Nullable Field firstMissingField = firstMissingParameterFieldByIndex(parameterFields,
198-
providedArgumentCount);
199-
200-
final String missingTargetDescription = (firstMissingField != null)
201-
? "field '%s' (index %d, type %s)".formatted(firstMissingField.getName(),
202-
firstMissingField.getAnnotation(Parameter.class).value(), firstMissingField.getType().getName())
203-
: "parameter at index %d".formatted(providedArgumentCount);
204-
205-
throw new ParameterResolutionException(
206-
"Not enough arguments for @ParameterizedClass field injection in %s: %s cannot be injected because only %d argument(s) were provided; at least %d are required.".formatted(
207-
this.testClass.getName(), missingTargetDescription, providedArgumentCount, requiredArgumentCount));
208-
}
209-
210-
private static int requiredArgumentCountForParameterFields(List<Field> parameterFields) {
211-
int maxIndex = -1;
212-
for (Field field : parameterFields) {
213-
Parameter param = field.getAnnotation(Parameter.class);
214-
if (param != null) {
215-
int index = param.value();
216-
if (index >= 0 && index > maxIndex) {
217-
maxIndex = index;
218-
}
219-
}
220-
}
221-
return maxIndex + 1;
222-
}
223-
224-
private static @Nullable Field firstMissingParameterFieldByIndex(List<Field> parameterFields,
225-
int providedArgumentCount) {
226-
Field candidate = null;
227-
int minIndex = Integer.MAX_VALUE;
228-
for (Field field : parameterFields) {
229-
Parameter param = field.getAnnotation(Parameter.class);
230-
if (param != null) {
231-
int index = param.value();
232-
if (index >= 0 && index >= providedArgumentCount && index < minIndex) {
233-
candidate = field;
234-
minIndex = index;
235-
}
236-
}
237-
}
238-
return candidate;
239-
}
240-
241176
enum InjectionType {
242177
CONSTRUCTOR, FIELDS
243178
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ private ClassTemplateConstructorParameterResolver createExtensionForConstructorI
6565
}
6666

6767
private Extension createExtensionForFieldInjection() {
68+
this.declarationContext.getResolverFacade().assertEnoughFieldArguments(this.arguments);
69+
6870
ResolverFacade resolverFacade = this.declarationContext.getResolverFacade();
6971
TestInstance.Lifecycle lifecycle = this.declarationContext.getTestInstanceLifecycle();
7072
return switch (lifecycle) {

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,43 @@ private static ParameterResolutionException parameterResolutionException(String
464464
return new ParameterResolutionException(fullMessage, cause);
465465
}
466466

467+
private static void assertFieldAvailableOrThrow(FieldContext fieldContext, EvaluatedArgumentSet arguments) {
468+
int parameterIndex = fieldContext.getParameterIndex();
469+
int provided = arguments.getTotalLength();
470+
int required = parameterIndex + 1;
471+
472+
if (provided < required) {
473+
Field field = fieldContext.getField();
474+
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
475+
("Not enough arguments for @ParameterizedClass field injection: "
476+
+ "field '%s' (index %d, type %s) cannot be injected because "
477+
+ "only %d argument(s) were provided; at least %d are required.").formatted(field.getName(),
478+
parameterIndex, field.getType().getName(), provided, required));
479+
}
480+
}
481+
482+
void assertEnoughFieldArguments(EvaluatedArgumentSet arguments) {
483+
int provided = arguments.getTotalLength();
484+
int required = determineConsumedArgumentLength(Integer.MAX_VALUE);
485+
486+
if (provided < required) {
487+
int missingIndex = provided;
488+
var maybeDecl = getIndexedParameterDeclarations().get(missingIndex);
489+
if (maybeDecl.isPresent() && maybeDecl.get() instanceof FieldParameterDeclaration fpd) {
490+
Field field = fpd.getField();
491+
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
492+
("Not enough arguments for @ParameterizedClass field injection: "
493+
+ "field '%s' (index %d, type %s) cannot be injected because "
494+
+ "only %d argument(s) were provided; at least %d are required.").formatted(field.getName(),
495+
missingIndex, field.getType().getName(), provided, required));
496+
}
497+
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
498+
"Not enough arguments for @ParameterizedClass field injection: "
499+
+ "index %d cannot be injected because only %d argument(s) were provided; at least %d are required.".formatted(
500+
missingIndex, provided, required));
501+
}
502+
}
503+
467504
private interface Resolver {
468505

469506
@Nullable
@@ -495,6 +532,8 @@ private record Converter(ArgumentConverter argumentConverter) implements Resolve
495532
@Override
496533
public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext,
497534
EvaluatedArgumentSet arguments, int invocationIndex) {
535+
ResolverFacade.assertFieldAvailableOrThrow(fieldContext, arguments);
536+
498537
Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex());
499538
try {
500539
return this.argumentConverter.convert(argument, fieldContext);

jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ void failsWithMeaningfulErrorWhenTooFewArgumentsProvidedForFieldInjection() {
560560
results.containerEvents().assertThatEvents()
561561
.haveExactly(1, event(finishedWithFailure(
562562
instanceOf(org.junit.jupiter.api.extension.ParameterResolutionException.class),
563-
message(it -> it.contains("field 's")
563+
message(it -> it.contains("field 's'")
564564
&& it.contains("index 1")
565565
&& it.contains("only 1 argument")
566566
&& it.contains("at least 2"))

0 commit comments

Comments
 (0)