From 38a819d51d46cdf79c23f176d7e9245232bba487 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Thu, 18 Apr 2024 20:17:48 +0300 Subject: [PATCH 01/20] add repeatable capabilities for CSVSource annotation --- .../jupiter/params/provider/CSVSources.java | 29 ++++++++++++ .../params/provider/CsvArgumentsProvider.java | 2 +- .../jupiter/params/provider/CsvSource.java | 2 + ...RepeatableAnnotationArgumentsProvider.java | 46 +++++++++++++++++++ .../AnnotationConsumerInitializer.java | 29 ++++++++++-- .../ParameterizedTestIntegrationTests.java | 15 ++++++ .../provider/CsvArgumentsProviderTests.java | 14 +++--- .../AnnotationConsumerInitializerTests.java | 29 ++++++++++++ 8 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java new file mode 100644 index 00000000000..f7ca9a8662c --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java @@ -0,0 +1,29 @@ +/* + * 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.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.7") +public @interface CSVSources { + CsvSource[] value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index a924cf63722..2086208a588 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -33,7 +33,7 @@ /** * @since 5.0 */ -class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { +class CsvArgumentsProvider extends RepeatableAnnotationArgumentsProvider { private static final String LINE_SEPARATOR = "\n"; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index ecf3ca0848e..b214fb17285 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -64,6 +65,7 @@ */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(CSVSources.class) @Documented @API(status = STABLE, since = "5.7") @ArgumentsSource(CsvArgumentsProvider.class) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java new file mode 100644 index 00000000000..58762fb0a05 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java @@ -0,0 +1,46 @@ +/* + * 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.provider; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.support.AnnotationConsumer; +import org.junit.platform.commons.util.Preconditions; + +@API(status = EXPERIMENTAL, since = "5.10") +public abstract class RepeatableAnnotationArgumentsProvider + implements ArgumentsProvider, AnnotationConsumer { + + public RepeatableAnnotationArgumentsProvider() { + } + + private final List annotations = new ArrayList<>(); + + @Override + public void accept(A annotation) { + Preconditions.notNull(annotation, "annotation must not be null"); + annotations.add(annotation); + } + + @Override + public Stream provideArguments(ExtensionContext context) { + return annotations.stream().flatMap(annotation -> provideArguments(context, annotation)); + } + + protected abstract Stream provideArguments(ExtensionContext context, A annotation); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java index 94814e7c5bf..37e5a05249c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java @@ -12,18 +12,23 @@ import static java.util.Arrays.asList; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.AnnotationUtils; /** * {@code AnnotationConsumerInitializer} is an internal helper class for @@ -47,14 +52,28 @@ private AnnotationConsumerInitializer() { public static T initialize(AnnotatedElement annotatedElement, T annotationConsumerInstance) { if (annotationConsumerInstance instanceof AnnotationConsumer) { Class annotationType = findConsumedAnnotationType(annotationConsumerInstance); - Annotation annotation = AnnotationUtils.findAnnotation(annotatedElement, annotationType) // - .orElseThrow(() -> new JUnitException(annotationConsumerInstance.getClass().getName() - + " must be used with an annotation of type " + annotationType.getName())); - initializeAnnotationConsumer((AnnotationConsumer) annotationConsumerInstance, annotation); + List annotations = streamAnnotations(annotatedElement, annotationType); + + if (annotations.isEmpty()) { + throw new JUnitException(annotationConsumerInstance.getClass().getName() + + " must be used with an annotation of type " + annotationType.getName()); + } + + annotations.forEach(annotation -> initializeAnnotationConsumer( + (AnnotationConsumer) annotationConsumerInstance, annotation)); } return annotationConsumerInstance; } + private static List streamAnnotations(AnnotatedElement annotatedElement, + Class annotationType) { + + return annotationType.isAnnotationPresent(Repeatable.class) + ? findRepeatableAnnotations(annotatedElement, annotationType) + : Stream.of(findAnnotation(annotatedElement, annotationType)).filter(Optional::isPresent).map( + Optional::get).collect(Collectors.toList()); + } + private static Class findConsumedAnnotationType(T annotationConsumerInstance) { Predicate consumesAnnotation = annotationConsumingMethodSignatures.stream() // .map(signature -> (Predicate) signature::isMatchingWith) // diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 69a28343706..b2a0e2f9aad 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -248,6 +248,14 @@ void executesWithCsvSource() { .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); } + @Test + void executesWithRepeatableCsvSource() { + var results = execute("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")))); + } + @Test void executesWithCustomName() { var results = execute("testWithCustomName", String.class, int.class); @@ -1107,6 +1115,13 @@ void testWithCsvSource(String argument) { fail(argument); } + @ParameterizedTest + @CsvSource({ "a" }) + @CsvSource({ "b" }) + void testWithRepeatableCsvSource(String argument) { + fail(argument); + } + @ParameterizedTest(name = "{0} and {1}") @CsvSource({ "foo, 23", "bar, 42" }) void testWithCustomName(String argument, int i) { diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index a7ecddec1f7..0ea40f9c70e 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -40,7 +40,7 @@ void throwsExceptionIfNeitherValueNorTextBlockIsDeclared() { var annotation = csvSource().build(); assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); } @@ -52,7 +52,7 @@ void throwsExceptionIfValueAndTextBlockAreDeclared() { """).build(); assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessage("@CsvSource must be declared with either `value` or `textBlock` but not both"); } @@ -223,7 +223,7 @@ void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { var annotation = csvSource().delimiter('|').delimiterString("~~~").build(); assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// .withMessageContaining("CsvSource"); } @@ -270,7 +270,7 @@ void throwsExceptionIfSourceExceedsMaxCharsPerColumnConfig() { var annotation = csvSource().lines("413").maxCharsPerColumn(2).build(); assertThatExceptionOfType(CsvParsingException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); } @@ -289,7 +289,7 @@ void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').build(); assertThatExceptionOfType(CsvParsingException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("Failed to parse CSV input configured via Mock for CsvSource")// .withRootCauseInstanceOf(ArrayIndexOutOfBoundsException.class); } @@ -308,7 +308,7 @@ void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber() { var annotation = csvSource().lines("41").delimiter(';').maxCharsPerColumn(-1).build(); assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); } @@ -372,7 +372,7 @@ void throwsExceptionIfColumnCountExceedsHeaderCount() { """).build(); assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(() -> provideArguments(annotation))// + .isThrownBy(() -> provideArguments(annotation).findAny())// .withMessage( "The number of columns (3) exceeds the number of supplied headers (2) in CSV record: [banana, 2, BOOM!]"); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java index 7e3c34b3ab9..ee699642f0e 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java @@ -13,7 +13,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.support.AnnotationConsumerInitializer.initialize; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.time.LocalDate; @@ -28,6 +32,7 @@ import org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.RepeatableAnnotationArgumentsProvider; import org.junit.platform.commons.JUnitException; @DisplayName("AnnotationConsumerInitializer") @@ -93,6 +98,16 @@ void shouldThrowExceptionWhenParameterIsNotAnnotated() throws NoSuchMethodExcept assertThatThrownBy(() -> initialize(parameter, instance)).isInstanceOf(JUnitException.class); } + @Test + void shouldInitializeForEachAnnotationsWhenItIsRepeatable() throws NoSuchMethodException { + var instance = spy(new SomeRepeatableAnnotationArgumentsProvider()); + var method = SubjectClass.class.getDeclaredMethod("repeatableAnnotation", String.class); + + initialize(method, instance); + + verify(instance, times(2)).accept(any(CsvSource.class)); + } + private static class SomeAnnotationBasedArgumentsProvider extends AnnotationBasedArgumentsProvider { CsvSource annotation; @@ -126,6 +141,15 @@ public void accept(CsvSource csvSource) { } } + private static class SomeRepeatableAnnotationArgumentsProvider + extends RepeatableAnnotationArgumentsProvider { + + @Override + protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { + return Stream.empty(); + } + } + @SuppressWarnings("unused") private static class SubjectClass { @@ -138,6 +162,11 @@ void bar(@JavaTimeConversionPattern("pattern") LocalDate date) { void noAnnotation(String param) { } + + @CsvSource("a") + @CsvSource("b") + void repeatableAnnotation(String param) { + } } } From 601b3377cd8118d1ba1e4393290b6ff9f6a4b308 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 19 Apr 2024 15:15:49 +0300 Subject: [PATCH 02/20] apply suggestions based on #3787 comments --- .../junit/jupiter/params/provider/CsvSource.java | 2 +- .../provider/{CSVSources.java => CsvSources.java} | 4 ++-- .../RepeatableAnnotationArgumentsProvider.java | 2 +- .../support/AnnotationConsumerInitializer.java | 14 ++++++-------- .../params/ParameterizedTestIntegrationTests.java | 6 +++--- 5 files changed, 13 insertions(+), 15 deletions(-) rename junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/{CSVSources.java => CsvSources.java} (91%) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index b214fb17285..8a142e2bf97 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -65,7 +65,7 @@ */ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -@Repeatable(CSVSources.class) +@Repeatable(CsvSources.class) @Documented @API(status = STABLE, since = "5.7") @ArgumentsSource(CsvArgumentsProvider.class) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java similarity index 91% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java rename to junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java index f7ca9a8662c..afdcd801656 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CSVSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java @@ -23,7 +23,7 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented -@API(status = STABLE, since = "5.7") -public @interface CSVSources { +@API(status = STABLE, since = "5.11") +public @interface CsvSources { CsvSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java index 58762fb0a05..b109135f7f4 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java @@ -22,7 +22,7 @@ import org.junit.jupiter.params.support.AnnotationConsumer; import org.junit.platform.commons.util.Preconditions; -@API(status = EXPERIMENTAL, since = "5.10") +@API(status = EXPERIMENTAL, since = "5.11") public abstract class RepeatableAnnotationArgumentsProvider implements ArgumentsProvider, AnnotationConsumer { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java index 37e5a05249c..9296c70dead 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java @@ -11,6 +11,7 @@ package org.junit.jupiter.params.support; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; @@ -21,11 +22,9 @@ import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; @@ -52,7 +51,7 @@ private AnnotationConsumerInitializer() { public static T initialize(AnnotatedElement annotatedElement, T annotationConsumerInstance) { if (annotationConsumerInstance instanceof AnnotationConsumer) { Class annotationType = findConsumedAnnotationType(annotationConsumerInstance); - List annotations = streamAnnotations(annotatedElement, annotationType); + List annotations = findAnnotations(annotatedElement, annotationType); if (annotations.isEmpty()) { throw new JUnitException(annotationConsumerInstance.getClass().getName() @@ -65,13 +64,12 @@ public static T initialize(AnnotatedElement annotatedElement, T annotationCo return annotationConsumerInstance; } - private static List streamAnnotations(AnnotatedElement annotatedElement, - Class annotationType) { + private static List findAnnotations(AnnotatedElement annotatedElement, + Class annotationType) { return annotationType.isAnnotationPresent(Repeatable.class) ? findRepeatableAnnotations(annotatedElement, annotationType) - : Stream.of(findAnnotation(annotatedElement, annotationType)).filter(Optional::isPresent).map( - Optional::get).collect(Collectors.toList()); + : findAnnotation(annotatedElement, annotationType).map(Collections::singletonList).orElse(emptyList()); } private static Class findConsumedAnnotationType(T annotationConsumerInstance) { diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index b2a0e2f9aad..953b8bcdd0a 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -251,9 +251,9 @@ void executesWithCsvSource() { @Test void executesWithRepeatableCsvSource() { var results = execute("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")))); + 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")))); } @Test From 2cfc83ae5b8dac1a65a5d340eef4a7e9fa9a9781 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 19 Apr 2024 18:33:56 +0300 Subject: [PATCH 03/20] add unit tests for RepeatableAnnotationArgumentsProvider --- ...atableAnnotationArgumentsProviderTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java new file mode 100644 index 00000000000..1cbbdf13f1b --- /dev/null +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java @@ -0,0 +1,50 @@ +package org.junit.jupiter.params.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; +import static org.mockito.Mockito.mock; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.Spy; + +class RepeatableAnnotationArgumentsProviderTest { + + @Spy + private final RepeatableAnnotationArgumentsProvider repeatableAnnotationArgumentsProvider = new RepeatableAnnotationArgumentsProvider<>() { + + @Override + protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { + return Stream.of(Arguments.of(annotation)); + } + }; + + @Test + @DisplayName("should throw exception when null annotation is provided to accept method") + void shouldThrowExceptionWhenNullAnnotationIsProvidedToAccept() { + assertThatThrownBy(() -> repeatableAnnotationArgumentsProvider.accept(null)) // + .hasMessage("annotation must not be null"); + } + + @Test + @DisplayName("should invoke the provideArguments template method for every accepted annotation") + void shouldInvokeTemplateMethodForEachAnnotationProvided() { + var extensionContext = mock(ExtensionContext.class); + var annotation1 = csvSource("1"); + var annotation2 = csvSource("2"); + + repeatableAnnotationArgumentsProvider.accept(annotation1); + repeatableAnnotationArgumentsProvider.accept(annotation2); + + var arguments = repeatableAnnotationArgumentsProvider.provideArguments(extensionContext).toList(); + + assertThat(arguments).hasSize(2); + assertThat(arguments.getFirst().get()[0]).isEqualTo(annotation1); + assertThat(arguments.get(1).get()[0]).isEqualTo(annotation2); + } + +} \ No newline at end of file From c1e61545e147769d36a24ebd5e217a02bc8ce3a2 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 19 Apr 2024 18:58:13 +0300 Subject: [PATCH 04/20] adapt the rest of @..Source (except Null/Empty related) annotations as repeatable --- .../provider/CsvFileArgumentsProvider.java | 2 +- .../params/provider/CsvFileSource.java | 2 ++ .../params/provider/CsvFileSources.java | 19 +++++++++++++++++++ .../provider/EnumArgumentsProvider.java | 2 +- .../jupiter/params/provider/EnumSource.java | 2 ++ .../jupiter/params/provider/EnumSources.java | 19 +++++++++++++++++++ .../provider/FieldArgumentsProvider.java | 2 +- .../jupiter/params/provider/FieldSource.java | 2 ++ .../jupiter/params/provider/FieldSources.java | 19 +++++++++++++++++++ .../provider/MethodArgumentsProvider.java | 2 +- .../jupiter/params/provider/MethodSource.java | 2 ++ .../params/provider/MethodSources.java | 19 +++++++++++++++++++ .../provider/ValueArgumentsProvider.java | 2 +- .../jupiter/params/provider/ValueSource.java | 2 ++ .../jupiter/params/provider/ValueSources.java | 19 +++++++++++++++++++ 15 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index 39d286408aa..de6c56cd0be 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -41,7 +41,7 @@ /** * @since 5.0 */ -class CsvFileArgumentsProvider extends AnnotationBasedArgumentsProvider { +class CsvFileArgumentsProvider extends RepeatableAnnotationArgumentsProvider { private final InputStreamProvider inputStreamProvider; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java index 6017132348d..83b4a41a599 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -63,6 +64,7 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(CsvFileSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(CsvFileArgumentsProvider.class) @SuppressWarnings("exports") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java new file mode 100644 index 00000000000..acdcec6ae75 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java @@ -0,0 +1,19 @@ +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.11") +public @interface CsvFileSources { + CsvFileSource[] value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java index 0a525289e56..d858533681f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java @@ -24,7 +24,7 @@ /** * @since 5.0 */ -class EnumArgumentsProvider extends AnnotationBasedArgumentsProvider { +class EnumArgumentsProvider extends RepeatableAnnotationArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, EnumSource enumSource) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java index ab05a56cf8a..51534e59065 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java @@ -16,6 +16,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -49,6 +50,7 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(EnumSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(EnumArgumentsProvider.class) @SuppressWarnings("exports") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java new file mode 100644 index 00000000000..db445f12643 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java @@ -0,0 +1,19 @@ +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.11") +public @interface EnumSources { + EnumSource[] value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java index c385d1bb3ba..aff6f90b384 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java @@ -35,7 +35,7 @@ * * @since 5.11 */ -class FieldArgumentsProvider extends AnnotationBasedArgumentsProvider { +class FieldArgumentsProvider extends RepeatableAnnotationArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, FieldSource fieldSource) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java index 1d3198ceb52..a74ed2aa810 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -112,6 +113,7 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(FieldSources.class) @API(status = EXPERIMENTAL, since = "5.11") @ArgumentsSource(FieldArgumentsProvider.class) @SuppressWarnings("exports") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java new file mode 100644 index 00000000000..dcadb99614d --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java @@ -0,0 +1,19 @@ +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.11") +public @interface FieldSources{ + FieldSource[] value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index 5915602fba9..b8ff50c149e 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -35,7 +35,7 @@ /** * @since 5.0 */ -class MethodArgumentsProvider extends AnnotationBasedArgumentsProvider { +class MethodArgumentsProvider extends RepeatableAnnotationArgumentsProvider { private static final Predicate isFactoryMethod = // method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java index 72404ee1063..b0ea60cc66f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -103,6 +104,7 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(MethodSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(MethodArgumentsProvider.class) @SuppressWarnings("exports") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java new file mode 100644 index 00000000000..1e0425520cc --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java @@ -0,0 +1,19 @@ +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.11") +public @interface MethodSources { + MethodSource[] value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java index e42d448a741..43ce3ec917d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java @@ -24,7 +24,7 @@ /** * @since 5.0 */ -class ValueArgumentsProvider extends AnnotationBasedArgumentsProvider { +class ValueArgumentsProvider extends RepeatableAnnotationArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, ValueSource valueSource) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java index d2ae43eb03e..30be90497eb 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java @@ -14,6 +14,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -40,6 +41,7 @@ @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Repeatable(ValueSources.class) @API(status = STABLE, since = "5.7") @ArgumentsSource(ValueArgumentsProvider.class) @SuppressWarnings("exports") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java new file mode 100644 index 00000000000..8a8e86fbff7 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java @@ -0,0 +1,19 @@ +package org.junit.jupiter.params.provider; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = STABLE, since = "5.11") +public @interface ValueSources { + ValueSource[] value(); +} From 9854e8aca9216e554adf926f84a14872a98c6dc0 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 19 Apr 2024 20:16:18 +0300 Subject: [PATCH 05/20] wip - add integration tests for repeatable annotations --- .../ParameterizedTestIntegrationTests.java | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 953b8bcdd0a..e34c18fd2c4 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -248,14 +248,6 @@ void executesWithCsvSource() { .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); } - @Test - void executesWithRepeatableCsvSource() { - var results = execute("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")))); - } - @Test void executesWithCustomName() { var results = execute("testWithCustomName", String.class, int.class); @@ -1078,6 +1070,31 @@ private EngineExecutionResults execute(String methodName, Class... methodPara } + @Nested + class RepeatableSourcesIntegrationTests { + + @Test + void executesWithRepeatableCsvSource() { + var results = execute("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")))); + } + + @Test + void executesWithRepeatableCsvFileSource() { + var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // + .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), finishedWithFailure(message("apple 1")))); + } + + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { + return ParameterizedTestIntegrationTests.this.execute(RepeatableSourcesTestCase.class, methodName, + methodParameterTypes); + } + } + @Test void closeAutoCloseableArgumentsAfterTest() { var results = execute("testWithAutoCloseableArgument", AutoCloseableArgument.class); @@ -1115,13 +1132,6 @@ void testWithCsvSource(String argument) { fail(argument); } - @ParameterizedTest - @CsvSource({ "a" }) - @CsvSource({ "b" }) - void testWithRepeatableCsvSource(String argument) { - fail(argument); - } - @ParameterizedTest(name = "{0} and {1}") @CsvSource({ "foo, 23", "bar, 42" }) void testWithCustomName(String argument, int i) { @@ -1920,6 +1930,24 @@ static Stream providerMethod() { } + static class RepeatableSourcesTestCase { + + @ParameterizedTest + @CsvSource({ "a" }) + @CsvSource({ "b" }) + void testWithRepeatableCsvSource(String argument) { + fail(argument); + } + + @ParameterizedTest + @CsvFileSource(resources = "two-column.csv") + @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") + void testWithRepeatableCsvFileSource(String column1, String column2) { + fail("%s %s".formatted(column1, column2)); + } + + } + private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { @Override From 4caeb42c2e77ebec6ac9b6277e82eaee74a298fc Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 19 Apr 2024 20:19:54 +0300 Subject: [PATCH 06/20] apply spotless formatting --- .../jupiter/params/provider/CsvFileSources.java | 10 ++++++++++ .../junit/jupiter/params/provider/EnumSources.java | 10 ++++++++++ .../junit/jupiter/params/provider/FieldSources.java | 12 +++++++++++- .../junit/jupiter/params/provider/MethodSources.java | 10 ++++++++++ .../junit/jupiter/params/provider/ValueSources.java | 10 ++++++++++ .../params/ParameterizedTestIntegrationTests.java | 8 +++++--- .../RepeatableAnnotationArgumentsProviderTest.java | 12 +++++++++++- 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java index acdcec6ae75..69a671eef55 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.apiguardian.api.API.Status.STABLE; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java index db445f12643..fbbd9afa434 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.apiguardian.api.API.Status.STABLE; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java index dcadb99614d..19e20067b40 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.apiguardian.api.API.Status.STABLE; @@ -14,6 +24,6 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") -public @interface FieldSources{ +public @interface FieldSources { FieldSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java index 1e0425520cc..8f0792ade99 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.apiguardian.api.API.Status.STABLE; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java index 8a8e86fbff7..00587e701b7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.apiguardian.api.API.Status.STABLE; diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index e34c18fd2c4..704c811cb9c 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1085,13 +1085,15 @@ void executesWithRepeatableCsvSource() { void executesWithRepeatableCsvFileSource() { var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // - .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), finishedWithFailure(message("apple 1")))); + .haveExactly(1, + event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // + .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), + finishedWithFailure(message("apple 1")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(RepeatableSourcesTestCase.class, methodName, - methodParameterTypes); + methodParameterTypes); } } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java index 1cbbdf13f1b..6d69ec23df0 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java @@ -1,3 +1,13 @@ +/* + * 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.provider; import static org.assertj.core.api.Assertions.assertThat; @@ -47,4 +57,4 @@ void shouldInvokeTemplateMethodForEachAnnotationProvided() { assertThat(arguments.get(1).get()[0]).isEqualTo(annotation2); } -} \ No newline at end of file +} From a194155138bd4cc64c776f25101e7151dd88c568 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 17:44:33 +0300 Subject: [PATCH 07/20] add more integration tests for each repeatable *..Source annotation --- .../ParameterizedTestIntegrationTests.java | 100 ++++++++++++++++-- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 704c811cb9c..c0aec28c8b4 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -84,6 +84,7 @@ import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.engine.JupiterTestEngine; +import org.junit.jupiter.params.ParameterizedTestIntegrationTests.RepeatableSourcesTestCase.Action; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; @@ -97,6 +98,7 @@ import org.junit.jupiter.params.provider.CsvFileSource; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; @@ -1073,6 +1075,16 @@ private EngineExecutionResults execute(String methodName, Class... methodPara @Nested class RepeatableSourcesIntegrationTests { + @Test + void executesWithRepeatableCsvFileSource() { + var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // + .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), + finishedWithFailure(message("apple 1")))); + } + @Test void executesWithRepeatableCsvSource() { var results = execute("testWithRepeatableCsvSource", String.class); @@ -1082,13 +1094,35 @@ void executesWithRepeatableCsvSource() { } @Test - void executesWithRepeatableCsvFileSource() { - var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); - results.allEvents().assertThatEvents() // - .haveExactly(1, - event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // - .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), - finishedWithFailure(message("apple 1")))); + void executesWithRepeatableMethodSource() { + var results = execute("testWithRepeatableMethodSource", String.class); + results.allEvents().assertThatEvents() + .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + .haveExactly(1, event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); + } + + @Test + void executesWithRepeatableEnumSource() { + var results = execute("testWithRepeatableEnumSource", Action.class); + results.allEvents().assertThatEvents() + .haveExactly(1, event(test(), displayName("[1] argument=FOO"), finishedWithFailure(message("FOO")))) // + .haveExactly(1, event(test(), displayName("[2] argument=BAR"), finishedWithFailure(message("BAR")))); + } + + @Test + void executesWithRepeatableValueSource() { + var results = execute("testWithRepeatableValueSource", String.class); + results.allEvents().assertThatEvents() + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } + + @Test + void executesWithRepeatableFieldSource() { + var results = execute("testWithRepeatableFieldSource", String.class); + results.allEvents().assertThatEvents() + .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + .haveExactly(1, event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { @@ -1934,6 +1968,13 @@ static Stream providerMethod() { static class RepeatableSourcesTestCase { + @ParameterizedTest + @CsvFileSource(resources = "two-column.csv") + @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") + void testWithRepeatableCsvFileSource(String column1, String column2) { + fail("%s %s".formatted(column1, column2)); + } + @ParameterizedTest @CsvSource({ "a" }) @CsvSource({ "b" }) @@ -1942,12 +1983,49 @@ void testWithRepeatableCsvSource(String argument) { } @ParameterizedTest - @CsvFileSource(resources = "two-column.csv") - @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") - void testWithRepeatableCsvFileSource(String column1, String column2) { - fail("%s %s".formatted(column1, column2)); + @EnumSource(SmartAction.class) + @EnumSource(QuickAction.class) + void testWithRepeatableEnumSource(Action argument) { + fail(argument.toString()); + } + + interface Action {} + + private enum SmartAction implements Action { FOO } + + private enum QuickAction implements Action { BAR } + + @ParameterizedTest + @MethodSource("someArgumentsMethodSource") + @MethodSource("otherArgumentsMethodSource") + void testWithRepeatableMethodSource(String argument) { + fail(argument); + } + + public static Stream someArgumentsMethodSource() { + return Stream.of(Arguments.of("some")); } + public static Stream otherArgumentsMethodSource() { + return Stream.of(Arguments.of("other")); + } + + @ParameterizedTest + @FieldSource("someArgumentsContainer") + @FieldSource("otherArgumentsContainer") + void testWithRepeatableFieldSource(String argument) { + fail(argument); + } + + static List someArgumentsContainer = List.of("some"); + static List otherArgumentsContainer = List.of("other"); + + @ParameterizedTest + @ValueSource(strings = "foo") + @ValueSource(strings = "bar") + void testWithRepeatableValueSource(String argument) { + fail(argument); + } } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { From af820171e4aa12485411d0f66c7b7b02cc2edace Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 17:48:02 +0300 Subject: [PATCH 08/20] spotless apply --- .../ParameterizedTestIntegrationTests.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index c0aec28c8b4..3d00589be0c 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1080,9 +1080,9 @@ void executesWithRepeatableCsvFileSource() { var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, - event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // + event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), - finishedWithFailure(message("apple 1")))); + finishedWithFailure(message("apple 1")))); } @Test @@ -1096,33 +1096,39 @@ void executesWithRepeatableCsvSource() { @Test void executesWithRepeatableMethodSource() { var results = execute("testWithRepeatableMethodSource", String.class); - results.allEvents().assertThatEvents() - .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // - .haveExactly(1, event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } @Test void executesWithRepeatableEnumSource() { var results = execute("testWithRepeatableEnumSource", Action.class); - results.allEvents().assertThatEvents() + results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=FOO"), finishedWithFailure(message("FOO")))) // - .haveExactly(1, event(test(), displayName("[2] argument=BAR"), finishedWithFailure(message("BAR")))); + .haveExactly(1, + event(test(), displayName("[2] argument=BAR"), finishedWithFailure(message("BAR")))); } @Test void executesWithRepeatableValueSource() { var results = execute("testWithRepeatableValueSource", String.class); - results.allEvents().assertThatEvents() + results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); } @Test void executesWithRepeatableFieldSource() { var results = execute("testWithRepeatableFieldSource", String.class); - results.allEvents().assertThatEvents() - .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // - .haveExactly(1, event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + .haveExactly(1, + event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { @@ -1989,11 +1995,16 @@ void testWithRepeatableEnumSource(Action argument) { fail(argument.toString()); } - interface Action {} + interface Action { + } - private enum SmartAction implements Action { FOO } + private enum SmartAction implements Action { + FOO + } - private enum QuickAction implements Action { BAR } + private enum QuickAction implements Action { + BAR + } @ParameterizedTest @MethodSource("someArgumentsMethodSource") From 2345777fe7988ebc414d2e0d46b2632639184d49 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 18:01:29 +0300 Subject: [PATCH 09/20] add some more integration tests for repeatable use cases --- .../ParameterizedTestIntegrationTests.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 3d00589be0c..3a03a906dc0 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1131,6 +1131,24 @@ void executesWithRepeatableFieldSource() { event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } + @Test + void executesWithSameRepeatableAnnotationMultipleTimes() { + var results = execute("testWithSameRepeatableAnnotationMultipleTimes", String.class); + results.allEvents().assertThatEvents() + .haveExactly(1, event(test(), started())) // + .haveExactly(1, event(test(), finishedWithFailure(message("foo")))); + } + + @Test + void executesWithDifferentRepeatableAnnotations() { + var results = execute("testWithDifferentRepeatableAnnotations", 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")))) // + .haveExactly(1, event(test(), displayName("[3] argument=c"), finishedWithFailure(message("c")))) // + .haveExactly(1, event(test(), displayName("[4] argument=d"), finishedWithFailure(message("d")))); + } + private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return ParameterizedTestIntegrationTests.this.execute(RepeatableSourcesTestCase.class, methodName, methodParameterTypes); @@ -2037,6 +2055,25 @@ void testWithRepeatableFieldSource(String argument) { void testWithRepeatableValueSource(String argument) { fail(argument); } + + @ParameterizedTest + @ValueSource(strings = "foo") + @ValueSource(strings = "foo") + @ValueSource(strings = "foo") + @ValueSource(strings = "foo") + @ValueSource(strings = "foo") + void testWithSameRepeatableAnnotationMultipleTimes(String argument) { + fail(argument); + } + + @ParameterizedTest + @ValueSource(strings = "a") + @ValueSource(strings = "b") + @CsvSource({ "c" }) + @CsvSource({ "d" }) + void testWithDifferentRepeatableAnnotations(String argument) { + fail(argument); + } } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { From 7d001f738bc5464e5acc723e47b92764b9827aba Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 18:03:04 +0300 Subject: [PATCH 10/20] spotless apply --- .../jupiter/params/ParameterizedTestIntegrationTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 3a03a906dc0..710d7871d9b 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1134,7 +1134,7 @@ void executesWithRepeatableFieldSource() { @Test void executesWithSameRepeatableAnnotationMultipleTimes() { var results = execute("testWithSameRepeatableAnnotationMultipleTimes", String.class); - results.allEvents().assertThatEvents() + results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), started())) // .haveExactly(1, event(test(), finishedWithFailure(message("foo")))); } @@ -1142,7 +1142,7 @@ void executesWithSameRepeatableAnnotationMultipleTimes() { @Test void executesWithDifferentRepeatableAnnotations() { var results = execute("testWithDifferentRepeatableAnnotations", String.class); - results.allEvents().assertThatEvents() + 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")))) // .haveExactly(1, event(test(), displayName("[3] argument=c"), finishedWithFailure(message("c")))) // From 364347a010c1566eaad3c4bc89d6b9c7bc688655 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 18:13:58 +0300 Subject: [PATCH 11/20] force stream consume for preconditions validation in unit tests --- .../params/provider/CsvFileArgumentsProviderTests.java | 5 +++-- .../params/provider/EnumArgumentsProviderTests.java | 10 +++++----- .../params/provider/ValueArgumentsProviderTests.java | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index e7c061a2ecd..afdce474914 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -98,7 +98,8 @@ void throwsExceptionIfBothDelimitersAreSimultaneouslySet() { .delimiterString(";")// .build(); - var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments(annotation, "foo")); + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(annotation, "foo").findAny()); assertThat(exception)// .hasMessageStartingWith("The delimiter and delimiterString attributes cannot be set simultaneously in")// @@ -435,7 +436,7 @@ void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber(@TempDir Path tempD .build(); var exception = assertThrows(PreconditionViolationException.class, // - () -> provideArguments(new CsvFileArgumentsProvider(), annotation)); + () -> provideArguments(new CsvFileArgumentsProvider(), annotation).findAny()); assertThat(exception)// .hasMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java index f63514b1be3..6a531208577 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java @@ -56,21 +56,21 @@ void provideAllEnumConstantsWithNamingAll() { @Test void duplicateConstantNameIsDetected() { Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FOO", "BAR", "FOO")); + () -> provideArguments(EnumWithTwoConstants.class, "FOO", "BAR", "FOO").findAny()); assertThat(exception).hasMessageContaining("Duplicate enum constant name(s) found"); } @Test void invalidConstantNameIsDetected() { Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FO0", "B4R")); + () -> provideArguments(EnumWithTwoConstants.class, "FO0", "B4R").findAny()); assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); } @Test void invalidPatternIsDetected() { Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, Mode.MATCH_ALL, "(", ")")); + () -> provideArguments(EnumWithTwoConstants.class, Mode.MATCH_ALL, "(", ")").findAny()); assertThat(exception).hasMessageContaining("Pattern compilation failed"); } @@ -90,7 +90,7 @@ void incorrectParameterTypeIsDetected() throws Exception { TestCase.class.getDeclaredMethod("methodWithIncorrectParameter", Object.class)); Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class)); + () -> provideArguments(NullEnum.class).findAny()); assertThat(exception).hasMessageStartingWith("First parameter must reference an Enum type"); } @@ -100,7 +100,7 @@ void methodsWithoutParametersAreDetected() throws Exception { TestCase.class.getDeclaredMethod("methodWithoutParameters")); Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class)); + () -> provideArguments(NullEnum.class).findAny()); assertThat(exception).hasMessageStartingWith("Test method must declare at least one parameter"); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java index 86b2221aec9..dea71ff0f72 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java @@ -29,7 +29,7 @@ class ValueArgumentsProviderTests { void multipleInputsAreNotAllowed() { var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments(new short[1], new byte[0], new int[1], new long[0], new float[0], new double[0], - new char[0], new boolean[0], new String[0], new Class[0])); + new char[0], new boolean[0], new String[0], new Class[0]).findAny()); assertThat(exception).hasMessageContaining( "Exactly one type of input must be provided in the @ValueSource annotation, but there were 2"); @@ -39,7 +39,7 @@ void multipleInputsAreNotAllowed() { void onlyEmptyInputsAreNotAllowed() { var exception = assertThrows(PreconditionViolationException.class, () -> provideArguments(new short[0], new byte[0], new int[0], new long[0], new float[0], new double[0], - new char[0], new boolean[0], new String[0], new Class[0])); + new char[0], new boolean[0], new String[0], new Class[0]).findAny()); assertThat(exception).hasMessageContaining( "Exactly one type of input must be provided in the @ValueSource annotation, but there were 0"); From 1b51e1c3a3f3293363e39bf529d63f5767ca3256 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 18:45:20 +0300 Subject: [PATCH 12/20] merge RepeatableAnnotationArgumentsProvider and AnnotationBasedArgumentsProvider into one provider --- .../AnnotationBasedArgumentsProvider.java | 8 ++- .../params/provider/CsvArgumentsProvider.java | 2 +- .../provider/CsvFileArgumentsProvider.java | 2 +- .../provider/EnumArgumentsProvider.java | 2 +- .../provider/FieldArgumentsProvider.java | 2 +- .../provider/MethodArgumentsProvider.java | 2 +- ...RepeatableAnnotationArgumentsProvider.java | 46 -------------- .../provider/ValueArgumentsProvider.java | 2 +- ...AnnotationBasedArgumentsProviderTests.java | 20 ++++++- ...atableAnnotationArgumentsProviderTest.java | 60 ------------------- .../AnnotationConsumerInitializerTests.java | 26 ++++---- 11 files changed, 40 insertions(+), 132 deletions(-) delete mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java delete mode 100644 junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java index 0b950ef71d8..f751b35e3c5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -39,17 +41,17 @@ public abstract class AnnotationBasedArgumentsProvider public AnnotationBasedArgumentsProvider() { } - private A annotation; + private final List annotations = new ArrayList<>(); @Override public final void accept(A annotation) { Preconditions.notNull(annotation, "annotation must not be null"); - this.annotation = annotation; + annotations.add(annotation); } @Override public final Stream provideArguments(ExtensionContext context) { - return provideArguments(context, this.annotation); + return annotations.stream().flatMap(annotation -> provideArguments(context, annotation)); } /** diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 2086208a588..a924cf63722 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -33,7 +33,7 @@ /** * @since 5.0 */ -class CsvArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { private static final String LINE_SEPARATOR = "\n"; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index de6c56cd0be..39d286408aa 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -41,7 +41,7 @@ /** * @since 5.0 */ -class CsvFileArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class CsvFileArgumentsProvider extends AnnotationBasedArgumentsProvider { private final InputStreamProvider inputStreamProvider; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java index d858533681f..0a525289e56 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java @@ -24,7 +24,7 @@ /** * @since 5.0 */ -class EnumArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class EnumArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, EnumSource enumSource) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java index aff6f90b384..c385d1bb3ba 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java @@ -35,7 +35,7 @@ * * @since 5.11 */ -class FieldArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class FieldArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, FieldSource fieldSource) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index b8ff50c149e..5915602fba9 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -35,7 +35,7 @@ /** * @since 5.0 */ -class MethodArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class MethodArgumentsProvider extends AnnotationBasedArgumentsProvider { private static final Predicate isFactoryMethod = // method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java deleted file mode 100644 index b109135f7f4..00000000000 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.provider; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.support.AnnotationConsumer; -import org.junit.platform.commons.util.Preconditions; - -@API(status = EXPERIMENTAL, since = "5.11") -public abstract class RepeatableAnnotationArgumentsProvider - implements ArgumentsProvider, AnnotationConsumer { - - public RepeatableAnnotationArgumentsProvider() { - } - - private final List annotations = new ArrayList<>(); - - @Override - public void accept(A annotation) { - Preconditions.notNull(annotation, "annotation must not be null"); - annotations.add(annotation); - } - - @Override - public Stream provideArguments(ExtensionContext context) { - return annotations.stream().flatMap(annotation -> provideArguments(context, annotation)); - } - - protected abstract Stream provideArguments(ExtensionContext context, A annotation); -} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java index 43ce3ec917d..e42d448a741 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java @@ -24,7 +24,7 @@ /** * @since 5.0 */ -class ValueArgumentsProvider extends RepeatableAnnotationArgumentsProvider { +class ValueArgumentsProvider extends AnnotationBasedArgumentsProvider { @Override protected Stream provideArguments(ExtensionContext context, ValueSource valueSource) { diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java index 25ffd4e1d48..af6e1eec06b 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; import static org.mockito.ArgumentMatchers.eq; @@ -30,7 +31,7 @@ class AnnotationBasedArgumentsProviderTests { private final AnnotationBasedArgumentsProvider annotationBasedArgumentsProvider = new AnnotationBasedArgumentsProvider<>() { @Override protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { - return Stream.empty(); + return Stream.of(Arguments.of(annotation)); } }; @@ -54,4 +55,21 @@ void shouldInvokeTemplateMethodWithTheAnnotationProvidedToAccept() { verify(spiedProvider, atMostOnce()).provideArguments(eq(extensionContext), eq(annotation)); } + @Test + @DisplayName("should invoke the provideArguments template method for every accepted annotation") + void shouldInvokeTemplateMethodForEachAnnotationProvided() { + var extensionContext = mock(ExtensionContext.class); + var foo = csvSource("foo"); + var bar = csvSource("bar"); + + annotationBasedArgumentsProvider.accept(foo); + annotationBasedArgumentsProvider.accept(bar); + + var arguments = annotationBasedArgumentsProvider.provideArguments(extensionContext).toList(); + + assertThat(arguments).hasSize(2); + assertThat(arguments.getFirst().get()[0]).isEqualTo(foo); + assertThat(arguments.get(1).get()[0]).isEqualTo(bar); + } + } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java deleted file mode 100644 index 6d69ec23df0..00000000000 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/RepeatableAnnotationArgumentsProviderTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.params.provider.MockCsvAnnotationBuilder.csvSource; -import static org.mockito.Mockito.mock; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.mockito.Spy; - -class RepeatableAnnotationArgumentsProviderTest { - - @Spy - private final RepeatableAnnotationArgumentsProvider repeatableAnnotationArgumentsProvider = new RepeatableAnnotationArgumentsProvider<>() { - - @Override - protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { - return Stream.of(Arguments.of(annotation)); - } - }; - - @Test - @DisplayName("should throw exception when null annotation is provided to accept method") - void shouldThrowExceptionWhenNullAnnotationIsProvidedToAccept() { - assertThatThrownBy(() -> repeatableAnnotationArgumentsProvider.accept(null)) // - .hasMessage("annotation must not be null"); - } - - @Test - @DisplayName("should invoke the provideArguments template method for every accepted annotation") - void shouldInvokeTemplateMethodForEachAnnotationProvided() { - var extensionContext = mock(ExtensionContext.class); - var annotation1 = csvSource("1"); - var annotation2 = csvSource("2"); - - repeatableAnnotationArgumentsProvider.accept(annotation1); - repeatableAnnotationArgumentsProvider.accept(annotation2); - - var arguments = repeatableAnnotationArgumentsProvider.provideArguments(extensionContext).toList(); - - assertThat(arguments).hasSize(2); - assertThat(arguments.getFirst().get()[0]).isEqualTo(annotation1); - assertThat(arguments.get(1).get()[0]).isEqualTo(annotation2); - } - -} diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java index ee699642f0e..b60da429e03 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java @@ -21,6 +21,8 @@ import static org.mockito.Mockito.when; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -32,7 +34,6 @@ import org.junit.jupiter.params.provider.AnnotationBasedArgumentsProvider; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.RepeatableAnnotationArgumentsProvider; import org.junit.platform.commons.JUnitException; @DisplayName("AnnotationConsumerInitializer") @@ -57,9 +58,11 @@ void shouldInitializeAnnotationBasedArgumentsProvider() throws NoSuchMethodExcep var method = SubjectClass.class.getDeclaredMethod("foo"); var initialisedAnnotationConsumer = initialize(method, instance); - initialisedAnnotationConsumer.provideArguments(mock()); + initialisedAnnotationConsumer.provideArguments(mock()).findAny(); - assertThat(initialisedAnnotationConsumer.annotation) // + assertThat(initialisedAnnotationConsumer.annotations) // + .hasSize(1) // + .element(0) // .isInstanceOfSatisfying(CsvSource.class, // source -> assertThat(source.value()).containsExactly("a", "b")); } @@ -99,8 +102,8 @@ void shouldThrowExceptionWhenParameterIsNotAnnotated() throws NoSuchMethodExcept } @Test - void shouldInitializeForEachAnnotationsWhenItIsRepeatable() throws NoSuchMethodException { - var instance = spy(new SomeRepeatableAnnotationArgumentsProvider()); + void shouldInitializeForEachAnnotations() throws NoSuchMethodException { + var instance = spy(new SomeAnnotationBasedArgumentsProvider()); var method = SubjectClass.class.getDeclaredMethod("repeatableAnnotation", String.class); initialize(method, instance); @@ -110,11 +113,11 @@ void shouldInitializeForEachAnnotationsWhenItIsRepeatable() throws NoSuchMethodE private static class SomeAnnotationBasedArgumentsProvider extends AnnotationBasedArgumentsProvider { - CsvSource annotation; + List annotations = new ArrayList<>(); @Override protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { - this.annotation = annotation; + annotations.add(annotation); return Stream.empty(); } } @@ -141,15 +144,6 @@ public void accept(CsvSource csvSource) { } } - private static class SomeRepeatableAnnotationArgumentsProvider - extends RepeatableAnnotationArgumentsProvider { - - @Override - protected Stream provideArguments(ExtensionContext context, CsvSource annotation) { - return Stream.empty(); - } - } - @SuppressWarnings("unused") private static class SubjectClass { From 468365955e451cd4e9f7becba91c760010a17624 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 19:06:34 +0300 Subject: [PATCH 13/20] add integration test for repeated @ArgumentsSource --- .../ParameterizedTestIntegrationTests.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 710d7871d9b..f1222b706bc 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1131,6 +1131,18 @@ void executesWithRepeatableFieldSource() { event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } + @Test + void executesWithRepeatableArgumentsSource() { + var results = execute("testWithRepeatableArgumentsSource", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))) // + .haveExactly(1, event(test(), displayName("[3] argument=foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[4] argument=bar"), finishedWithFailure(message("bar")))); + + } + @Test void executesWithSameRepeatableAnnotationMultipleTimes() { var results = execute("testWithSameRepeatableAnnotationMultipleTimes", String.class); @@ -2074,6 +2086,13 @@ void testWithSameRepeatableAnnotationMultipleTimes(String argument) { void testWithDifferentRepeatableAnnotations(String argument) { fail(argument); } + + @ParameterizedTest + @ArgumentsSource(TwoSingleStringArgumentsProvider.class) + @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) + void testWithRepeatableArgumentsSource(String argument) { + fail(argument); + } } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { From 0acecf1d4979c1c32d7dbb9b65a05b5a3012b607 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Sun, 21 Apr 2024 21:35:53 +0300 Subject: [PATCH 14/20] Add release notes and user guide section related to repeatable annotations --- .../release-notes-5.11.0-M1.adoc | 4 ++- .../asciidoc/user-guide/writing-tests.adoc | 28 +++++++++++++++++++ .../java/example/ParameterizedTestDemo.java | 18 ++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc index 332a1c2e8e8..640cb5f2f79 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc @@ -111,7 +111,9 @@ page in the Wiki. the supplied message is not the _expected message_ of the thrown exception. * Improved documentation for semantics of a disabled test regarding class-level lifecycle methods and callbacks. - +* Support `@..Source` annotations as repeatable for parameterized tests. See the + <<../user-guide/index.adoc#writing-tests-parameterized-repeatable-sources, User Guide>> + for more details. [[release-notes-5.11.0-M1-junit-vintage]] === JUnit Vintage diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 756722623a2..64fc734fb72 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1963,6 +1963,34 @@ If you wish to implement a custom `ArgumentsProvider` that also consumes an anno (like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`), you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class. +[[writing-tests-parameterized-repeatable-sources]] +===== Multiple sources using repeatable annotations +Repeatable annotations allows you a convenient way to provide multiple sources from +different providers whenever it fits. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=repeatable_annotations] +---- + +Following the above parameterized test, a test case will run for each argument: + +---- +[1] foo +[2] bar +---- + +Annotations that support repeatable capability: + +* `@ValueSource` +* `@EnumSource` +* `@MethodSource` +* `@FieldSource` +* `@CsvSource` +* `@CsvFileSource` +* `@ArgumentsSource` + + [[writing-tests-parameterized-tests-argument-conversion]] ==== Argument Conversion diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index 0ca33aec886..1b94e053519 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -542,4 +542,22 @@ static Stream namedArguments() { } // end::named_arguments[] // @formatter:on + + // tag::repeatable_annotations[] + @DisplayName("A parameterized test that makes use of repeatable annotations") + @ParameterizedTest + @MethodSource("someProvider") + @MethodSource("otherProvider") + void testWithRepeatedAnnotation(String argument) { + assertNotNull(argument); + } + + static Stream someProvider() { + return Stream.of("foo"); + } + + static Stream otherProvider() { + return Stream.of("bar"); + } + // end::repeatable_annotations[] } From 5a316dabd1cf3aff26f38bf3884b21d3b0818070 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Thu, 25 Apr 2024 17:33:14 +0300 Subject: [PATCH 15/20] Move release notes to the correct milestone --- .../docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc | 3 --- .../docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc index 640cb5f2f79..58b18e4f15e 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc @@ -111,9 +111,6 @@ page in the Wiki. the supplied message is not the _expected message_ of the thrown exception. * Improved documentation for semantics of a disabled test regarding class-level lifecycle methods and callbacks. -* Support `@..Source` annotations as repeatable for parameterized tests. See the - <<../user-guide/index.adoc#writing-tests-parameterized-repeatable-sources, User Guide>> - for more details. [[release-notes-5.11.0-M1-junit-vintage]] === JUnit Vintage diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc index 54c67ada5c9..3a3da0ba33e 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc @@ -45,8 +45,9 @@ JUnit repository on GitHub. [[release-notes-5.11.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ - +* Support `@..Source` annotations as repeatable for parameterized tests. See the +<<../user-guide/index.adoc#writing-tests-parameterized-repeatable-sources, User Guide>> +for more details. [[release-notes-5.11.0-M2-junit-vintage]] === JUnit Vintage From f899d5b3c4c9587fbc9a47fa21f42a9265f9fed9 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Thu, 25 Apr 2024 18:53:12 +0300 Subject: [PATCH 16/20] Add java docs for containers of repeatable annotations --- .../params/provider/CsvFileSources.java | 17 +++++++++++++++++ .../jupiter/params/provider/CsvSources.java | 17 +++++++++++++++++ .../jupiter/params/provider/EnumSources.java | 17 +++++++++++++++++ .../jupiter/params/provider/FieldSources.java | 17 +++++++++++++++++ .../jupiter/params/provider/MethodSources.java | 18 ++++++++++++++++++ .../jupiter/params/provider/ValueSources.java | 17 +++++++++++++++++ 6 files changed, 103 insertions(+) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java index 69a671eef55..bc6bf3503fc 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java @@ -20,10 +20,27 @@ import org.apiguardian.api.API; +/** + * {@code @CsvFileSources} is a simple container for one or more + * {@link CsvFileSource} annotations. + * + *

Note, however, that use of the {@code @CsvFileSources} container is completely + * optional since {@code @CsvFileSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see CsvFileSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface CsvFileSources { + + /** + * An array of one or more {@link CsvFileSource @CsvFileSource} + * annotations. + */ CsvFileSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java index afdcd801656..6c6951a75be 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java @@ -20,10 +20,27 @@ import org.apiguardian.api.API; +/** + * {@code @CsvSources} is a simple container for one or more + * {@link CsvSource} annotations. + * + *

Note, however, that use of the {@code @CsvSources} container is completely + * optional since {@code @CsvSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see CsvSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface CsvSources { + + /** + * An array of one or more {@link CsvSource @CsvSource} + * annotations. + */ CsvSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java index fbbd9afa434..22feb5aa46d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java @@ -20,10 +20,27 @@ import org.apiguardian.api.API; +/** + * {@code @EnumSources} is a simple container for one or more + * {@link EnumSource} annotations. + * + *

Note, however, that use of the {@code @EnumSources} container is completely + * optional since {@code @EnumSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see EnumSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface EnumSources { + + /** + * An array of one or more {@link EnumSource @EnumSource} + * annotations. + */ EnumSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java index 19e20067b40..b38b3908f70 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java @@ -20,10 +20,27 @@ import org.apiguardian.api.API; +/** + * {@code @FieldSources} is a simple container for one or more + * {@link FieldSource} annotations. + * + *

Note, however, that use of the {@code @FieldSources} container is completely + * optional since {@code @FieldSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see FieldSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface FieldSources { + + /** + * An array of one or more {@link FieldSource @FieldSource} + * annotations. + */ FieldSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java index 8f0792ade99..cef8f5b7b34 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java @@ -20,10 +20,28 @@ import org.apiguardian.api.API; + +/** + * {@code @MethodSources} is a simple container for one or more + * {@link MethodSource} annotations. + * + *

Note, however, that use of the {@code @MethodSources} container is completely + * optional since {@code @MethodSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see MethodSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface MethodSources { + + /** + * An array of one or more {@link MethodSource @MethodSource} + * annotations. + */ MethodSource[] value(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java index 00587e701b7..8db4dcc5b01 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java @@ -20,10 +20,27 @@ import org.apiguardian.api.API; +/** + * {@code @ValueSources} is a simple container for one or more + * {@link ValueSource} annotations. + * + *

Note, however, that use of the {@code @ValueSources} container is completely + * optional since {@code @ValueSource} is a {@linkplain java.lang.annotation.Repeatable + * repeatable} annotation. + * + * @since 5.11 + * @see ValueSource + * @see java.lang.annotation.Repeatable + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") public @interface ValueSources { + + /** + * An array of one or more {@link ValueSource @ValueSource} + * annotations. + */ ValueSource[] value(); } From f8d041dba477df734981e2d418ac05cd8e595952 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Thu, 25 Apr 2024 19:25:47 +0300 Subject: [PATCH 17/20] Improve user guide readability related to repeatable annotations --- .../src/docs/asciidoc/user-guide/writing-tests.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 64fc734fb72..d3212f3a1c7 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1965,8 +1965,8 @@ you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` clas [[writing-tests-parameterized-repeatable-sources]] ===== Multiple sources using repeatable annotations -Repeatable annotations allows you a convenient way to provide multiple sources from -different providers whenever it fits. +Repeatable annotations provide a convenient way to specify multiple sources from +different providers. [source,java,indent=0] ---- @@ -1980,7 +1980,7 @@ Following the above parameterized test, a test case will run for each argument: [2] bar ---- -Annotations that support repeatable capability: +The following annotations are repeatable: * `@ValueSource` * `@EnumSource` From 66454ab73cf436b8c3a56bc3cf66d5ad09a465d6 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Fri, 26 Apr 2024 09:27:51 +0300 Subject: [PATCH 18/20] Spotless apply for removing extra new line --- .../src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc | 1 + .../java/org/junit/jupiter/params/provider/MethodSources.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc index 58b18e4f15e..332a1c2e8e8 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc @@ -112,6 +112,7 @@ page in the Wiki. * Improved documentation for semantics of a disabled test regarding class-level lifecycle methods and callbacks. + [[release-notes-5.11.0-M1-junit-vintage]] === JUnit Vintage diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java index cef8f5b7b34..056453f2982 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java @@ -20,7 +20,6 @@ import org.apiguardian.api.API; - /** * {@code @MethodSources} is a simple container for one or more * {@link MethodSource} annotations. From 7a0f7f1716268de082f0d3de9b9526bca6cafef1 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Wed, 8 May 2024 19:01:11 +0300 Subject: [PATCH 19/20] Change API status to experimental for FieldSources enum --- .../java/org/junit/jupiter/params/provider/FieldSources.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java index b38b3908f70..0b46746db5e 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java @@ -10,7 +10,7 @@ package org.junit.jupiter.params.provider; -import static org.apiguardian.api.API.Status.STABLE; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -35,7 +35,7 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented -@API(status = STABLE, since = "5.11") +@API(status = EXPERIMENTAL, since = "5.11") public @interface FieldSources { /** From 37c7c0917224f044453be1811b9660806bb1e659 Mon Sep 17 00:00:00 2001 From: Madalin Giurca Date: Wed, 8 May 2024 19:24:08 +0300 Subject: [PATCH 20/20] Mention 'repeatable' capability in javadocs for *..Source annotations --- .../org/junit/jupiter/params/provider/CsvFileSource.java | 6 +++--- .../org/junit/jupiter/params/provider/CsvSource.java | 7 ++++--- .../org/junit/jupiter/params/provider/EnumSource.java | 4 ++-- .../org/junit/jupiter/params/provider/FieldSource.java | 9 +++++---- .../org/junit/jupiter/params/provider/MethodSource.java | 9 +++++---- .../org/junit/jupiter/params/provider/ValueSource.java | 4 ++-- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java index 83b4a41a599..1798dfc171b 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -22,9 +22,9 @@ import org.apiguardian.api.API; /** - * {@code @CsvFileSource} is an {@link ArgumentsSource} which is used to load - * comma-separated value (CSV) files from one or more classpath {@link #resources} - * or {@link #files}. + * {@code @CsvFileSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} which is used to load comma-separated value (CSV) + * files from one or more classpath {@link #resources} or {@link #files}. * *

The CSV records parsed from these resources and files will be provided as * arguments to the annotated {@code @ParameterizedTest} method. Note that the diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index 8a142e2bf97..ef09eea27ba 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -22,9 +22,10 @@ import org.apiguardian.api.API; /** - * {@code @CsvSource} is an {@link ArgumentsSource} which reads comma-separated - * values (CSV) from one or more CSV records supplied via the {@link #value} - * attribute or {@link #textBlock} attribute. + * {@code @CsvSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} which reads comma-separated values (CSV) from one + * or more CSV records supplied via the {@link #value} attribute or + * {@link #textBlock} attribute. * *

The supplied values will be provided as arguments to the annotated * {@code @ParameterizedTest} method. diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java index 51534e59065..3bf7e9b88e5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java @@ -30,8 +30,8 @@ import org.junit.platform.commons.util.Preconditions; /** - * {@code @EnumSource} is an {@link ArgumentsSource} for constants of - * an {@link Enum}. + * {@code @EnumSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} for constants of an {@link Enum}. * *

The enum constants will be provided as arguments to the annotated * {@code @ParameterizedTest} method. diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java index a74ed2aa810..77680a00b7d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java @@ -23,10 +23,11 @@ import org.junit.jupiter.params.ParameterizedTest; /** - * {@code @FieldSource} is an {@link ArgumentsSource} which provides access to - * values of {@linkplain #value() fields} of the class in which this annotation - * is declared or from static fields in external classes referenced by - * fully qualified field name. + * {@code @FieldSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} which provides access to values of + * {@linkplain #value() fields} of the class in which this annotation is declared + * or from static fields in external classes referenced by fully qualified + * field name. * *

Each field must be able to supply a stream of arguments, * and each set of "arguments" within the "stream" will be provided as the physical diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java index b0ea60cc66f..977e7555a5d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java @@ -23,10 +23,11 @@ import org.junit.jupiter.params.ParameterizedTest; /** - * {@code @MethodSource} is an {@link ArgumentsSource} which provides access - * to values returned from {@linkplain #value() factory methods} of the class in - * which this annotation is declared or from static factory methods in external - * classes referenced by fully qualified method name. + * {@code @MethodSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} which provides access to values returned from + * {@linkplain #value() factory methods} of the class in which this annotation + * is declared or from static factory methods in external classes referenced + * by fully qualified method name. * *

Each factory method must generate a stream of arguments, * and each set of "arguments" within the "stream" will be provided as the physical diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java index 30be90497eb..bc0ed303e93 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java @@ -22,8 +22,8 @@ import org.apiguardian.api.API; /** - * {@code @ValueSource} is an {@link ArgumentsSource} which provides access to - * an array of literal values. + * {@code @ValueSource} is a {@linkplain Repeatable repeatable} + * {@link ArgumentsSource} which provides access to an array of literal values. * *

Supported types include {@link #shorts}, {@link #bytes}, {@link #ints}, * {@link #longs}, {@link #floats}, {@link #doubles}, {@link #chars},