Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@
* a rule checking for no accesses to classes assignable to C will not fail, since ArchUnit does not know about the details
* of class B, but only simple information like the fully qualified name. For information how to configure the import and
* resolution behavior of missing classes, compare {@link ClassFileImporter}.
* <br><br>
* Classes to be analyzed can be specified in different ways:
* <ul>
* <li>{@link #packages()} - specify package names as strings</li>
* <li>{@link #packagesOf()} - specify packages relative to classes</li>
* <li>{@link #classes()} - specify individual classes to analyze</li>
* <li>{@link #locations()} - specify custom locations via {@link LocationProvider}</li>
* <li>{@link #wholeClasspath()} - import all classes on the classpath</li>
* </ul>
* These options can be combined. If no option is specified, the package of the annotated test class will be imported.
*
* @see ArchUnitRunner
* @see ClassFileImporter
Expand Down Expand Up @@ -83,4 +93,9 @@
* @return The {@link CacheMode} to use for this test class.
*/
CacheMode cacheMode() default CacheMode.FOREVER;

/**
* @return Classes to be used for testing instead of packages
*/
Class<?>[] classes() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,5 +209,10 @@ public CacheMode getCacheMode() {
public boolean scanWholeClasspath() {
return analyzeClasses.wholeClasspath();
}

@Override
public Class<?>[] getClassesToAnalyze() {
return analyzeClasses.classes();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
* a rule checking for no accesses to classes assignable to C will not fail, since ArchUnit does not know about the details
* of class B, but only simple information like the fully qualified name. For information how to configure the import and
* resolution behavior of missing classes, compare {@link ClassFileImporter}.
* <br><br>
* Classes to be analyzed can be specified in different ways:
* <ul>
* <li>{@link #packages()} - specify package names as strings</li>
* <li>{@link #packagesOf()} - specify packages relative to classes</li>
* <li>{@link #classes()} - specify individual classes to analyze</li>
* <li>{@link #locations()} - specify custom locations via {@link LocationProvider}</li>
* <li>{@link #wholeClasspath()} - import all classes on the classpath</li>
* </ul>
* These options can be combined. If no option is specified, the package of the annotated test class will be imported.
*
* @see ClassFileImporter
*/
Expand Down Expand Up @@ -87,4 +97,9 @@
* @return The {@link CacheMode} to use for this test class.
*/
CacheMode cacheMode() default CacheMode.FOREVER;

/**
* @return Classes to be used for testing instead of packages
*/
Class<?>[] classes() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ public CacheMode getCacheMode() {
public boolean scanWholeClasspath() {
return analyzeClasses.wholeClasspath();
}

@Override
public Class<?>[] getClassesToAnalyze() {
return analyzeClasses.classes();
}
}

private static class TestMember<MEMBER extends AccessibleObject & Member> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.tngtech.archunit.junit.engine_api.FieldSelector;
import com.tngtech.archunit.junit.engine_api.FieldSource;
import com.tngtech.archunit.junit.internal.ArchUnitTestEngine.SharedCache;
import com.tngtech.archunit.junit.internal.testexamples.AnalyzeClassesWithClassesProperty;
import com.tngtech.archunit.junit.internal.testexamples.ClassWithPrivateTests;
import com.tngtech.archunit.junit.internal.testexamples.ComplexMetaTags;
import com.tngtech.archunit.junit.internal.testexamples.ComplexRuleLibrary;
Expand Down Expand Up @@ -1079,6 +1080,7 @@ void passes_AnalyzeClasses_to_cache() {
assertThat(request.getLocationProviders()).isEqualTo(expected.locations());
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
assertThat(request.getClassesToAnalyze()).isEmpty();
}

@Test
Expand All @@ -1104,6 +1106,21 @@ void a_class_with_analyze_classes_as_meta_annotation() {
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
}

@Test
void passes_AnalyzeClasses_with_new_classes_property_to_cache() {
execute(createEngineId(), AnalyzeClassesWithClassesProperty.class);

verify(classCache).getClassesToAnalyzeFor(eq(AnalyzeClassesWithClassesProperty.class), classAnalysisRequestCaptor.capture());
ClassAnalysisRequest request = classAnalysisRequestCaptor.getValue();
AnalyzeClasses expected = AnalyzeClassesWithClassesProperty.class.getAnnotation(AnalyzeClasses.class);
assertThat(request.getClassesToAnalyze()).isEqualTo(expected.classes());
assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
assertThat(request.getPackageNames()).isEmpty();
assertThat(request.getPackageRoots()).isEmpty();
assertThat(request.getLocationProviders()).isEmpty();
assertThat(request.scanWholeClasspath()).as("scan whole classpath").isFalse();
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.tngtech.archunit.junit.internal.testexamples;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.library.testclasses.coveringallclasses.first.First;
import com.tngtech.archunit.library.testclasses.coveringallclasses.second.Second;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

@AnalyzeClasses(
classes = {First.class, Second.class},
importOptions = {ImportOption.DoNotIncludeTests.class, ImportOption.DoNotIncludeJars.class}
)
public class AnalyzeClassesWithClassesProperty {
@ArchTest
public static final ArchRule irrelevant = classes().should(new ArchCondition<JavaClass>("exist") {
@Override
public void check(JavaClass item, ConditionEvents events) {
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ interface ClassAnalysisRequest {
CacheMode getCacheMode();

boolean scanWholeClasspath();

Class<?>[] getClassesToAnalyze();
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private Specific(ClassAnalysisRequest classAnalysisRequest, Class<?> testClass)
declaredLocations = ImmutableSet.<Location>builder()
.addAll(getLocationsOfPackages(classAnalysisRequest))
.addAll(getLocationsOfProviders(classAnalysisRequest, testClass))
.addAll(getLocationsOfClasses(classAnalysisRequest))
.addAll(classAnalysisRequest.scanWholeClasspath() ? Locations.inClassPath() : emptySet())
.build();
}
Expand All @@ -201,6 +202,12 @@ private Set<Location> getLocationsOfProviders(ClassAnalysisRequest classAnalysis
.collect(toSet());
}

private Set<Location> getLocationsOfClasses(ClassAnalysisRequest classAnalysisRequest) {
return stream(classAnalysisRequest.getClassesToAnalyze())
.flatMap(clazz -> Locations.ofClass(clazz).stream())
.collect(toSet());
}

private LocationProvider tryCreate(Class<? extends LocationProvider> providerClass) {
try {
return newInstanceOf(providerClass);
Expand Down Expand Up @@ -240,6 +247,7 @@ private static boolean noSpecificLocationRequested(ClassAnalysisRequest classAna
return classAnalysisRequest.getPackageNames().length == 0
&& classAnalysisRequest.getPackageRoots().length == 0
&& classAnalysisRequest.getLocationProviders().length == 0
&& classAnalysisRequest.getClassesToAnalyze().length == 0
&& !classAnalysisRequest.scanWholeClasspath();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ public void gets_all_classes_relative_to_class() {
assertThat(classes.contain(getClass())).as("root class is contained itself").isTrue();
}

@Test
public void gets_all_classes_specified() {
JavaClasses classes = cache.getClassesToAnalyzeFor(TestClass.class, new TestAnalysisRequest()
.withClassesToAnalyze(getClass()));

assertThat(classes).hasSize(1);
assertThat(classes.contain(getClass())).as("root class is contained itself").isTrue();
}

@Test
public void get_all_classes_by_LocationProvider() {
JavaClasses classes = cache.getClassesToAnalyzeFor(TestClass.class, new TestAnalysisRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class TestAnalysisRequest implements ClassAnalysisRequest {
private boolean wholeClasspath = false;
private Class<? extends ImportOption>[] importOptions = new Class[0];
private CacheMode cacheMode = CacheMode.FOREVER;
private Class<?>[] classesToAnalyze = new Class[0];

@Override
public String[] getPackageNames() {
Expand All @@ -33,6 +34,9 @@ public boolean scanWholeClasspath() {
return wholeClasspath;
}

@Override
public Class<?>[] getClassesToAnalyze() { return classesToAnalyze; }

@Override
public Class<? extends ImportOption>[] getImportOptions() {
return importOptions;
Expand Down Expand Up @@ -74,4 +78,9 @@ TestAnalysisRequest withCacheMode(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}

TestAnalysisRequest withClassesToAnalyze(Class<?>... classesToAnalyze) {
this.classesToAnalyze = classesToAnalyze;
return this;
}
}
13 changes: 12 additions & 1 deletion docs/userguide/009_JUnit_Support.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,18 @@ packages these classes reside in will be imported:
@AnalyzeClasses(packagesOf = {SubOneConfiguration.class, SubTwoConfiguration.class})
----

As a third option, locations can be specified freely by implementing a `LocationProvider`:
As an alternative to specifying packages, you can directly specify individual classes to be analyzed:

[source,java,options="nowrap"]
----
@AnalyzeClasses(classes = {String.class, Integer.class})
----

This allows for more fine-grained control over which classes are imported for testing.
You can combine the `classes` property with other properties like `packages`, `packagesOf`, etc.
In this case, all specified classes and packages will be imported for analysis.

As another option, locations can be specified freely by implementing a `LocationProvider`:

[source,java,options="nowrap"]
----
Expand Down