diff options
author | Jens Nyman <jnyman@google.com> | 2024-03-18 13:27:20 +0000 |
---|---|---|
committer | Jens Nyman <jnyman@google.com> | 2024-03-18 13:27:20 +0000 |
commit | 12066d29df68922d8c4a1a0c2c6128abc487340f (patch) | |
tree | fe5ede2fcb7ea251d8199d640ae9fc2523c7c6d5 | |
parent | 42fab13ba4f87b8556f9aca032f849e6c13b070e (diff) | |
download | TestParameterInjector-12066d29df68922d8c4a1a0c2c6128abc487340f.tar.gz |
Context: Support repeated annotations
6 files changed, 344 insertions, 58 deletions
diff --git a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java index f2a8c73..5586d7b 100644 --- a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java +++ b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java @@ -16,24 +16,86 @@ package com.google.testing.junit.testparameterinjector; import static com.google.common.collect.Iterables.getOnlyElement; +import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.base.Optional; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Proxy; import java.util.NoSuchElementException; /** A value class that contains extra information about the context of a field or parameter. */ final class GenericParameterContext { private final ImmutableList<Annotation> annotationsOnParameter; + + /** Same contract as #getAnnotations */ + private final Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>> + getAnnotationsFunction; + private final Class<?> testClass; - GenericParameterContext(ImmutableList<Annotation> annotationsOnParameter, Class<?> testClass) { + private GenericParameterContext( + ImmutableList<Annotation> annotationsOnParameter, + Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>> + getAnnotationsFunction, + Class<?> testClass) { this.annotationsOnParameter = annotationsOnParameter; + this.getAnnotationsFunction = getAnnotationsFunction; this.testClass = testClass; } + // Field.getAnnotationsByType() is not available on old Android SDKs. There is a fallback in that + // case in this method. + @SuppressWarnings("AndroidJdkLibsChecker") + static GenericParameterContext create(Field field, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(field.getAnnotations()), + /* getAnnotationsFunction= */ annotationType -> { + try { + return ImmutableList.copyOf(field.getAnnotationsByType(annotationType)); + } catch (NoSuchMethodError ignored) { + return getAnnotationsFallback( + ImmutableList.copyOf(field.getAnnotations()), annotationType); + } + }, + testClass); + } + + // Parameter is not available on old Android SDKs, and isn't desugared. That's why this method + // should only be called with a fallback. + @SuppressWarnings("AndroidJdkLibsChecker") + static GenericParameterContext create(Parameter parameter, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(parameter.getAnnotations()), + /* getAnnotationsFunction= */ annotationType -> + ImmutableList.copyOf(parameter.getAnnotationsByType(annotationType)), + testClass); + } + + static GenericParameterContext createWithRepeatableAnnotationsFallback( + Annotation[] annotationsOnParameter, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(annotationsOnParameter), + /* getAnnotationsFunction= */ annotationType -> + getAnnotationsFallback(ImmutableList.copyOf(annotationsOnParameter), annotationType), + testClass); + } + + static GenericParameterContext createWithoutParameterAnnotations(Class<?> testClass) { + return new GenericParameterContext( + /* annotationsOnParameter= */ ImmutableList.of(), + /* getAnnotationsFunction= */ annotationType -> + getAnnotationsFallback(ImmutableList.of(), annotationType), + testClass); + } + /** * Returns the only annotation with the given type on the field or parameter. * @@ -49,7 +111,15 @@ final class GenericParameterContext { .toList()); } - // TODO: b/317524353 - Add support for repeated annotations + /** + * Returns the annotations with the given type on the field or parameter. + * + * <p>Returns an empty list if this there is no annotation with the given type. + */ + @SuppressWarnings("unchecked") // Safe because of the getAnnotationsFunction contract + <A extends Annotation> ImmutableList<A> getAnnotations(Class<A> annotationType) { + return (ImmutableList<A>) getAnnotationsFunction.apply(annotationType); + } /** The class that contains the test that is currently being run. */ Class<?> testClass() { @@ -73,4 +143,49 @@ final class GenericParameterContext { .join(Joiner.on(',')), testClass().getSimpleName()); } + + private static ImmutableList<Annotation> getAnnotationsFallback( + ImmutableList<Annotation> annotationsOnParameter, + Class<? extends Annotation> annotationType) { + ImmutableList<Annotation> candidates = + FluentIterable.from(annotationsOnParameter) + .filter(annotation -> annotation.annotationType().equals(annotationType)) + .toList(); + if (candidates.isEmpty() && getContainerType(annotationType).isPresent()) { + ImmutableList<Annotation> containerAnnotations = + getAnnotationsFallback(annotationsOnParameter, getContainerType(annotationType).get()); + if (containerAnnotations.size() == 1) { + Annotation containerAnnotation = getOnlyElement(containerAnnotations); + try { + Method annotationValueMethod = + containerAnnotation.annotationType().getDeclaredMethod("value"); + annotationValueMethod.setAccessible(true); + return ImmutableList.copyOf( + (Annotation[]) + Proxy.getInvocationHandler(containerAnnotation) + .invoke(containerAnnotation, annotationValueMethod, null)); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + return ImmutableList.of(); + } else { + return candidates; + } + } + + private static Optional<Class<? extends Annotation>> getContainerType( + Class<? extends Annotation> annotationType) { + try { + Repeatable repeatable = annotationType.getAnnotation(Repeatable.class); + if (repeatable == null) { + return Optional.absent(); + } else { + return Optional.of(repeatable.value()); + } + } catch (NoClassDefFoundError ignored) { + // If @Repeatable does not exist, then there is no container type by definition + return Optional.absent(); + } + } } diff --git a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterAnnotationMethodProcessor.java b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterAnnotationMethodProcessor.java index a4bcb74..16b206a 100644 --- a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterAnnotationMethodProcessor.java +++ b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterAnnotationMethodProcessor.java @@ -272,36 +272,26 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso public static AnnotationWithMetadata withMetadata( Annotation annotation, - Annotation[] allAnnotations, Class<?> paramClass, String paramName, - Class<?> testClass) { + GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( - annotation, - Optional.of(paramClass), - Optional.of(paramName), - new GenericParameterContext(ImmutableList.copyOf(allAnnotations), testClass)); + annotation, Optional.of(paramClass), Optional.of(paramName), context); } public static AnnotationWithMetadata withMetadata( - Annotation annotation, - Annotation[] allAnnotations, - Class<?> paramClass, - Class<?> testClass) { + Annotation annotation, Class<?> paramClass, GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( - annotation, - Optional.of(paramClass), - Optional.absent(), - new GenericParameterContext(ImmutableList.copyOf(allAnnotations), testClass)); + annotation, Optional.of(paramClass), Optional.absent(), context); } public static AnnotationWithMetadata withoutMetadata( - Annotation annotation, Class<?> testClass) { + Annotation annotation, GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( annotation, /* paramClass= */ Optional.absent(), /* paramName= */ Optional.absent(), - new GenericParameterContext(/* annotationsOnParameter= */ ImmutableList.of(), testClass)); + context); } // Prevent anyone relying on equals() and hashCode() so that it remains possible to add fields @@ -947,7 +937,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation != null) { return ImmutableList.of( TestParameterValueHolder.create( - AnnotationWithMetadata.withoutMetadata(annotation, testClass), origin)); + AnnotationWithMetadata.withoutMetadata( + annotation, + GenericParameterContext.createWithoutParameterAnnotations(testClass)), + origin)); } } else if (origin == Origin.METHOD_PARAMETER) { @@ -961,7 +954,8 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso return ImmutableList.of( TestParameterValueHolder.create( AnnotationWithMetadata.withoutMetadata( - method.getAnnotation(annotationType), testClass), + method.getAnnotation(annotationType), + GenericParameterContext.createWithoutParameterAnnotations(testClass)), origin)); } } else if (origin == Origin.FIELD) { @@ -977,10 +971,9 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso annotation -> AnnotationWithMetadata.withMetadata( annotation, - field.getAnnotations(), field.getType(), field.getName(), - testClass))) + GenericParameterContext.create(field, testClass)))) .toList()); if (!annotations.isEmpty()) { return toTestParameterValueList(annotations, origin); @@ -990,7 +983,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation != null) { return ImmutableList.of( TestParameterValueHolder.create( - AnnotationWithMetadata.withoutMetadata(annotation, testClass), origin)); + AnnotationWithMetadata.withoutMetadata( + annotation, + GenericParameterContext.createWithoutParameterAnnotations(testClass)), + origin)); } } return ImmutableList.of(); @@ -1050,15 +1046,13 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso : parameter.isNamePresent() ? AnnotationWithMetadata.withMetadata( annotation, - /* allAnnotations= */ parameter.getAnnotations(), parameter.getType(), parameter.getName(), - testClass) + GenericParameterContext.create(parameter, testClass)) : AnnotationWithMetadata.withMetadata( annotation, - /* allAnnotations= */ parameter.getAnnotations(), parameter.getType(), - testClass); + GenericParameterContext.create(parameter, testClass)); }) .filter(Objects::nonNull) .toList(); @@ -1077,7 +1071,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation.annotationType().equals(annotationType)) { resultBuilder.add( AnnotationWithMetadata.withMetadata( - annotation, /* allAnnotations= */ annotations[i], parameterTypes[i], testClass)); + annotation, + parameterTypes[i], + GenericParameterContext.createWithRepeatableAnnotationsFallback( + annotations[i], testClass))); } } } diff --git a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterValuesProvider.java b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterValuesProvider.java index a8fd19d..ccdb18b 100644 --- a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterValuesProvider.java +++ b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/TestParameterValuesProvider.java @@ -101,7 +101,38 @@ public abstract class TestParameterValuesProvider return delegate.getAnnotation(annotationType); } - // TODO: b/317524353 - Add support for repeated annotations + /** + * Returns the only annotation with the given type on the field or parameter that was annotated + * with @TestParameter. + * + * <p>For example, if the test code is as follows: + * + * <pre> + * {@literal @}Test + * public void myTest_success( + * {@literal @}CustomAnnotation(123) + * {@literal @}CustomAnnotation(456) + * {@literal @}TestParameter(valuesProvider=MyProvider.class) + * Foo foo) { + * ... + * } + * </pre> + * + * then {@code context.getOtherAnnotations(CustomAnnotation.class)} will return the annotation + * with 123 and 456. + * + * <p>Returns an empty list if this there is no annotation with the given type. + * + * @throws IllegalArgumentException if the argument it TestParameter.class because it is already + * handled by the TestParameterInjector framework. + */ + public <A extends Annotation> ImmutableList<A> getOtherAnnotations(Class<A> annotationType) { + checkArgument( + !TestParameter.class.equals(annotationType), + "Getting the @TestParameter annotating the field or parameter is not allowed because" + + " it is already handled by the TestParameterInjector framework."); + return delegate.getAnnotations(annotationType); + } /** * The class that contains the test that is currently being run. diff --git a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/GenericParameterContext.java b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/GenericParameterContext.java index 15ac68b..02e5367 100644 --- a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/GenericParameterContext.java +++ b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/GenericParameterContext.java @@ -16,24 +16,86 @@ package com.google.testing.junit.testparameterinjector.junit5; import static com.google.common.collect.Iterables.getOnlyElement; +import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.base.Optional; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Proxy; import java.util.NoSuchElementException; /** A value class that contains extra information about the context of a field or parameter. */ final class GenericParameterContext { private final ImmutableList<Annotation> annotationsOnParameter; + + /** Same contract as #getAnnotations */ + private final Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>> + getAnnotationsFunction; + private final Class<?> testClass; - GenericParameterContext(ImmutableList<Annotation> annotationsOnParameter, Class<?> testClass) { + private GenericParameterContext( + ImmutableList<Annotation> annotationsOnParameter, + Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>> + getAnnotationsFunction, + Class<?> testClass) { this.annotationsOnParameter = annotationsOnParameter; + this.getAnnotationsFunction = getAnnotationsFunction; this.testClass = testClass; } + // Field.getAnnotationsByType() is not available on old Android SDKs. There is a fallback in that + // case in this method. + @SuppressWarnings("AndroidJdkLibsChecker") + static GenericParameterContext create(Field field, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(field.getAnnotations()), + /* getAnnotationsFunction= */ annotationType -> { + try { + return ImmutableList.copyOf(field.getAnnotationsByType(annotationType)); + } catch (NoSuchMethodError ignored) { + return getAnnotationsFallback( + ImmutableList.copyOf(field.getAnnotations()), annotationType); + } + }, + testClass); + } + + // Parameter is not available on old Android SDKs, and isn't desugared. That's why this method + // should only be called with a fallback. + @SuppressWarnings("AndroidJdkLibsChecker") + static GenericParameterContext create(Parameter parameter, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(parameter.getAnnotations()), + /* getAnnotationsFunction= */ annotationType -> + ImmutableList.copyOf(parameter.getAnnotationsByType(annotationType)), + testClass); + } + + static GenericParameterContext createWithRepeatableAnnotationsFallback( + Annotation[] annotationsOnParameter, Class<?> testClass) { + return new GenericParameterContext( + ImmutableList.copyOf(annotationsOnParameter), + /* getAnnotationsFunction= */ annotationType -> + getAnnotationsFallback(ImmutableList.copyOf(annotationsOnParameter), annotationType), + testClass); + } + + static GenericParameterContext createWithoutParameterAnnotations(Class<?> testClass) { + return new GenericParameterContext( + /* annotationsOnParameter= */ ImmutableList.of(), + /* getAnnotationsFunction= */ annotationType -> + getAnnotationsFallback(ImmutableList.of(), annotationType), + testClass); + } + /** * Returns the only annotation with the given type on the field or parameter. * @@ -49,7 +111,15 @@ final class GenericParameterContext { .toList()); } - // TODO: b/317524353 - Add support for repeated annotations + /** + * Returns the annotations with the given type on the field or parameter. + * + * <p>Returns an empty list if this there is no annotation with the given type. + */ + @SuppressWarnings("unchecked") // Safe because of the getAnnotationsFunction contract + <A extends Annotation> ImmutableList<A> getAnnotations(Class<A> annotationType) { + return (ImmutableList<A>) getAnnotationsFunction.apply(annotationType); + } /** The class that contains the test that is currently being run. */ Class<?> testClass() { @@ -73,4 +143,49 @@ final class GenericParameterContext { .join(Joiner.on(',')), testClass().getSimpleName()); } + + private static ImmutableList<Annotation> getAnnotationsFallback( + ImmutableList<Annotation> annotationsOnParameter, + Class<? extends Annotation> annotationType) { + ImmutableList<Annotation> candidates = + FluentIterable.from(annotationsOnParameter) + .filter(annotation -> annotation.annotationType().equals(annotationType)) + .toList(); + if (candidates.isEmpty() && getContainerType(annotationType).isPresent()) { + ImmutableList<Annotation> containerAnnotations = + getAnnotationsFallback(annotationsOnParameter, getContainerType(annotationType).get()); + if (containerAnnotations.size() == 1) { + Annotation containerAnnotation = getOnlyElement(containerAnnotations); + try { + Method annotationValueMethod = + containerAnnotation.annotationType().getDeclaredMethod("value"); + annotationValueMethod.setAccessible(true); + return ImmutableList.copyOf( + (Annotation[]) + Proxy.getInvocationHandler(containerAnnotation) + .invoke(containerAnnotation, annotationValueMethod, null)); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + return ImmutableList.of(); + } else { + return candidates; + } + } + + private static Optional<Class<? extends Annotation>> getContainerType( + Class<? extends Annotation> annotationType) { + try { + Repeatable repeatable = annotationType.getAnnotation(Repeatable.class); + if (repeatable == null) { + return Optional.absent(); + } else { + return Optional.of(repeatable.value()); + } + } catch (NoClassDefFoundError ignored) { + // If @Repeatable does not exist, then there is no container type by definition + return Optional.absent(); + } + } } diff --git a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterAnnotationMethodProcessor.java b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterAnnotationMethodProcessor.java index 3024ffb..7fd2336 100644 --- a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterAnnotationMethodProcessor.java +++ b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterAnnotationMethodProcessor.java @@ -272,36 +272,26 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso public static AnnotationWithMetadata withMetadata( Annotation annotation, - Annotation[] allAnnotations, Class<?> paramClass, String paramName, - Class<?> testClass) { + GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( - annotation, - Optional.of(paramClass), - Optional.of(paramName), - new GenericParameterContext(ImmutableList.copyOf(allAnnotations), testClass)); + annotation, Optional.of(paramClass), Optional.of(paramName), context); } public static AnnotationWithMetadata withMetadata( - Annotation annotation, - Annotation[] allAnnotations, - Class<?> paramClass, - Class<?> testClass) { + Annotation annotation, Class<?> paramClass, GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( - annotation, - Optional.of(paramClass), - Optional.absent(), - new GenericParameterContext(ImmutableList.copyOf(allAnnotations), testClass)); + annotation, Optional.of(paramClass), Optional.absent(), context); } public static AnnotationWithMetadata withoutMetadata( - Annotation annotation, Class<?> testClass) { + Annotation annotation, GenericParameterContext context) { return new AutoValue_TestParameterAnnotationMethodProcessor_AnnotationWithMetadata( annotation, /* paramClass= */ Optional.absent(), /* paramName= */ Optional.absent(), - new GenericParameterContext(/* annotationsOnParameter= */ ImmutableList.of(), testClass)); + context); } // Prevent anyone relying on equals() and hashCode() so that it remains possible to add fields @@ -947,7 +937,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation != null) { return ImmutableList.of( TestParameterValueHolder.create( - AnnotationWithMetadata.withoutMetadata(annotation, testClass), origin)); + AnnotationWithMetadata.withoutMetadata( + annotation, + GenericParameterContext.createWithoutParameterAnnotations(testClass)), + origin)); } } else if (origin == Origin.METHOD_PARAMETER) { @@ -961,7 +954,8 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso return ImmutableList.of( TestParameterValueHolder.create( AnnotationWithMetadata.withoutMetadata( - method.getAnnotation(annotationType), testClass), + method.getAnnotation(annotationType), + GenericParameterContext.createWithoutParameterAnnotations(testClass)), origin)); } } else if (origin == Origin.FIELD) { @@ -977,10 +971,9 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso annotation -> AnnotationWithMetadata.withMetadata( annotation, - field.getAnnotations(), field.getType(), field.getName(), - testClass))) + GenericParameterContext.create(field, testClass)))) .toList()); if (!annotations.isEmpty()) { return toTestParameterValueList(annotations, origin); @@ -990,7 +983,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation != null) { return ImmutableList.of( TestParameterValueHolder.create( - AnnotationWithMetadata.withoutMetadata(annotation, testClass), origin)); + AnnotationWithMetadata.withoutMetadata( + annotation, + GenericParameterContext.createWithoutParameterAnnotations(testClass)), + origin)); } } return ImmutableList.of(); @@ -1050,15 +1046,13 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso : parameter.isNamePresent() ? AnnotationWithMetadata.withMetadata( annotation, - /* allAnnotations= */ parameter.getAnnotations(), parameter.getType(), parameter.getName(), - testClass) + GenericParameterContext.create(parameter, testClass)) : AnnotationWithMetadata.withMetadata( annotation, - /* allAnnotations= */ parameter.getAnnotations(), parameter.getType(), - testClass); + GenericParameterContext.create(parameter, testClass)); }) .filter(Objects::nonNull) .toList(); @@ -1077,7 +1071,10 @@ final class TestParameterAnnotationMethodProcessor implements TestMethodProcesso if (annotation.annotationType().equals(annotationType)) { resultBuilder.add( AnnotationWithMetadata.withMetadata( - annotation, /* allAnnotations= */ annotations[i], parameterTypes[i], testClass)); + annotation, + parameterTypes[i], + GenericParameterContext.createWithRepeatableAnnotationsFallback( + annotations[i], testClass))); } } } diff --git a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterValuesProvider.java b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterValuesProvider.java index 603f24d..29f945f 100644 --- a/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterValuesProvider.java +++ b/junit5/src/main/java/com/google/testing/junit/testparameterinjector/junit5/TestParameterValuesProvider.java @@ -101,7 +101,38 @@ public abstract class TestParameterValuesProvider return delegate.getAnnotation(annotationType); } - // TODO: b/317524353 - Add support for repeated annotations + /** + * Returns the only annotation with the given type on the field or parameter that was annotated + * with @TestParameter. + * + * <p>For example, if the test code is as follows: + * + * <pre> + * {@literal @}Test + * public void myTest_success( + * {@literal @}CustomAnnotation(123) + * {@literal @}CustomAnnotation(456) + * {@literal @}TestParameter(valuesProvider=MyProvider.class) + * Foo foo) { + * ... + * } + * </pre> + * + * then {@code context.getOtherAnnotations(CustomAnnotation.class)} will return the annotation + * with 123 and 456. + * + * <p>Returns an empty list if this there is no annotation with the given type. + * + * @throws IllegalArgumentException if the argument it TestParameter.class because it is already + * handled by the TestParameterInjector framework. + */ + public <A extends Annotation> ImmutableList<A> getOtherAnnotations(Class<A> annotationType) { + checkArgument( + !TestParameter.class.equals(annotationType), + "Getting the @TestParameter annotating the field or parameter is not allowed because" + + " it is already handled by the TestParameterInjector framework."); + return delegate.getAnnotations(annotationType); + } /** * The class that contains the test that is currently being run. |