Skip to content

Commit 09cd8b3

Browse files
committed
Add ArchUnit test for consistency of repeatable annotations
Issues: #4059 and #4063 (cherry picked from commit eba399e)
1 parent fa46a92 commit 09cd8b3

File tree

1 file changed

+59
-0
lines changed
  • platform-tooling-support-tests/src/test/java/platform/tooling/support/tests

1 file changed

+59
-0
lines changed

platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,50 @@
1717
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
1818
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage;
1919
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
20+
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
2021
import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC;
22+
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
2123
import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier;
2224
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
2325
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining;
26+
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith;
2427
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
2528
import static com.tngtech.archunit.lang.conditions.ArchPredicates.have;
2629
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
2730
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
2831
import static org.junit.jupiter.api.Assertions.assertTrue;
2932
import static platform.tooling.support.Helper.loadJarFiles;
3033

34+
import java.lang.annotation.Annotation;
35+
import java.lang.annotation.Repeatable;
36+
import java.lang.annotation.Retention;
37+
import java.lang.annotation.Target;
38+
import java.util.Arrays;
3139
import java.util.Set;
40+
import java.util.function.BiPredicate;
3241
import java.util.stream.Collectors;
3342

43+
import com.tngtech.archunit.base.DescribedPredicate;
44+
import com.tngtech.archunit.core.domain.JavaClass;
3445
import com.tngtech.archunit.core.domain.JavaClasses;
3546
import com.tngtech.archunit.core.importer.Location;
3647
import com.tngtech.archunit.junit.AnalyzeClasses;
3748
import com.tngtech.archunit.junit.ArchTest;
3849
import com.tngtech.archunit.junit.LocationProvider;
50+
import com.tngtech.archunit.lang.ArchCondition;
3951
import com.tngtech.archunit.lang.ArchRule;
4052
import com.tngtech.archunit.library.GeneralCodingRules;
4153

4254
import org.apiguardian.api.API;
4355
import org.junit.jupiter.api.Order;
56+
import org.junit.jupiter.api.extension.ExtendWith;
57+
import org.junit.jupiter.params.provider.ArgumentsSource;
4458

4559
@Order(Integer.MAX_VALUE)
4660
@AnalyzeClasses(locations = ArchUnitTests.AllJars.class)
4761
class ArchUnitTests {
4862

63+
@SuppressWarnings("unused")
4964
@ArchTest
5065
private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() //
5166
.that(have(modifier(PUBLIC))) //
@@ -55,6 +70,17 @@ class ArchUnitTests {
5570
.and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) //
5671
.should().beAnnotatedWith(API.class);
5772

73+
@SuppressWarnings("unused")
74+
@ArchTest // Consistency of @Documented and @Inherited is checked by the compiler but not for @Retention and @Target
75+
private final ArchRule repeatableAnnotationsShouldHaveMatchingContainerAnnotations = classes() //
76+
.that(nameStartingWith("org.junit.")) //
77+
.and().areAnnotations() //
78+
.and().areAnnotatedWith(Repeatable.class) //
79+
.and(are(not(type(ExtendWith.class)))) // to be resolved in https://github.com/junit-team/junit5/issues/4059
80+
.and(are(not(type(ArgumentsSource.class).or(annotatedWith(ArgumentsSource.class))))) // to be resolved in https://github.com/junit-team/junit5/issues/4063
81+
.should(haveContainerAnnotationWithSameRetentionPolicy()) //
82+
.andShould(haveContainerAnnotationWithSameTargetTypes());
83+
5884
@ArchTest
5985
void allAreIn(JavaClasses classes) {
6086
// about 928 classes found in all jars
@@ -94,6 +120,16 @@ void avoidAccessingStandardStreams(JavaClasses classes) {
94120
GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset);
95121
}
96122

123+
private static ArchCondition<? super JavaClass> haveContainerAnnotationWithSameRetentionPolicy() {
124+
return ArchCondition.from(new RepeatableAnnotationPredicate<>(Retention.class,
125+
(expectedTarget, actualTarget) -> expectedTarget.value() == actualTarget.value()));
126+
}
127+
128+
private static ArchCondition<? super JavaClass> haveContainerAnnotationWithSameTargetTypes() {
129+
return ArchCondition.from(new RepeatableAnnotationPredicate<>(Target.class,
130+
(expectedTarget, actualTarget) -> Arrays.equals(expectedTarget.value(), actualTarget.value())));
131+
}
132+
97133
static class AllJars implements LocationProvider {
98134

99135
@Override
@@ -103,4 +139,27 @@ public Set<Location> get(Class<?> testClass) {
103139

104140
}
105141

142+
private static class RepeatableAnnotationPredicate<T extends Annotation> extends DescribedPredicate<JavaClass> {
143+
144+
private final Class<T> annotationType;
145+
private final BiPredicate<T, T> predicate;
146+
147+
public RepeatableAnnotationPredicate(Class<T> annotationType, BiPredicate<T, T> predicate) {
148+
super("have identical @%s annotation as container annotation", annotationType.getSimpleName());
149+
this.annotationType = annotationType;
150+
this.predicate = predicate;
151+
}
152+
153+
@Override
154+
public boolean test(JavaClass annotationClass) {
155+
var containerAnnotationClass = (JavaClass) annotationClass.getAnnotationOfType(
156+
Repeatable.class.getName()).get("value").orElseThrow();
157+
var expectedAnnotation = annotationClass.tryGetAnnotationOfType(annotationType);
158+
var actualAnnotation = containerAnnotationClass.tryGetAnnotationOfType(annotationType);
159+
return expectedAnnotation.map(expectedTarget -> actualAnnotation //
160+
.map(actualTarget -> predicate.test(expectedTarget, actualTarget)) //
161+
.orElse(false)) //
162+
.orElse(actualAnnotation.isEmpty());
163+
}
164+
}
106165
}

0 commit comments

Comments
 (0)