Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2e56d56
Issue: #3708 add ParameterizedTest#argumentCountValidation
jonas-jebing-at-ebay Oct 4, 2024
3d40ebe
Merge branch 'main' into argument-count-validation-mode
JonasJebing Oct 23, 2024
98cdf69
address PR comments
JonasJebing Oct 23, 2024
89a7443
gradle spotlessApply
JonasJebing Oct 23, 2024
5c71632
move example to .adoc because it's a test that should error
JonasJebing Oct 23, 2024
edd9f46
fix newline char assertion for Windows
JonasJebing Oct 23, 2024
beb53ab
gradle spotlessApply
JonasJebing Oct 23, 2024
b117b69
Merge branch 'main' into argument-count-validation-mode
JonasJebing Oct 23, 2024
37216f8
Merge branch 'main' into argument-count-validation-mode
JonasJebing Oct 24, 2024
f87404f
add change to release-notes 5.12.0-M1
JonasJebing Oct 24, 2024
14a071b
fix ArgumentCountValidationMode javadoc typo
JonasJebing Oct 24, 2024
762c39a
improve ParameterizedTest javadoc
JonasJebing Oct 24, 2024
8a84dc2
use underscores for unused lambda parameter
JonasJebing Oct 24, 2024
09ced82
use root context store for caching config value
JonasJebing Oct 24, 2024
0bf0b05
remove mention of experimental status from release note
JonasJebing Oct 24, 2024
f189f07
Merge branch 'main' into argument-count-validation-mode
JonasJebing Oct 25, 2024
36e7729
`@ParameterizedTest`s to Parameterized tests
JonasJebing Oct 25, 2024
851a4db
improve release note
JonasJebing Oct 25, 2024
a986bba
move user guide example to ParameterizedTestDemo
JonasJebing Oct 25, 2024
2dafffb
Merge branch 'main' into argument-count-validation-mode
JonasJebing Nov 12, 2024
e70240c
move argument count validation to happen later
JonasJebing Nov 12, 2024
3d84b9f
update javadoc to use new ArgumentCountValidator
JonasJebing Nov 12, 2024
dd6b55a
retrigger checks
JonasJebing Nov 13, 2024
1151f6b
Merge branch 'main' into argument-count-validation-mode
JonasJebing Nov 14, 2024
906881c
add small ArgumentCountValidator perf optimisations
JonasJebing Nov 14, 2024
89ceaec
Merge branch 'main' into argument-count-validation-mode
JonasJebing Nov 15, 2024
9619c36
Merge branch 'main' into argument-count-validation-mode
JonasJebing Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.params;

import org.apiguardian.api.API;
import org.junit.jupiter.params.provider.ArgumentsSource;

/**
* Enumeration of argument count validation modes for {@link ParameterizedTest @ParameterizedTest}.
*
* <p>When an {@link ArgumentsSource} provides more arguments than declared by the test method,
* there might be a bug in the test method or the {@link ArgumentsSource}.
* By default, the additional arguments are ignored.
* {@link ArgumentCountValidationMode} allows you to control how additional arguments are handled.
*
* @since 5.12
* @see ParameterizedTest
*/
@API(status = API.Status.EXPERIMENTAL, since = "5.12")
public enum ArgumentCountValidationMode {
/**
* Use the default cleanup mode.
*
* <p>The default cleanup mode may be changed via the
* {@value ParameterizedTestExtension#ARGUMENT_COUNT_VALIDATION_KEY} configuration parameter
* (see the User Guide for details on configuration parameters).
*/
DEFAULT,

/**
* Use the "none" argument count validation mode.
*
* <p>When there are more arguments provided than declared by the test method,
* these additional arguments are ignored.
*/
NONE,

/**
* Use the strict argument count validation mode.
*
* <p>When there are more arguments provided than declared by the test method, this raises an error.
*/
STRICT,
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apiguardian.api.API;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.provider.ArgumentsSource;

/**
* {@code @ParameterizedTest} is used to signal that the annotated method is a
Expand Down Expand Up @@ -291,4 +292,21 @@
@API(status = STABLE, since = "5.10")
boolean autoCloseArguments() default true;

/**
* Configure how the number of arguments provided by an {@link ArgumentsSource} are validated.
*
* <p>Defaults to {@link ArgumentCountValidationMode#DEFAULT}.
*
* <p>When an {@link ArgumentsSource} provides more arguments than declared by the test method,
* there might be a bug in the test method or the {@link ArgumentsSource}.
* By default, the additional arguments are ignored.
* {@code argumentCountValidation} allows you to control how additional arguments are handled.
* This can also be controlled via the {@value ParameterizedTestExtension#ARGUMENT_COUNT_VALIDATION_KEY}
* configuration parameter (see the User Guide for details on configuration parameters).
*
* @since 5.12
* @see ArgumentCountValidationMode
*/
@API(status = EXPERIMENTAL, since = "5.12")
ArgumentCountValidationMode argumentCountValidation() default ArgumentCountValidationMode.DEFAULT;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
Expand All @@ -26,6 +30,8 @@
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.support.AnnotationConsumerInitializer;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.commons.util.Preconditions;

Expand All @@ -34,10 +40,13 @@
*/
class ParameterizedTestExtension implements TestTemplateInvocationContextProvider {

private static final Logger logger = LoggerFactory.getLogger(ParameterizedTestExtension.class);

private static final String METHOD_CONTEXT_KEY = "context";
static final String ARGUMENT_MAX_LENGTH_KEY = "junit.jupiter.params.displayname.argument.maxlength";
static final String DEFAULT_DISPLAY_NAME = "{default_display_name}";
static final String DISPLAY_NAME_PATTERN_KEY = "junit.jupiter.params.displayname.default";
static final String ARGUMENT_COUNT_VALIDATION_KEY = "junit.jupiter.params.argumentCountValidation";

@Override
public boolean supportsTestTemplate(ExtensionContext context) {
Expand Down Expand Up @@ -86,6 +95,7 @@ public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContex
.map(provider -> AnnotationConsumerInitializer.initialize(templateMethod, provider))
.flatMap(provider -> arguments(provider, extensionContext))
.map(arguments -> {
validateArgumentCount(extensionContext, arguments);
invocationCount.incrementAndGet();
return createInvocationContext(formatter, methodContext, arguments, invocationCount.intValue());
})
Expand All @@ -99,6 +109,55 @@ private ExtensionContext.Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(ParameterizedTestExtension.class, context.getRequiredTestMethod()));
}

private void validateArgumentCount(ExtensionContext extensionContext, Arguments arguments) {
ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext);
switch (argumentCountValidationMode) {
case DEFAULT:
case NONE:
return;
case STRICT:
int testParamCount = extensionContext.getRequiredTestMethod().getParameterCount();
int argumentsCount = arguments.get().length;
Preconditions.condition(testParamCount == argumentsCount, () -> String.format(
"Configuration error: the @ParameterizedTest has %s argument(s) but there were %s argument(s) provided./nNote: the provided arguments are %s",
testParamCount, argumentsCount, Arrays.toString(arguments.get())));
break;
default:
throw new ExtensionConfigurationException(
"Unsupported argument count validation mode: " + argumentCountValidationMode);
}
}

private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) {
ParameterizedTest parameterizedTest = findAnnotation(//
extensionContext.getRequiredTestMethod(), ParameterizedTest.class//
).orElseThrow(NoSuchElementException::new);
if (parameterizedTest.argumentCountValidation() != ArgumentCountValidationMode.DEFAULT) {
return parameterizedTest.argumentCountValidation();
}
else {
return getArgumentCountValidationModeConfiguration(extensionContext);
}
}

private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration(ExtensionContext extensionContext) {
String key = ARGUMENT_COUNT_VALIDATION_KEY;
ArgumentCountValidationMode fallback = ArgumentCountValidationMode.DEFAULT;
Optional<String> optionalValue = extensionContext.getConfigurationParameter(key);
if (optionalValue.isPresent()) {
String value = optionalValue.get();
return Arrays.stream(ArgumentCountValidationMode.values()).filter(
mode -> mode.name().equalsIgnoreCase(value)).findFirst().orElseGet(() -> {
logger.warn(() -> String.format(
"Ignored invalid configuration '%s' set via the '%s' configuration parameter.", value, key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be the same as in EnumConfigurationParameterConverter. Unfortunately, we can't (easily) reuse that here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some way that we could make it more easily reusable across the project? Maybe I could add another PR to address that after merging this one.
For now, I basically copied the messages from EnumConfigurationParameterConverter.

return fallback;
});
}
else {
return fallback;
}
}

private TestTemplateInvocationContext createInvocationContext(ParameterizedTestNameFormatter formatter,
ParameterizedTestMethodContext methodContext, Arguments arguments, int invocationIndex) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Event;
import org.junit.platform.testkit.engine.EventConditions;
import org.opentest4j.TestAbortedException;

/**
Expand Down Expand Up @@ -1093,6 +1094,42 @@ private EngineExecutionResults execute(String methodName, Class<?>... methodPara

}

@Nested
class UnusedArgumentsWithStrictArgumentsCountIntegrationTests {
@Test
void failsWithArgumentsSourceProvidingUnusedArguments() {
var results = execute(UnusedArgumentsTestCase.class, "testWithTwoUnusedStringArgumentsProvider",
String.class);
results.allEvents().assertThatEvents() //
.haveExactly(1, event(EventConditions.finishedWithFailure(message(
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided./nNote: the provided arguments are [foo, unused1]"))));
}

@Test
void failsWithMethodSourceProvidingUnusedArguments() {
var results = execute(UnusedArgumentsTestCase.class, "testWithMethodSourceProvidingUnusedArguments",
String.class);
results.allEvents().assertThatEvents() //
.haveExactly(1, event(EventConditions.finishedWithFailure(message(
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided./nNote: the provided arguments are [foo, unused1]"))));
}

@Test
void executesWithMethodSourceProvidingUnusedArguments() {
var results = execute(RepeatableSourcesTestCase.class, "testWithRepeatableCsvSource", String.class);
results.allEvents().assertThatEvents() //
.haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) //
.haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b"))));
}

private EngineExecutionResults execute(Class<?> javaClass, String methodName,
Class<?>... methodParameterTypes) {
return EngineTestKit.engine(new JupiterTestEngine()).selectors(
selectMethod(javaClass, methodName, methodParameterTypes)).configurationParameter(
ParameterizedTestExtension.ARGUMENT_COUNT_VALIDATION_KEY, "strict").execute();
}
}

@Nested
class RepeatableSourcesIntegrationTests {

Expand Down