From 3f3aa297413834bcb76b123741342e839248e543 Mon Sep 17 00:00:00 2001 From: Mario khoury Date: Sun, 25 May 2025 00:30:55 +0200 Subject: [PATCH 1/3] Add List-based support to Arguments API (of, arguments, argumentSet, toList) --- .../jupiter/params/provider/Arguments.java | 72 +++++++++++++++++++ .../provider/EmptyArgumentsProvider.java | 2 +- .../params/provider/ArgumentsTests.java | 50 +++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java index b1492ec95188..2ca50ef59591 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -13,6 +13,10 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; @@ -172,4 +176,72 @@ public String toString() { } + /** + * Factory method for creating an instance of {@code Arguments} based on + * the supplied {@code arguments} as a {@link List}. + * + * @param arguments the arguments as a List to be used for an invocation + * of the test method; must not be {@code null} but may contain {@code null} + * @return an instance of {@code Arguments}; never {@code null} + * @see #arguments(List) + */ + @API(status = EXPERIMENTAL, since = "6.0") + static Arguments of(@Nullable List<@Nullable Object> arguments) { + if (arguments == null) { + return of((Object) null); // Properly wrap null + } + if (arguments.isEmpty()) { + // Must still return empty arguments array + return of(new Object[0]); + } + return () -> arguments.toArray(new Object[0]); + } + + /** + * Factory method for creating an instance of {@code Arguments} based on + * the supplied {@code arguments} as a {@link List}. + * + *

This method is an alias for {@link Arguments#of} and is + * intended to be used when statically imported — for example, via: + * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} + * + * @param arguments the arguments as a List to be used for an invocation of the test + * method; must not be {@code null} but may contain {@code null} + * @return an instance of {@code Arguments}; never {@code null} + * @since 6.0 + * @see #argumentSet(String, Object...) + */ + @API(status = EXPERIMENTAL, since = "6.0") + static Arguments arguments(List<@Nullable Object> arguments) { + return of(arguments); + } + + /** + * Factory method for creating an {@link ArgumentSet} based on the supplied + * {@code name} and {@code arguments} as a List. + * + * @param name the name of the argument set; must not be {@code null} or blank + * @param arguments the arguments list to be used for an invocation of the test + * method; must not be {@code null} but may contain {@code null} + * @return an {@code ArgumentSet}; never {@code null} + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + static ArgumentSet argumentSet(String name, List<@Nullable Object> arguments) { + Preconditions.notBlank(name, "name must not be null or blank"); + Preconditions.notNull(arguments, "arguments list must not be null"); + return new ArgumentSet(name, arguments.toArray(new Object[0])); + } + + /** + * Convert the arguments to a mutable List. + * + * @return a mutable List of arguments; never {@code null} but may contain {@code null} + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + default List<@Nullable Object> toList() { + return new ArrayList<>(Arrays.asList(get())); + } + } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java index 9c9b4f5a8173..22af73d7596e 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java @@ -57,7 +57,7 @@ public Stream provideArguments(ParameterDeclarations parame return Stream.of(arguments(Collections.emptySet())); } if (List.class.equals(parameterType)) { - return Stream.of(arguments(Collections.emptyList())); + return Stream.of(Arguments.of((Object) Collections.emptyList())); } if (Set.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySet())); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java index 76dd591f7101..7a461baaecbb 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java @@ -15,7 +15,11 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.Arguments.of; +import java.util.Arrays; +import java.util.List; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.Arguments.ArgumentSet; /** * Unit tests for {@link Arguments}. @@ -56,4 +60,50 @@ void argumentsReturnsSameArrayUsedForCreating() { assertThat(arguments.get()).isSameAs(input); } + @Test + void ofSupportsList() { + List input = Arrays.asList(1, "two", null, 3.0); + Arguments arguments = Arguments.of(input); + + assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); + } + + @Test + void argumentsSupportsListAlias() { + List input = Arrays.asList("a", 2, null); + Arguments arguments = Arguments.arguments(input); + + assertArrayEquals(new Object[] { "a", 2, null }, arguments.get()); + } + + @Test + void argumentSetSupportsList() { + List input = Arrays.asList("x", null, 42); + ArgumentSet argumentSet = Arguments.argumentSet("list-test", input); + + assertArrayEquals(new Object[] { "x", null, 42 }, argumentSet.get()); + assertThat(argumentSet.getName()).isEqualTo("list-test"); + } + + @Test + void toListReturnsMutableListOfArguments() { + Arguments arguments = Arguments.of(Arrays.asList("a", 2, null)); + + List result = arguments.toList(); + + assertThat(result).containsExactly("a", 2, null); // preserves content + result.add("extra"); // confirms mutability + assertThat(result).contains("extra"); + } + + @Test + void toListWorksOnEmptyArguments() { + Arguments arguments = Arguments.of(Arrays.asList()); + + List result = arguments.toList(); + + assertThat(result).isEmpty(); + result.add("extra"); + assertThat(result).containsExactly("extra"); + } } From d566f959f5928db624bb23e720e9e480c8c0a7f1 Mon Sep 17 00:00:00 2001 From: Mario khoury Date: Sun, 25 May 2025 17:36:07 +0200 Subject: [PATCH 2/3] Address feedback: fix argumentsFrom to unpack List properly, update test accordingly --- .../jupiter/params/provider/Arguments.java | 132 +++++++++--------- .../provider/EmptyArgumentsProvider.java | 2 +- .../params/provider/ArgumentsTests.java | 10 +- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java index 2ca50ef59591..7268a78e0a79 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -129,53 +129,6 @@ static ArgumentSet argumentSet(String name, @Nullable Object... arguments) { return new ArgumentSet(name, arguments); } - /** - * Specialization of {@link Arguments} that associates a {@link #getName() name} - * with a set of {@link #get() arguments}. - * - * @since 5.11 - * @see Arguments#argumentSet(String, Object...) - * @see org.junit.jupiter.params.ParameterizedTest#ARGUMENT_SET_NAME_PLACEHOLDER - * @see org.junit.jupiter.params.ParameterizedTest#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER - */ - @API(status = EXPERIMENTAL, since = "5.11") - final class ArgumentSet implements Arguments { - - private final String name; - - private final @Nullable Object[] arguments; - - private ArgumentSet(String name, @Nullable Object[] arguments) { - Preconditions.notBlank(name, "name must not be null or blank"); - Preconditions.notNull(arguments, "arguments array must not be null"); - this.name = name; - this.arguments = arguments; - } - - /** - * Get the name of this {@code ArgumentSet}. - * @return the name of this {@code ArgumentSet}; never {@code null} or blank - */ - public String getName() { - return this.name; - } - - @Override - public @Nullable Object[] get() { - return this.arguments; - } - - /** - * Return the {@link #getName() name} of this {@code ArgumentSet}. - * @return the name of this {@code ArgumentSet} - */ - @Override - public String toString() { - return getName(); - } - - } - /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@code arguments} as a {@link List}. @@ -183,27 +136,22 @@ public String toString() { * @param arguments the arguments as a List to be used for an invocation * of the test method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} - * @see #arguments(List) + * @since 6.0 + * @see #argumentsFrom(List) */ @API(status = EXPERIMENTAL, since = "6.0") - static Arguments of(@Nullable List<@Nullable Object> arguments) { - if (arguments == null) { - return of((Object) null); // Properly wrap null - } - if (arguments.isEmpty()) { - // Must still return empty arguments array - return of(new Object[0]); - } - return () -> arguments.toArray(new Object[0]); + static Arguments from(List<@Nullable Object> arguments) { + Preconditions.notNull(arguments, "arguments must not be null"); + return of(arguments.toArray()); } /** * Factory method for creating an instance of {@code Arguments} based on * the supplied {@code arguments} as a {@link List}. * - *

This method is an alias for {@link Arguments#of} and is + *

This method is an alias for {@link Arguments#from} and is * intended to be used when statically imported — for example, via: - * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} + * {@code import static org.junit.jupiter.params.provider.Arguments.argumentsFrom;} * * @param arguments the arguments as a List to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} @@ -212,29 +160,37 @@ static Arguments of(@Nullable List<@Nullable Object> arguments) { * @see #argumentSet(String, Object...) */ @API(status = EXPERIMENTAL, since = "6.0") - static Arguments arguments(List<@Nullable Object> arguments) { - return of(arguments); + static Arguments argumentsFrom(List<@Nullable Object> arguments) { + return from(arguments); } /** * Factory method for creating an {@link ArgumentSet} based on the supplied - * {@code name} and {@code arguments} as a List. + * {@code name} and {@code arguments} as a {@link List}. + * + *

This method is a convenient alternative to {@link #argumentSet(String, Object...)} + * when working with {@link List} based inputs. * * @param name the name of the argument set; must not be {@code null} or blank * @param arguments the arguments list to be used for an invocation of the test * method; must not be {@code null} but may contain {@code null} * @return an {@code ArgumentSet}; never {@code null} * @since 6.0 + * @see #argumentSet(String, Object...) */ @API(status = EXPERIMENTAL, since = "6.0") - static ArgumentSet argumentSet(String name, List<@Nullable Object> arguments) { + static ArgumentSet argumentSetFrom(String name, List<@Nullable Object> arguments) { Preconditions.notBlank(name, "name must not be null or blank"); Preconditions.notNull(arguments, "arguments list must not be null"); - return new ArgumentSet(name, arguments.toArray(new Object[0])); + return new ArgumentSet(name, arguments.toArray()); } /** - * Convert the arguments to a mutable List. + * Convert the arguments to a new mutable {@link List} containing the same + * elements as {@link #get()}. + * + *

This is useful for test logic that benefits from {@code List} operations such as filtering, + * transformation, or assertions. * * @return a mutable List of arguments; never {@code null} but may contain {@code null} * @since 6.0 @@ -244,4 +200,50 @@ static ArgumentSet argumentSet(String name, List<@Nullable Object> arguments) { return new ArrayList<>(Arrays.asList(get())); } + /** + * Specialization of {@link Arguments} that associates a {@link #getName() name} + * with a set of {@link #get() arguments}. + * + * @since 5.11 + * @see Arguments#argumentSet(String, Object...) + * @see org.junit.jupiter.params.ParameterizedTest#ARGUMENT_SET_NAME_PLACEHOLDER + * @see org.junit.jupiter.params.ParameterizedTest#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER + */ + @API(status = EXPERIMENTAL, since = "5.11") + final class ArgumentSet implements Arguments { + + private final String name; + + private final @Nullable Object[] arguments; + + private ArgumentSet(String name, @Nullable Object[] arguments) { + Preconditions.notBlank(name, "name must not be null or blank"); + Preconditions.notNull(arguments, "arguments array must not be null"); + this.name = name; + this.arguments = arguments; + } + + /** + * Get the name of this {@code ArgumentSet}. + * @return the name of this {@code ArgumentSet}; never {@code null} or blank + */ + public String getName() { + return this.name; + } + + @Override + public @Nullable Object[] get() { + return this.arguments; + } + + /** + * Return the {@link #getName() name} of this {@code ArgumentSet}. + * @return the name of this {@code ArgumentSet} + */ + @Override + public String toString() { + return getName(); + } + + } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java index 22af73d7596e..9c9b4f5a8173 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java @@ -57,7 +57,7 @@ public Stream provideArguments(ParameterDeclarations parame return Stream.of(arguments(Collections.emptySet())); } if (List.class.equals(parameterType)) { - return Stream.of(Arguments.of((Object) Collections.emptyList())); + return Stream.of(arguments(Collections.emptyList())); } if (Set.class.equals(parameterType)) { return Stream.of(arguments(Collections.emptySet())); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java index 7a461baaecbb..4440ed55741d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java @@ -63,7 +63,7 @@ void argumentsReturnsSameArrayUsedForCreating() { @Test void ofSupportsList() { List input = Arrays.asList(1, "two", null, 3.0); - Arguments arguments = Arguments.of(input); + Arguments arguments = Arguments.from(input); assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); } @@ -71,7 +71,7 @@ void ofSupportsList() { @Test void argumentsSupportsListAlias() { List input = Arrays.asList("a", 2, null); - Arguments arguments = Arguments.arguments(input); + Arguments arguments = Arguments.argumentsFrom(input); assertArrayEquals(new Object[] { "a", 2, null }, arguments.get()); } @@ -79,7 +79,7 @@ void argumentsSupportsListAlias() { @Test void argumentSetSupportsList() { List input = Arrays.asList("x", null, 42); - ArgumentSet argumentSet = Arguments.argumentSet("list-test", input); + ArgumentSet argumentSet = Arguments.argumentSetFrom("list-test", input); assertArrayEquals(new Object[] { "x", null, 42 }, argumentSet.get()); assertThat(argumentSet.getName()).isEqualTo("list-test"); @@ -87,7 +87,7 @@ void argumentSetSupportsList() { @Test void toListReturnsMutableListOfArguments() { - Arguments arguments = Arguments.of(Arrays.asList("a", 2, null)); + Arguments arguments = Arguments.from(Arrays.asList("a", 2, null)); List result = arguments.toList(); @@ -98,7 +98,7 @@ void toListReturnsMutableListOfArguments() { @Test void toListWorksOnEmptyArguments() { - Arguments arguments = Arguments.of(Arrays.asList()); + Arguments arguments = Arguments.from(Arrays.asList()); List result = arguments.toList(); From b73ae1102ae54276c03422ad396914b050d321df Mon Sep 17 00:00:00 2001 From: Mario khoury Date: Tue, 27 May 2025 21:49:58 +0200 Subject: [PATCH 3/3] Address feedback: fix documentation and adding test cases --- .../jupiter/params/provider/Arguments.java | 39 ++++++++++--------- .../params/provider/ArgumentsTests.java | 34 ++++++++++++++-- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java index 7268a78e0a79..dbd1046de7c4 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -131,11 +131,11 @@ static ArgumentSet argumentSet(String name, @Nullable Object... arguments) { /** * Factory method for creating an instance of {@code Arguments} based on - * the supplied {@code arguments} as a {@link List}. + * the supplied {@link List} of {@code arguments}. * - * @param arguments the arguments as a List to be used for an invocation - * of the test method; must not be {@code null} but may contain {@code null} - * @return an instance of {@code Arguments}; never {@code null} + * @param arguments the arguments to be used for an invocation of the test + * method; must not be {@code null} but may contain {@code null}. + * @return an instance of {@code Arguments}; never {@code null}. * @since 6.0 * @see #argumentsFrom(List) */ @@ -147,15 +147,15 @@ static Arguments from(List<@Nullable Object> arguments) { /** * Factory method for creating an instance of {@code Arguments} based on - * the supplied {@code arguments} as a {@link List}. + * the supplied {@link List} of {@code arguments}. * *

This method is an alias for {@link Arguments#from} and is * intended to be used when statically imported — for example, via: * {@code import static org.junit.jupiter.params.provider.Arguments.argumentsFrom;} * - * @param arguments the arguments as a List to be used for an invocation of the test - * method; must not be {@code null} but may contain {@code null} - * @return an instance of {@code Arguments}; never {@code null} + * @param arguments the arguments to be used for an invocation of the test + * method; must not be {@code null} but may contain {@code null}. + * @return an instance of {@code Arguments}; never {@code null}. * @since 6.0 * @see #argumentSet(String, Object...) */ @@ -166,15 +166,17 @@ static Arguments argumentsFrom(List<@Nullable Object> arguments) { /** * Factory method for creating an {@link ArgumentSet} based on the supplied - * {@code name} and {@code arguments} as a {@link List}. + * {@code name} and {@link List} of {@code arguments}. * - *

This method is a convenient alternative to {@link #argumentSet(String, Object...)} - * when working with {@link List} based inputs. + *

This method is a convenient alternative to + * {@link #argumentSet(String, Object...)} when working with {@link List} + * based inputs. * - * @param name the name of the argument set; must not be {@code null} or blank - * @param arguments the arguments list to be used for an invocation of the test - * method; must not be {@code null} but may contain {@code null} - * @return an {@code ArgumentSet}; never {@code null} + * @param name the name of the argument set; must not be {@code null} + * or blank. + * @param arguments the arguments to be used for an invocation of the test + * method; must not be {@code null} but may contain {@code null}. + * @return an {@code ArgumentSet}; never {@code null}. * @since 6.0 * @see #argumentSet(String, Object...) */ @@ -189,10 +191,11 @@ static ArgumentSet argumentSetFrom(String name, List<@Nullable Object> arguments * Convert the arguments to a new mutable {@link List} containing the same * elements as {@link #get()}. * - *

This is useful for test logic that benefits from {@code List} operations such as filtering, - * transformation, or assertions. + *

This is useful for test logic that benefits from {@code List} + * operations such as filtering, transformation, or assertions. * - * @return a mutable List of arguments; never {@code null} but may contain {@code null} + * @return a mutable List of arguments; never {@code null} but may contain + * {@code null}. * @since 6.0 */ @API(status = EXPERIMENTAL, since = "6.0") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java index 4440ed55741d..4ce0dbb61d28 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.jupiter.params.provider.Arguments.of; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -61,7 +62,7 @@ void argumentsReturnsSameArrayUsedForCreating() { } @Test - void ofSupportsList() { + void fromSupportsList() { List input = Arrays.asList(1, "two", null, 3.0); Arguments arguments = Arguments.from(input); @@ -69,7 +70,20 @@ void ofSupportsList() { } @Test - void argumentsSupportsListAlias() { + void fromSupportsListDefensiveCopy() { + List input = new ArrayList<>(Arrays.asList(1, "two", null, 3.0)); + Arguments arguments = Arguments.from(input); + + // Modify input + input.set(1, "changed"); + input.add("new"); + + // Assert that arguments are unchanged + assertArrayEquals(new Object[] { 1, "two", null, 3.0 }, arguments.get()); + } + + @Test + void argumentsFromSupportsList() { List input = Arrays.asList("a", 2, null); Arguments arguments = Arguments.argumentsFrom(input); @@ -87,7 +101,7 @@ void argumentSetSupportsList() { @Test void toListReturnsMutableListOfArguments() { - Arguments arguments = Arguments.from(Arrays.asList("a", 2, null)); + Arguments arguments = Arguments.of("a", 2, null); List result = arguments.toList(); @@ -96,9 +110,21 @@ void toListReturnsMutableListOfArguments() { assertThat(result).contains("extra"); } + @Test + void toListDoesNotAffectInternalArgumentsState() { + Arguments arguments = Arguments.of("a", 2, null); + + List result = arguments.toList(); + result.add("extra"); // mutate the returned list + + // Confirm that internal state was not modified + List freshCopy = arguments.toList(); + assertThat(freshCopy).containsExactly("a", 2, null); + } + @Test void toListWorksOnEmptyArguments() { - Arguments arguments = Arguments.from(Arrays.asList()); + Arguments arguments = Arguments.of(); List result = arguments.toList();