Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surefire test filtering support #1

Draft
wants to merge 3 commits into
base: feature/issue-641-tooling-tests
Choose a base branch
from
Draft
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 @@ -18,6 +18,7 @@
import java.lang.reflect.Field;

import com.tngtech.archunit.core.domain.JavaClasses;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
Expand Down Expand Up @@ -77,4 +78,19 @@ void notify(RunNotifier notifier) {
notifier.fireTestFailure(new Failure(description, failure));
}
}

static class AbortedResult extends Result {
private final Description description;
private final AssumptionViolatedException failedAssumption;

AbortedResult(Description description, AssumptionViolatedException failedAssumption) {
this.description = description;
this.failedAssumption = failedAssumption;
}

@Override
void notify(RunNotifier notifier) {
notifier.fireTestAssumptionFailed(new Failure(description, failedAssumption));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.junit.ArchTest;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;

import static com.tngtech.archunit.junit.internal.DisplayNameResolver.determineDisplayName;
Expand All @@ -38,6 +39,8 @@ Result evaluateOn(JavaClasses classes) {
try {
executeTestMethod(classes);
return new PositiveResult();
} catch (AssumptionViolatedException failedAssumption) {
return new AbortedResult(describeSelf(), failedAssumption);
} catch (Throwable failure) {
return new NegativeResult(describeSelf(), failure);
}
Expand Down
17 changes: 9 additions & 8 deletions archunit-junit/junit5/engine/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ dependencies {
api project(path: ':archunit')
api project(path: ':archunit-junit5-api')
api project(path: ':archunit-junit5-engine-api')

implementation project(path: ':archunit-junit')
dependency.addGuava { dependencyNotation, config -> implementation(dependencyNotation, config) }
implementation dependency.slf4j
implementation dependency.junitPlatformLauncher

compileOnly dependency.findBugsAnnotations
compileOnly dependency.surefireApi

testImplementation project(path: ':archunit', configuration: 'tests')
testImplementation dependency.assertj
Expand Down Expand Up @@ -46,23 +51,19 @@ test {

shadowJar {
exclude 'META-INF/maven/**'

dependencies {
Copy link
Owner Author

@crizzis crizzis Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certain dependencies (e.g. junitPlatformLaungher) are needed with their original (unshadowed) package names (see why here and here)

The changes made to the shadowJar config, as well as disabling both checkExportedPackage and checkArtifact, was the only way I managed to get it work. Leaving the current config resulted in a bunch of ClassNotFoundExceptions in tests, because the classes from junitPlatformLauncher, surefireApi etc. would, unsurprisingly, not be found on the runtime test classpath.

Please advise on how this requirement should properly be handled. If the packages absolutely must be shadowed, I can introduce a proxy at runtime, but that just adds unnecessary complexity IMHO.

exclude(dependency {
def isApi = it.configuration == 'archJunitApi'
def isUnwantedDependency = it.name != dependency.guava && it.moduleName != 'archunit-junit'
isUnwantedDependency || isApi
})
}
}

def configureDependencies = { deps ->
deps.children().removeIf { dep ->
dep.scope.text() != 'compile' || !(dep.artifactId.text() in ['archunit', 'archunit-junit5-api', 'archunit-junit5-engine-api'])
}
}

this.with project(':archunit-junit').configureJUnitArchive(configureDependencies)

singlePackageExport {
exportedPackage = 'com.tngtech.archunit.junit.internal'
}

checkExportedPackage.enabled = false
checkArtifact.enabled = false
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,30 @@
*/
package com.tngtech.archunit.junit.internal;

import java.util.Properties;

import com.tngtech.archunit.ArchConfiguration;
import com.tngtech.archunit.junit.internal.filtering.TestSourceFilter;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
import org.junit.platform.engine.support.hierarchical.Node;

class ArchUnitEngineDescriptor extends EngineDescriptor implements Node<ArchUnitEngineExecutionContext> {
ArchUnitEngineDescriptor(UniqueId uniqueId) {
public class ArchUnitEngineDescriptor extends EngineDescriptor implements Node<ArchUnitEngineExecutionContext> {
public ArchUnitEngineDescriptor(UniqueId uniqueId) {
super(uniqueId, "ArchUnit JUnit 5");
}

private TestSourceFilter additionalFilter = TestSourceFilter.NOOP;

public void setAdditionalFilter(TestSourceFilter additionalFilter) {
this.additionalFilter = additionalFilter;
}

public TestSourceFilter getAdditionalFilter() {
return additionalFilter;
}

public Properties getConfiguration() {
return ArchConfiguration.get().getSubProperties(ArchConfiguration.JUNIT_PREFIX);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
*/
package com.tngtech.archunit.junit.internal;

import com.tngtech.archunit.ArchConfiguration;
import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;

class ArchUnitEngineExecutionContext implements EngineExecutionContext {

ArchConfiguration getConfiguration() {
return ArchConfiguration.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.tngtech.archunit.junit.CacheMode;
import com.tngtech.archunit.junit.LocationProvider;
import com.tngtech.archunit.junit.engine_api.FieldSource;
import com.tngtech.archunit.junit.internal.filtering.TestSourceFilter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
Expand Down Expand Up @@ -61,13 +62,14 @@ private ArchUnitTestDescriptor(ElementResolver resolver, Class<?> testClass, Cla
this.classCache = classCache;
}

static void resolve(TestDescriptor parent, ElementResolver resolver, ClassCache classCache) {
static void resolve(TestDescriptor parent, ElementResolver resolver, ClassCache classCache, TestSourceFilter additionalFilter) {
resolver.resolveClass()
.ifRequestedAndResolved(CreatesChildren::createChildren)
.ifRequestedButUnresolved((clazz, childResolver) -> createTestDescriptor(parent, classCache, clazz, childResolver));
.ifRequestedAndResolved((resolvedMember, elementResolver) -> resolvedMember.createChildren(resolver, additionalFilter))
.ifRequestedButUnresolved((clazz, childResolver) -> createTestDescriptor(parent, classCache, clazz, childResolver, additionalFilter));
}

private static void createTestDescriptor(TestDescriptor parent, ClassCache classCache, Class<?> clazz, ElementResolver childResolver) {
private static void createTestDescriptor(TestDescriptor parent, ClassCache classCache, Class<?> clazz, ElementResolver childResolver,
TestSourceFilter additionalFilter) {
if (clazz.getAnnotation(AnalyzeClasses.class) == null) {
LOG.warn("Class {} is not annotated with @{} and thus cannot run as a top level test. "
+ "This warning can be ignored if {} is only used as part of a rules library included via {}.in({}.class).",
Expand All @@ -79,36 +81,44 @@ private static void createTestDescriptor(TestDescriptor parent, ClassCache class

ArchUnitTestDescriptor classDescriptor = new ArchUnitTestDescriptor(childResolver, clazz, classCache);
parent.addChild(classDescriptor);
classDescriptor.createChildren(childResolver);
classDescriptor.createChildren(childResolver, additionalFilter);
}

@Override
public void createChildren(ElementResolver resolver) {
public void createChildren(ElementResolver resolver, TestSourceFilter filter) {
Supplier<JavaClasses> classes = () -> classCache.getClassesToAnalyzeFor(testClass, new JUnit5ClassAnalysisRequest(testClass));

getAllFields(testClass, withAnnotation(ArchTest.class))
.forEach(field -> resolveField(resolver, classes, field));
.forEach(field -> resolveField(resolver, classes, field, filter));
getAllMethods(testClass, withAnnotation(ArchTest.class))
.forEach(method -> resolveMethod(resolver, classes, method));
.forEach(method -> resolveMethod(resolver, classes, method, filter));
}

private void resolveField(ElementResolver resolver, Supplier<JavaClasses> classes, Field field) {
private void resolveField(ElementResolver resolver, Supplier<JavaClasses> classes, Field field, TestSourceFilter filter) {
resolver.resolveField(field)
.ifUnresolved(childResolver -> resolveChildren(this, childResolver, field, classes));
.ifUnresolved(childResolver -> resolveChildren(this, childResolver, field, classes, filter));
}

private void resolveMethod(ElementResolver resolver, Supplier<JavaClasses> classes, Method method) {
private void resolveMethod(ElementResolver resolver, Supplier<JavaClasses> classes, Method method, TestSourceFilter filter) {
resolver.resolveMethod(method)
.ifUnresolved(childResolver -> addChild(new ArchUnitMethodDescriptor(getUniqueId(), method, classes)));
.ifUnresolved(childResolver -> {
ArchUnitMethodDescriptor descriptor = new ArchUnitMethodDescriptor(getUniqueId(), method, classes);
if (filter.shouldRun(descriptor)) {
addChild(descriptor);
}
});
}

private static void resolveChildren(
TestDescriptor parent, ElementResolver resolver, Field field, Supplier<JavaClasses> classes) {
TestDescriptor parent, ElementResolver resolver, Field field, Supplier<JavaClasses> classes, TestSourceFilter filter) {

if (ArchTests.class.isAssignableFrom(field.getType())) {
resolveArchRules(parent, resolver, field, classes);
resolveArchRules(parent, resolver, field, classes, filter);
} else {
parent.addChild(new ArchUnitRuleDescriptor(resolver.getUniqueId(), getValue(field), classes, field));
ArchUnitRuleDescriptor descriptor = new ArchUnitRuleDescriptor(resolver.getUniqueId(), getValue(field), classes, field);
if (filter.shouldRun(descriptor)) {
parent.addChild(descriptor);
}
}
}

Expand All @@ -117,16 +127,18 @@ private static <T> T getValue(Field field) {
}

private static void resolveArchRules(
TestDescriptor parent, ElementResolver resolver, Field field, Supplier<JavaClasses> classes) {
TestDescriptor parent, ElementResolver resolver, Field field, Supplier<JavaClasses> classes, TestSourceFilter filter) {

if (!filter.shouldRun(FieldSource.from(field))) {
return;
}
DeclaredArchTests archTests = getDeclaredArchTests(field);

resolver.resolveClass(archTests.getDefinitionLocation())
.ifRequestedAndResolved(CreatesChildren::createChildren)
.ifRequestedAndResolved((resolvedMember, elementResolver) -> resolvedMember.createChildren(resolver, filter))
.ifRequestedButUnresolved((clazz, childResolver) -> {
ArchUnitArchTestsDescriptor rulesDescriptor = new ArchUnitArchTestsDescriptor(childResolver, archTests, classes, field);
parent.addChild(rulesDescriptor);
rulesDescriptor.createChildren(childResolver);
rulesDescriptor.createChildren(childResolver, TestSourceFilter.NOOP);
});
}

Expand Down Expand Up @@ -194,6 +206,7 @@ public Type getType() {

@Override
public ArchUnitEngineExecutionContext execute(ArchUnitEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {

invokeMethod(method, method.getDeclaringClass(), classes.get());
return context;
}
Expand All @@ -215,14 +228,18 @@ private static class ArchUnitArchTestsDescriptor extends AbstractArchUnitTestDes
}

@Override
public void createChildren(ElementResolver resolver) {
public void createChildren(ElementResolver resolver, TestSourceFilter filter) {
archTests.handleFields(field ->
resolver.resolve(FIELD_SEGMENT_TYPE, field.getName(), childResolver ->
resolveChildren(this, childResolver, field, classes)));
resolveChildren(this, childResolver, field, classes, filter)));

archTests.handleMethods(method ->
resolver.resolve(METHOD_SEGMENT_TYPE, method.getName(), childResolver ->
addChild(new ArchUnitMethodDescriptor(getUniqueId(), method, classes))));
resolver.resolve(METHOD_SEGMENT_TYPE, method.getName(), childResolver -> {
ArchUnitMethodDescriptor descriptor = new ArchUnitMethodDescriptor(getUniqueId(), method, classes);
if (filter.shouldRun(descriptor)) {
addChild(descriptor);
}
}));
}

@Override
Expand Down
Loading