Skip to content

Commit f6a08b9

Browse files
Adjust AnalyzeClasses annotation to support individual classes as parameter
Relates to #1195 Signed-off-by: Andreas Zöller <andreas.zoeller@tngtech.com>
1 parent 4d0fea4 commit f6a08b9

11 files changed

Lines changed: 123 additions & 1 deletion

File tree

archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/AnalyzeClasses.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@
3838
* a rule checking for no accesses to classes assignable to C will not fail, since ArchUnit does not know about the details
3939
* of class B, but only simple information like the fully qualified name. For information how to configure the import and
4040
* resolution behavior of missing classes, compare {@link ClassFileImporter}.
41+
* <br><br>
42+
* Classes to be analyzed can be specified in different ways:
43+
* <ul>
44+
* <li>{@link #packages()} - specify package names as strings</li>
45+
* <li>{@link #packagesOf()} - specify packages relative to classes</li>
46+
* <li>{@link #classes()} - specify individual classes to analyze</li>
47+
* <li>{@link #locations()} - specify custom locations via {@link LocationProvider}</li>
48+
* <li>{@link #wholeClasspath()} - import all classes on the classpath</li>
49+
* </ul>
50+
* These options can be combined. If no option is specified, the package of the annotated test class will be imported.
4151
*
4252
* @see ArchUnitRunner
4353
* @see ClassFileImporter
@@ -83,4 +93,9 @@
8393
* @return The {@link CacheMode} to use for this test class.
8494
*/
8595
CacheMode cacheMode() default CacheMode.FOREVER;
96+
97+
/**
98+
* @return Classes to be used for testing instead of packages
99+
*/
100+
Class<?>[] classes() default {};
86101
}

archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitRunnerInternal.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,10 @@ public CacheMode getCacheMode() {
209209
public boolean scanWholeClasspath() {
210210
return analyzeClasses.wholeClasspath();
211211
}
212+
213+
@Override
214+
public Class<?>[] getClassesToAnalyze() {
215+
return analyzeClasses.classes();
216+
}
212217
}
213218
}

archunit-junit/junit5/api/src/main/java/com/tngtech/archunit/junit/AnalyzeClasses.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
* a rule checking for no accesses to classes assignable to C will not fail, since ArchUnit does not know about the details
4242
* of class B, but only simple information like the fully qualified name. For information how to configure the import and
4343
* resolution behavior of missing classes, compare {@link ClassFileImporter}.
44+
* <br><br>
45+
* Classes to be analyzed can be specified in different ways:
46+
* <ul>
47+
* <li>{@link #packages()} - specify package names as strings</li>
48+
* <li>{@link #packagesOf()} - specify packages relative to classes</li>
49+
* <li>{@link #classes()} - specify individual classes to analyze</li>
50+
* <li>{@link #locations()} - specify custom locations via {@link LocationProvider}</li>
51+
* <li>{@link #wholeClasspath()} - import all classes on the classpath</li>
52+
* </ul>
53+
* These options can be combined. If no option is specified, the package of the annotated test class will be imported.
4454
*
4555
* @see ClassFileImporter
4656
*/
@@ -87,4 +97,9 @@
8797
* @return The {@link CacheMode} to use for this test class.
8898
*/
8999
CacheMode cacheMode() default CacheMode.FOREVER;
100+
101+
/**
102+
* @return Classes to be used for testing instead of packages
103+
*/
104+
Class<?>[] classes() default {};
90105
}

archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@ public CacheMode getCacheMode() {
324324
public boolean scanWholeClasspath() {
325325
return analyzeClasses.wholeClasspath();
326326
}
327+
328+
@Override
329+
public Class<?>[] getClassesToAnalyze() {
330+
return analyzeClasses.classes();
331+
}
327332
}
328333

329334
private static class TestMember<MEMBER extends AccessibleObject & Member> {

archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.tngtech.archunit.junit.engine_api.FieldSelector;
2121
import com.tngtech.archunit.junit.engine_api.FieldSource;
2222
import com.tngtech.archunit.junit.internal.ArchUnitTestEngine.SharedCache;
23+
import com.tngtech.archunit.junit.internal.testexamples.AnalyzeClassesWithClassesProperty;
2324
import com.tngtech.archunit.junit.internal.testexamples.ClassWithPrivateTests;
2425
import com.tngtech.archunit.junit.internal.testexamples.ComplexMetaTags;
2526
import com.tngtech.archunit.junit.internal.testexamples.ComplexRuleLibrary;
@@ -1079,6 +1080,7 @@ void passes_AnalyzeClasses_to_cache() {
10791080
assertThat(request.getLocationProviders()).isEqualTo(expected.locations());
10801081
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
10811082
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
1083+
assertThat(request.getClassesToAnalyze()).isEmpty();
10821084
}
10831085

10841086
@Test
@@ -1104,6 +1106,21 @@ void a_class_with_analyze_classes_as_meta_annotation() {
11041106
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
11051107
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
11061108
}
1109+
1110+
@Test
1111+
void passes_AnalyzeClasses_with_new_classes_property_to_cache() {
1112+
execute(createEngineId(), AnalyzeClassesWithClassesProperty.class);
1113+
1114+
verify(classCache).getClassesToAnalyzeFor(eq(AnalyzeClassesWithClassesProperty.class), classAnalysisRequestCaptor.capture());
1115+
ClassAnalysisRequest request = classAnalysisRequestCaptor.getValue();
1116+
AnalyzeClasses expected = AnalyzeClassesWithClassesProperty.class.getAnnotation(AnalyzeClasses.class);
1117+
assertThat(request.getClassesToAnalyze()).isEqualTo(expected.classes());
1118+
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
1119+
assertThat(request.getPackageNames()).isEmpty();
1120+
assertThat(request.getPackageRoots()).isEmpty();
1121+
assertThat(request.getLocationProviders()).isEmpty();
1122+
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isFalse();
1123+
}
11071124
}
11081125

11091126
@Nested
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.tngtech.archunit.junit.internal.testexamples;
2+
3+
import com.tngtech.archunit.core.domain.JavaClass;
4+
import com.tngtech.archunit.core.importer.ImportOption;
5+
import com.tngtech.archunit.junit.AnalyzeClasses;
6+
import com.tngtech.archunit.junit.ArchTest;
7+
import com.tngtech.archunit.lang.ArchCondition;
8+
import com.tngtech.archunit.lang.ArchRule;
9+
import com.tngtech.archunit.lang.ConditionEvents;
10+
import com.tngtech.archunit.library.testclasses.coveringallclasses.first.First;
11+
import com.tngtech.archunit.library.testclasses.coveringallclasses.second.Second;
12+
13+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
14+
15+
@AnalyzeClasses(
16+
classes = {First.class, Second.class},
17+
importOptions = {ImportOption.DoNotIncludeTests.class, ImportOption.DoNotIncludeJars.class}
18+
)
19+
public class AnalyzeClassesWithClassesProperty {
20+
@ArchTest
21+
public static final ArchRule irrelevant = classes().should(new ArchCondition<JavaClass>("exist") {
22+
@Override
23+
public void check(JavaClass item, ConditionEvents events) {
24+
}
25+
});
26+
}

archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ClassAnalysisRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ interface ClassAnalysisRequest {
1919
CacheMode getCacheMode();
2020

2121
boolean scanWholeClasspath();
22+
23+
Class<?>[] getClassesToAnalyze();
2224
}

archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ClassCache.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ private Specific(ClassAnalysisRequest classAnalysisRequest, Class<?> testClass)
183183
declaredLocations = ImmutableSet.<Location>builder()
184184
.addAll(getLocationsOfPackages(classAnalysisRequest))
185185
.addAll(getLocationsOfProviders(classAnalysisRequest, testClass))
186+
.addAll(getLocationsOfClasses(classAnalysisRequest))
186187
.addAll(classAnalysisRequest.scanWholeClasspath() ? Locations.inClassPath() : emptySet())
187188
.build();
188189
}
@@ -201,6 +202,12 @@ private Set<Location> getLocationsOfProviders(ClassAnalysisRequest classAnalysis
201202
.collect(toSet());
202203
}
203204

205+
private Set<Location> getLocationsOfClasses(ClassAnalysisRequest classAnalysisRequest) {
206+
return stream(classAnalysisRequest.getClassesToAnalyze())
207+
.flatMap(clazz -> Locations.ofClass(clazz).stream())
208+
.collect(toSet());
209+
}
210+
204211
private LocationProvider tryCreate(Class<? extends LocationProvider> providerClass) {
205212
try {
206213
return newInstanceOf(providerClass);
@@ -240,6 +247,7 @@ private static boolean noSpecificLocationRequested(ClassAnalysisRequest classAna
240247
return classAnalysisRequest.getPackageNames().length == 0
241248
&& classAnalysisRequest.getPackageRoots().length == 0
242249
&& classAnalysisRequest.getLocationProviders().length == 0
250+
&& classAnalysisRequest.getClassesToAnalyze().length == 0
243251
&& !classAnalysisRequest.scanWholeClasspath();
244252
}
245253
}

archunit-junit/src/test/java/com/tngtech/archunit/junit/internal/ClassCacheTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ public void gets_all_classes_relative_to_class() {
108108
assertThat(classes.contain(getClass())).as("root class is contained itself").isTrue();
109109
}
110110

111+
@Test
112+
public void gets_all_classes_specified() {
113+
JavaClasses classes = cache.getClassesToAnalyzeFor(TestClass.class, new TestAnalysisRequest()
114+
.withClassesToAnalyze(getClass()));
115+
116+
assertThat(classes).hasSize(1);
117+
assertThat(classes.contain(getClass())).as("root class is contained itself").isTrue();
118+
}
119+
111120
@Test
112121
public void get_all_classes_by_LocationProvider() {
113122
JavaClasses classes = cache.getClassesToAnalyzeFor(TestClass.class, new TestAnalysisRequest()

archunit-junit/src/test/java/com/tngtech/archunit/junit/internal/TestAnalysisRequest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class TestAnalysisRequest implements ClassAnalysisRequest {
1212
private boolean wholeClasspath = false;
1313
private Class<? extends ImportOption>[] importOptions = new Class[0];
1414
private CacheMode cacheMode = CacheMode.FOREVER;
15+
private Class<?>[] classesToAnalyze = new Class[0];
1516

1617
@Override
1718
public String[] getPackageNames() {
@@ -33,6 +34,9 @@ public boolean scanWholeClasspath() {
3334
return wholeClasspath;
3435
}
3536

37+
@Override
38+
public Class<?>[] getClassesToAnalyze() { return classesToAnalyze; }
39+
3640
@Override
3741
public Class<? extends ImportOption>[] getImportOptions() {
3842
return importOptions;
@@ -74,4 +78,9 @@ TestAnalysisRequest withCacheMode(CacheMode cacheMode) {
7478
this.cacheMode = cacheMode;
7579
return this;
7680
}
81+
82+
TestAnalysisRequest withClassesToAnalyze(Class<?>... classesToAnalyze) {
83+
this.classesToAnalyze = classesToAnalyze;
84+
return this;
85+
}
7786
}

0 commit comments

Comments
 (0)