17
17
import static com .tngtech .archunit .core .domain .JavaClass .Predicates .resideInAPackage ;
18
18
import static com .tngtech .archunit .core .domain .JavaClass .Predicates .resideInAnyPackage ;
19
19
import static com .tngtech .archunit .core .domain .JavaClass .Predicates .simpleName ;
20
+ import static com .tngtech .archunit .core .domain .JavaClass .Predicates .type ;
20
21
import static com .tngtech .archunit .core .domain .JavaModifier .PUBLIC ;
22
+ import static com .tngtech .archunit .core .domain .properties .CanBeAnnotated .Predicates .annotatedWith ;
21
23
import static com .tngtech .archunit .core .domain .properties .HasModifiers .Predicates .modifier ;
22
24
import static com .tngtech .archunit .core .domain .properties .HasName .Predicates .name ;
23
25
import static com .tngtech .archunit .core .domain .properties .HasName .Predicates .nameContaining ;
26
+ import static com .tngtech .archunit .core .domain .properties .HasName .Predicates .nameStartingWith ;
24
27
import static com .tngtech .archunit .lang .conditions .ArchPredicates .are ;
25
28
import static com .tngtech .archunit .lang .conditions .ArchPredicates .have ;
26
29
import static com .tngtech .archunit .lang .syntax .ArchRuleDefinition .classes ;
27
30
import static com .tngtech .archunit .library .dependencies .SlicesRuleDefinition .slices ;
28
31
import static org .junit .jupiter .api .Assertions .assertTrue ;
29
32
import static platform .tooling .support .Helper .loadJarFiles ;
30
33
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 ;
31
39
import java .util .Set ;
40
+ import java .util .function .BiPredicate ;
32
41
import java .util .stream .Collectors ;
33
42
43
+ import com .tngtech .archunit .base .DescribedPredicate ;
44
+ import com .tngtech .archunit .core .domain .JavaClass ;
34
45
import com .tngtech .archunit .core .domain .JavaClasses ;
35
46
import com .tngtech .archunit .core .importer .Location ;
36
47
import com .tngtech .archunit .junit .AnalyzeClasses ;
37
48
import com .tngtech .archunit .junit .ArchTest ;
38
49
import com .tngtech .archunit .junit .LocationProvider ;
50
+ import com .tngtech .archunit .lang .ArchCondition ;
39
51
import com .tngtech .archunit .lang .ArchRule ;
40
52
import com .tngtech .archunit .library .GeneralCodingRules ;
41
53
42
54
import org .apiguardian .api .API ;
43
55
import org .junit .jupiter .api .Order ;
56
+ import org .junit .jupiter .api .extension .ExtendWith ;
57
+ import org .junit .jupiter .params .provider .ArgumentsSource ;
44
58
45
59
@ Order (Integer .MAX_VALUE )
46
60
@ AnalyzeClasses (locations = ArchUnitTests .AllJars .class )
47
61
class ArchUnitTests {
48
62
63
+ @ SuppressWarnings ("unused" )
49
64
@ ArchTest
50
65
private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes () //
51
66
.that (have (modifier (PUBLIC ))) //
@@ -55,6 +70,17 @@ class ArchUnitTests {
55
70
.and (not (describe ("are shadowed" , resideInAnyPackage ("..shadow.." )))) //
56
71
.should ().beAnnotatedWith (API .class );
57
72
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
+
58
84
@ ArchTest
59
85
void allAreIn (JavaClasses classes ) {
60
86
// about 928 classes found in all jars
@@ -94,6 +120,16 @@ void avoidAccessingStandardStreams(JavaClasses classes) {
94
120
GeneralCodingRules .NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS .check (subset );
95
121
}
96
122
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
+
97
133
static class AllJars implements LocationProvider {
98
134
99
135
@ Override
@@ -103,4 +139,27 @@ public Set<Location> get(Class<?> testClass) {
103
139
104
140
}
105
141
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
+ }
106
165
}
0 commit comments