diff --git a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java new file mode 100644 index 00000000000..4fd375f8c31 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java @@ -0,0 +1,639 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.util; + +import java.lang.annotation.Annotation; +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.junit.jupiter.api.Test; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; +import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes; + +/** + * Unit tests for {@link MetaAnnotationUtils}. + * + * @author Sam Brannen + * @since 4.0 + * @see OverriddenMetaAnnotationAttributesTests + */ +@SuppressWarnings("deprecation") +class MetaAnnotationUtilsTests { + + private void assertAtComponentOnComposedAnnotation( + Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotation(rootDeclaringClass, rootDeclaringClass, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotation( + Class startClass, Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotation(startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotation(Class startClass, Class rootDeclaringClass, + Class declaringClass, String name, Class composedAnnotationType) { + + AnnotationDescriptor descriptor = findAnnotationDescriptor(startClass, Component.class); + assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(Component.class); + assertThat(descriptor.getAnnotation().value()).as("component name").isEqualTo(name); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + Class startClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, name, composedAnnotationType); + } + + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, + Class rootDeclaringClass, String name, Class composedAnnotationType) { + + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, rootDeclaringClass, composedAnnotationType, name, composedAnnotationType); + } + + @SuppressWarnings("unchecked") + private void assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(Class startClass, + Class rootDeclaringClass, Class declaringClass, String name, + Class composedAnnotationType) { + + Class annotationType = Component.class; + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + startClass, Service.class, annotationType, Order.class, Transactional.class); + + assertThat(descriptor).as("UntypedAnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(rootDeclaringClass); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(declaringClass); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(annotationType); + assertThat(((Component) descriptor.getAnnotation()).value()).as("component name").isEqualTo(name); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(composedAnnotationType); + } + + @Test + void findAnnotationDescriptorWithNoAnnotationPresent() { + assertThat(findAnnotationDescriptor(NonAnnotatedInterface.class, Transactional.class)).isNull(); + assertThat(findAnnotationDescriptor(NonAnnotatedClass.class, Transactional.class)).isNull(); + } + + @Test + void findAnnotationDescriptorWithInheritedAnnotationOnClass() { + // Note: @Transactional is inherited + assertThat(findAnnotationDescriptor(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + assertThat(findAnnotationDescriptor(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + } + + @Test + void findAnnotationDescriptorWithInheritedAnnotationOnInterface() { + // Note: @Transactional is inherited + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + + AnnotationDescriptor descriptor = + findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + void findAnnotationDescriptorForNonInheritedAnnotationOnClass() { + // Note: @Order is not inherited. + assertThat(findAnnotationDescriptor(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + assertThat(findAnnotationDescriptor(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + } + + @Test + void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() { + // Note: @Order is not inherited. + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); + + AnnotationDescriptor descriptor = + findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + void findAnnotationDescriptorWithMetaComponentAnnotation() { + assertAtComponentOnComposedAnnotation(HasMetaComponentAnnotation.class, "meta1", Meta1.class); + } + + @Test + void findAnnotationDescriptorWithLocalAndMetaComponentAnnotation() { + Class annotationType = Component.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor( + HasLocalAndMetaComponentAnnotation.class, annotationType); + + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(descriptor.getComposedAnnotation()).isNull(); + assertThat(descriptor.getComposedAnnotationType()).isNull(); + } + + @Test + void findAnnotationDescriptorForInterfaceWithMetaAnnotation() { + assertAtComponentOnComposedAnnotation(InterfaceWithMetaAnnotation.class, "meta1", Meta1.class); + } + + @Test + void findAnnotationDescriptorForClassWithMetaAnnotatedInterface() { + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); + AnnotationDescriptor descriptor = + findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); + } + + @Test + void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndAnnotatedSuperclass() { + AnnotationDescriptor descriptor = findAnnotationDescriptor( + MetaAnnotatedAndSuperAnnotatedContextConfigClass.class, ContextConfiguration.class); + + assertThat(descriptor).as("AnnotationDescriptor should not be null").isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).as("rootDeclaringClass").isEqualTo(MetaAnnotatedAndSuperAnnotatedContextConfigClass.class); + assertThat(descriptor.getDeclaringClass()).as("declaringClass").isEqualTo(MetaConfig.class); + assertThat(descriptor.getAnnotationType()).as("annotationType").isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).as("composedAnnotation should not be null").isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).as("composedAnnotationType").isEqualTo(MetaConfig.class); + + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).as("configured classes").isEqualTo(new Class[] {String.class}); + } + + @Test + void findAnnotationDescriptorForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); + } + + @Test + void findAnnotationDescriptorForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotation(SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, + ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaMetaAnnotatedClass() { + Class startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaMetaMetaAnnotatedClass() { + Class startClass = MetaMetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotation(startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnAnnotatedClassWithMissingTargetMetaAnnotation() { + // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component + AnnotationDescriptor descriptor = findAnnotationDescriptor( + InheritedAnnotationClass.class, Component.class); + assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + AnnotationDescriptor descriptor = findAnnotationDescriptor( + MetaCycleAnnotatedClass.class, Component.class); + assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); + } + + // ------------------------------------------------------------------------- + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithNoAnnotationPresent() { + assertThat(findAnnotationDescriptorForTypes(NonAnnotatedInterface.class, Transactional.class, Component.class)).isNull(); + assertThat(findAnnotationDescriptorForTypes(NonAnnotatedClass.class, Transactional.class, Order.class)).isNull(); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithInheritedAnnotationOnClass() { + // Note: @Transactional is inherited + assertThat(findAnnotationDescriptorForTypes(InheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + assertThat(findAnnotationDescriptorForTypes(SubInheritedAnnotationClass.class, Transactional.class).getRootDeclaringClass()).isEqualTo(InheritedAnnotationClass.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithInheritedAnnotationOnInterface() { + // Note: @Transactional is inherited + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubSubInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(InheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnClass() { + // Note: @Order is not inherited. + assertThat(findAnnotationDescriptorForTypes(NonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + assertThat(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationClass.class, Order.class).getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationClass.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() { + // Note: @Order is not inherited. + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); + + UntypedAnnotationDescriptor descriptor = + findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + + descriptor = findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(SubNonInheritedAnnotationInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(NonInheritedAnnotationInterface.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithLocalAndMetaComponentAnnotation() { + Class annotationType = Component.class; + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + HasLocalAndMetaComponentAnnotation.class, Transactional.class, annotationType, Order.class); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(HasLocalAndMetaComponentAnnotation.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(descriptor.getComposedAnnotation()).isNull(); + assertThat(descriptor.getComposedAnnotationType()).isNull(); + } + + @Test + void findAnnotationDescriptorForTypesWithMetaComponentAnnotation() { + Class startClass = HasMetaComponentAnnotation.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithMetaAnnotationWithDefaultAttributes() { + Class startClass = MetaConfigWithDefaultAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(startClass, + Service.class, ContextConfiguration.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaConfig.DevConfig.class, MetaConfig.ProductionConfig.class}); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesWithMetaAnnotationWithOverriddenAttributes() { + Class startClass = MetaConfigWithOverriddenAttributesTestCase.class; + Class annotationType = ContextConfiguration.class; + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + startClass, Service.class, ContextConfiguration.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(startClass); + assertThat(descriptor.getAnnotationType()).isEqualTo(annotationType); + assertThat(((ContextConfiguration) descriptor.getAnnotation()).value()).isEqualTo(new Class[] {}); + assertThat(descriptor.getAnnotationAttributes().getClassArray("classes")).isEqualTo(new Class[] {MetaAnnotationUtilsTests.class}); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaConfig.class); + } + + @Test + void findAnnotationDescriptorForTypesForInterfaceWithMetaAnnotation() { + Class startClass = InterfaceWithMetaAnnotation.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta1", Meta1.class); + } + + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesForClassWithMetaAnnotatedInterface() { + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, Component.class); + + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + ClassWithMetaAnnotatedInterface.class, Service.class, Component.class, Order.class, Transactional.class); + + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(ClassWithMetaAnnotatedInterface.class); + assertThat(descriptor.getDeclaringClass()).isEqualTo(Meta1.class); + assertThat(descriptor.getAnnotation()).isEqualTo(rawAnnotation); + assertThat(descriptor.getComposedAnnotation().annotationType()).isEqualTo(Meta1.class); + } + + @Test + void findAnnotationDescriptorForTypesForClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + Class startClass = ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes(startClass, "meta2", Meta2.class); + } + + @Test + void findAnnotationDescriptorForTypesForSubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface() { + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, + ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, "meta2", Meta2.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorForTypesOnMetaMetaAnnotatedClass() { + Class startClass = MetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, Meta2.class, "meta2", MetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + void findAnnotationDescriptorForTypesOnMetaMetaMetaAnnotatedClass() { + Class startClass = MetaMetaMetaAnnotatedClass.class; + assertAtComponentOnComposedAnnotationForMultipleCandidateTypes( + startClass, startClass, Meta2.class, "meta2", MetaMetaMeta.class); + } + + /** + * @since 4.0.3 + */ + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesOnAnnotatedClassWithMissingTargetMetaAnnotation() { + // InheritedAnnotationClass is NOT annotated or meta-annotated with @Component, + // @Service, or @Order, but it is annotated with @Transactional. + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + InheritedAnnotationClass.class, Service.class, Component.class, Order.class); + assertThat(descriptor).as("Should not find @Component on InheritedAnnotationClass").isNull(); + } + + /** + * @since 4.0.3 + */ + @Test + @SuppressWarnings("unchecked") + void findAnnotationDescriptorForTypesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( + MetaCycleAnnotatedClass.class, Service.class, Component.class, Order.class); + assertThat(descriptor).as("Should not find @Component on MetaCycleAnnotatedClass").isNull(); + } + + + // ------------------------------------------------------------------------- + + @Component(value = "meta1") + @Order + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + static @interface Meta1 { + } + + @Component(value = "meta2") + @Transactional + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + static @interface Meta2 { + } + + @Meta2 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + @interface MetaMeta { + } + + @MetaMeta + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + @interface MetaMetaMeta { + } + + @MetaCycle3 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented + @interface MetaCycle1 { + } + + @MetaCycle1 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented + @interface MetaCycle2 { + } + + @MetaCycle2 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + @interface MetaCycle3 { + } + + @ContextConfiguration + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + static @interface MetaConfig { + + static class DevConfig { + } + + static class ProductionConfig { + } + + + Class[] classes() default { DevConfig.class, ProductionConfig.class }; + } + + // ------------------------------------------------------------------------- + + @Meta1 + static class HasMetaComponentAnnotation { + } + + @Meta1 + @Component(value = "local") + @Meta2 + static class HasLocalAndMetaComponentAnnotation { + } + + @Meta1 + static interface InterfaceWithMetaAnnotation { + } + + static class ClassWithMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { + } + + @Meta2 + static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { + } + + static class SubClassWithLocalMetaAnnotationAndMetaAnnotatedInterface extends + ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface { + } + + @MetaMeta + static class MetaMetaAnnotatedClass { + } + + @MetaMetaMeta + static class MetaMetaMetaAnnotatedClass { + } + + @MetaCycle3 + static class MetaCycleAnnotatedClass { + } + + @MetaConfig + class MetaConfigWithDefaultAttributesTestCase { + } + + @MetaConfig(classes = MetaAnnotationUtilsTests.class) + class MetaConfigWithOverriddenAttributesTestCase { + } + + // ------------------------------------------------------------------------- + + @Transactional + static interface InheritedAnnotationInterface { + } + + static interface SubInheritedAnnotationInterface extends InheritedAnnotationInterface { + } + + static interface SubSubInheritedAnnotationInterface extends SubInheritedAnnotationInterface { + } + + @Order + static interface NonInheritedAnnotationInterface { + } + + static interface SubNonInheritedAnnotationInterface extends NonInheritedAnnotationInterface { + } + + static class NonAnnotatedClass { + } + + static interface NonAnnotatedInterface { + } + + @Transactional + static class InheritedAnnotationClass { + } + + static class SubInheritedAnnotationClass extends InheritedAnnotationClass { + } + + @Order + static class NonInheritedAnnotationClass { + } + + static class SubNonInheritedAnnotationClass extends NonInheritedAnnotationClass { + } + + @ContextConfiguration(classes = Number.class) + static class AnnotatedContextConfigClass { + } + + @MetaConfig(classes = String.class) + static class MetaAnnotatedAndSuperAnnotatedContextConfigClass extends AnnotatedContextConfigClass { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java new file mode 100644 index 00000000000..e95d8a83d31 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.util; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; + +/** + * Unit tests for {@link MetaAnnotationUtils} that verify support for overridden + * meta-annotation attributes. + * + *

See SPR-10181. + * + * @author Sam Brannen + * @since 4.0 + * @see MetaAnnotationUtilsTests + */ +@SuppressWarnings("deprecation") +class OverriddenMetaAnnotationAttributesTests { + + @Test + void contextConfigurationValue() throws Exception { + Class declaringClass = MetaValueConfigTestCase.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(declaringClass); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaValueConfig.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaValueConfig.class); + + // direct access to annotation value: + assertThat(descriptor.getAnnotation().value()).isEqualTo(new String[] { "foo.xml" }); + } + + @Test + void overriddenContextConfigurationValue() throws Exception { + Class declaringClass = OverriddenMetaValueConfigTestCase.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(declaringClass); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaValueConfig.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaValueConfig.class); + + // direct access to annotation value: + assertThat(descriptor.getAnnotation().value()).isEqualTo(new String[] { "foo.xml" }); + + // overridden attribute: + AnnotationAttributes attributes = descriptor.getAnnotationAttributes(); + + // NOTE: we would like to be able to override the 'value' attribute; however, + // Spring currently does not allow overrides for the 'value' attribute. + // See SPR-11393 for related discussions. + assertThat(attributes.getStringArray("value")).isEqualTo(new String[] { "foo.xml" }); + } + + @Test + void contextConfigurationLocationsAndInheritLocations() throws Exception { + Class declaringClass = MetaLocationsConfigTestCase.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(declaringClass); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaLocationsConfig.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaLocationsConfig.class); + + // direct access to annotation attributes: + assertThat(descriptor.getAnnotation().locations()).isEqualTo(new String[] { "foo.xml" }); + assertThat(descriptor.getAnnotation().inheritLocations()).isFalse(); + } + + @Test + void overriddenContextConfigurationLocationsAndInheritLocations() throws Exception { + Class declaringClass = OverriddenMetaLocationsConfigTestCase.class; + AnnotationDescriptor descriptor = findAnnotationDescriptor(declaringClass, + ContextConfiguration.class); + assertThat(descriptor).isNotNull(); + assertThat(descriptor.getRootDeclaringClass()).isEqualTo(declaringClass); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaLocationsConfig.class); + assertThat(descriptor.getAnnotationType()).isEqualTo(ContextConfiguration.class); + assertThat(descriptor.getComposedAnnotation()).isNotNull(); + assertThat(descriptor.getComposedAnnotationType()).isEqualTo(MetaLocationsConfig.class); + + // direct access to annotation attributes: + assertThat(descriptor.getAnnotation().locations()).isEqualTo(new String[] { "foo.xml" }); + assertThat(descriptor.getAnnotation().inheritLocations()).isFalse(); + + // overridden attributes: + AnnotationAttributes attributes = descriptor.getAnnotationAttributes(); + assertThat(attributes.getStringArray("locations")).isEqualTo(new String[] { "bar.xml" }); + assertThat(attributes.getBoolean("inheritLocations")).isTrue(); + } + + + // ------------------------------------------------------------------------- + + @ContextConfiguration("foo.xml") + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaValueConfig { + + String[] value() default {}; + } + + @MetaValueConfig + static class MetaValueConfigTestCase { + } + + @MetaValueConfig("bar.xml") + static class OverriddenMetaValueConfigTestCase { + } + + @ContextConfiguration(locations = "foo.xml", inheritLocations = false) + @Retention(RetentionPolicy.RUNTIME) + static @interface MetaLocationsConfig { + + String[] locations() default {}; + + boolean inheritLocations(); + } + + @MetaLocationsConfig(inheritLocations = true) + static class MetaLocationsConfigTestCase { + } + + @MetaLocationsConfig(locations = "bar.xml", inheritLocations = true) + static class OverriddenMetaLocationsConfigTestCase { + } + +}