Skip to content

Commit b8b815d

Browse files
committed
Treat non-inherited composed annos for inherited annos as inherited
Non-@inherited composed annotations which are meta-annotated with an @inherited annotation are now considered to be implicitly inherited when searching for the meta-annotation within a class hierarchy. Issue: #1133
1 parent f5d454e commit b8b815d

File tree

3 files changed

+66
-13
lines changed

3 files changed

+66
-13
lines changed

documentation/src/docs/asciidoc/release-notes-5.0.3.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ _@API Guardian_ JAR _mandatory_ again.
4646
`name` attribute of `<testcase/>` elements into XML reports. However, due to a
4747
limitation of Maven Surefire, instead of `methodName(Type)` they are written as
4848
`methodName{Type}`.
49+
* Non-inherited _composed annotations_ which are meta-annotated with a given `@Inherited`
50+
annotation are now considered to be implicitly _inherited_ when searching for the given
51+
meta-annotation within a class hierarchy.
4952

5053
===== New Features and Improvements
5154

@@ -68,6 +71,13 @@ _@API Guardian_ JAR _mandatory_ again.
6871
===== Bug Fixes
6972

7073
* The `@Tag` and `@Tags` annotations are now inherited within test class hierarchies.
74+
* Due to a change in the JUnit Platform's `AnnotationUtils` class, non-inherited
75+
_composed annotations_ which are meta-annotated with a given `@Inherited` annotation
76+
are now considered to be implicitly _inherited_ when searching for the given
77+
meta-annotation within a class hierarchy.
78+
** For example, an `@Inherited` annotation such as `@TestInstance` will now be discovered
79+
on a custom _composed annotation_ declared on a superclass even if the _composed
80+
annotation_ is not declared as `@Inherited`.
7181

7282

7383
[[release-notes-5.0.3-junit-vintage]]

junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,22 @@ public static <A extends Annotation> Optional<A> findAnnotation(Optional<? exten
143143
return Optional.empty();
144144
}
145145

146-
return findAnnotation(element.get(), annotationType, new HashSet<>());
146+
boolean inherited = annotationType.isAnnotationPresent(Inherited.class);
147+
148+
return findAnnotation(element.get(), annotationType, inherited, new HashSet<>());
147149
}
148150

149151
/**
150152
* @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(AnnotatedElement, Class)
151153
*/
152154
public static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElement element, Class<A> annotationType) {
153-
return findAnnotation(element, annotationType, new HashSet<>());
155+
boolean inherited = annotationType.isAnnotationPresent(Inherited.class);
156+
return findAnnotation(element, annotationType, inherited, new HashSet<>());
154157
}
155158

156159
@SuppressWarnings("unchecked")
157160
private static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElement element, Class<A> annotationType,
158-
Set<Annotation> visited) {
161+
boolean inherited, Set<Annotation> visited) {
159162

160163
Preconditions.notNull(annotationType, "annotationType must not be null");
161164

@@ -179,22 +182,34 @@ private static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElemen
179182

180183
// Meta-present on directly present annotations?
181184
Optional<A> directMetaAnnotation = findMetaAnnotation(annotationType, element.getDeclaredAnnotations(), key,
182-
visited);
185+
inherited, visited);
183186
if (directMetaAnnotation.isPresent()) {
184187
return directMetaAnnotation;
185188
}
186189

187-
// Search on interfaces
188190
if (element instanceof Class) {
189191
Class<?> clazz = (Class<?>) element;
192+
193+
// Search on interfaces
190194
for (Class<?> ifc : clazz.getInterfaces()) {
191195
if (ifc != Annotation.class) {
192-
Optional<A> annotationOnInterface = findAnnotation(ifc, annotationType, visited);
196+
Optional<A> annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited);
193197
if (annotationOnInterface.isPresent()) {
194198
return annotationOnInterface;
195199
}
196200
}
197201
}
202+
203+
// Search in class hierarchy
204+
if (inherited) {
205+
Class<?> superclass = clazz.getSuperclass();
206+
if (superclass != null && superclass != Object.class) {
207+
Optional<A> annotationOnSuperclass = findAnnotation(superclass, annotationType, inherited, visited);
208+
if (annotationOnSuperclass.isPresent()) {
209+
return annotationOnSuperclass;
210+
}
211+
}
212+
}
198213
}
199214

200215
// Indirectly present?
@@ -205,16 +220,17 @@ private static <A extends Annotation> Optional<A> findAnnotation(AnnotatedElemen
205220
}
206221

207222
// Meta-present on indirectly present annotations?
208-
return findMetaAnnotation(annotationType, element.getAnnotations(), key, visited);
223+
return findMetaAnnotation(annotationType, element.getAnnotations(), key, inherited, visited);
209224
}
210225

211226
private static <A extends Annotation> Optional<A> findMetaAnnotation(Class<A> annotationType,
212-
Annotation[] candidates, AnnotationCacheKey key, Set<Annotation> visited) {
227+
Annotation[] candidates, AnnotationCacheKey key, boolean inherited, Set<Annotation> visited) {
213228

214229
for (Annotation candidateAnnotation : candidates) {
215230
Class<? extends Annotation> candidateAnnotationType = candidateAnnotation.annotationType();
216231
if (!isInJavaLangAnnotationPackage(candidateAnnotationType) && visited.add(candidateAnnotation)) {
217-
Optional<A> metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, visited);
232+
Optional<A> metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited,
233+
visited);
218234
if (metaAnnotation.isPresent()) {
219235
annotationCache.put(key, metaAnnotation.get());
220236
return metaAnnotation;

platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ void findAnnotationIndirectlyPresentOnClass() {
122122
assertThat(findAnnotation(SubInheritedAnnotationClass.class, InheritedAnnotation.class)).isPresent();
123123
}
124124

125+
/**
126+
* Test for https://github.com/junit-team/junit5/issues/1133
127+
*/
128+
@Test
129+
void findInheritedAnnotationMetaPresentOnNonInheritedComposedAnnotationPresentOnSuperclass() {
130+
assertThat(findAnnotation(SubNonInheritedCompositionOfInheritedAnnotationClass.class,
131+
InheritedAnnotation.class)).isPresent();
132+
}
133+
125134
@Test
126135
void findAnnotationDirectlyPresentOnClass() {
127136
assertThat(findAnnotation(Annotation1Class.class, Annotation1.class)).isPresent();
@@ -133,14 +142,15 @@ void findAnnotationMetaPresentOnClass() {
133142
}
134143

135144
/**
136-
* <b>Note:</b> there is no findAnnotationIndirectlyMetaPresentOnMethod counterpart because {@link Inherited}
137-
* annotation has no effect if the annotated type is used to annotate anything other than a class.
145+
* <b>Note:</b> there is no findAnnotationIndirectlyMetaPresentOnMethod
146+
* counterpart because the {@code @Inherited} annotation has no effect if
147+
* the annotation type is used to annotate anything other than a class.
138148
*
139149
* @see Inherited
140150
*/
141151
@Test
142152
void findAnnotationIndirectlyMetaPresentOnClass() {
143-
assertThat(findAnnotation(InheritedComposedAnnotationSubClass.class, Annotation1.class)).isPresent();
153+
assertThat(findAnnotation(SubInheritedComposedAnnotationClass.class, Annotation1.class)).isPresent();
144154
}
145155

146156
@Test
@@ -475,8 +485,16 @@ private List<String> asNames(List<Field> fields) {
475485
@interface InheritedComposedAnnotation {
476486
}
477487

488+
@Target(ElementType.TYPE)
489+
@Retention(RetentionPolicy.RUNTIME)
490+
@InheritedAnnotation
491+
// DO NOT make this @Inherited.
492+
@interface NonInheritedCompositionOfInheritedAnnotation {
493+
}
494+
478495
@Target({ ElementType.TYPE, ElementType.METHOD })
479496
@Retention(RetentionPolicy.RUNTIME)
497+
// DO NOT make this @Inherited.
480498
@interface Tags {
481499

482500
Tag[] value();
@@ -485,6 +503,7 @@ private List<String> asNames(List<Field> fields) {
485503
@Target({ ElementType.TYPE, ElementType.METHOD })
486504
@Retention(RetentionPolicy.RUNTIME)
487505
@Repeatable(Tags.class)
506+
// DO NOT make this @Inherited.
488507
@interface Tag {
489508

490509
String value();
@@ -581,7 +600,15 @@ void method() {
581600
}
582601
}
583602

584-
static class InheritedComposedAnnotationSubClass extends InheritedComposedAnnotationClass {
603+
static class SubInheritedComposedAnnotationClass extends InheritedComposedAnnotationClass {
604+
}
605+
606+
@NonInheritedCompositionOfInheritedAnnotation
607+
static class NonInheritedCompositionOfInheritedAnnotationClass {
608+
}
609+
610+
static class SubNonInheritedCompositionOfInheritedAnnotationClass
611+
extends NonInheritedCompositionOfInheritedAnnotationClass {
585612
}
586613

587614
@Annotation1

0 commit comments

Comments
 (0)