From 288292416351fbc3ac425fe9069ad87452f6870e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Thu, 23 Jun 2022 10:14:29 +0200 Subject: [PATCH 01/10] Base filtering mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../junit/internal/ArchTestExecution.java | 16 +++ .../internal/ArchTestMethodExecution.java | 3 + archunit-junit/junit5/engine/build.gradle | 4 + .../internal/ArchUnitEngineDescriptor.java | 11 ++ .../internal/ArchUnitTestDescriptor.java | 63 +++++++---- .../junit/internal/ArchUnitTestEngine.java | 51 ++++++++- .../junit/internal/CreatesChildren.java | 4 +- .../filtering/AbstractTestNameFilter.java | 105 ++++++++++++++++++ .../filtering/FieldSelectorFactory.java | 50 +++++++++ .../filtering/MethodSelectorFactory.java | 50 +++++++++ .../internal/filtering/ReflectionUtils.java | 61 ++++++++++ .../filtering/TestSelectorFactory.java | 88 +++++++++++++++ .../internal/filtering/TestSourceFilter.java | 37 ++++++ .../junit/internal/ReflectionUtils.java | 37 +++++- archunit-tooling-test/build.gradle | 1 - .../tooling/engines/gradle/GradleEngine.java | 5 +- .../engines/surefire/MavenSurefireEngine.java | 24 +--- .../tooling/utils/JUnitEngineResolver.java | 27 +++++ .../resources/project/build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 20 files changed, 583 insertions(+), 60 deletions(-) create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/FieldSelectorFactory.java create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/MethodSelectorFactory.java create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ReflectionUtils.java create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java create mode 100644 archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/utils/JUnitEngineResolver.java diff --git a/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestExecution.java b/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestExecution.java index 51372bc049..fa71392795 100644 --- a/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestExecution.java +++ b/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestExecution.java @@ -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; @@ -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)); + } + } } diff --git a/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestMethodExecution.java b/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestMethodExecution.java index 2fdda33f16..c3b5583108 100644 --- a/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestMethodExecution.java +++ b/archunit-junit/junit4/src/main/java/com/tngtech/archunit/junit/internal/ArchTestMethodExecution.java @@ -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; @@ -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); } diff --git a/archunit-junit/junit5/engine/build.gradle b/archunit-junit/junit5/engine/build.gradle index 58abbe8154..b2f668a6dc 100644 --- a/archunit-junit/junit5/engine/build.gradle +++ b/archunit-junit/junit5/engine/build.gradle @@ -10,9 +10,13 @@ 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 testImplementation project(path: ':archunit', configuration: 'tests') testImplementation dependency.assertj diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java index 8981ccee06..5e534e46c4 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.junit.internal; +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; @@ -23,4 +24,14 @@ class ArchUnitEngineDescriptor extends EngineDescriptor implements Node 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).", @@ -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 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 classes, Field field) { + private void resolveField(ElementResolver resolver, Supplier 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 classes, Method method) { + private void resolveMethod(ElementResolver resolver, Supplier 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 classes) { + TestDescriptor parent, ElementResolver resolver, Field field, Supplier 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); + } } } @@ -117,16 +127,18 @@ private static T getValue(Field field) { } private static void resolveArchRules( - TestDescriptor parent, ElementResolver resolver, Field field, Supplier classes) { + TestDescriptor parent, ElementResolver resolver, Field field, Supplier 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); }); } @@ -194,6 +206,7 @@ public Type getType() { @Override public ArchUnitEngineExecutionContext execute(ArchUnitEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { + invokeMethod(method, method.getDeclaringClass(), classes.get()); return context; } @@ -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 diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java index 0d426bc961..475c133eab 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java @@ -18,9 +18,12 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.tngtech.archunit.Internal; @@ -30,6 +33,7 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.junit.engine_api.FieldSelector; +import com.tngtech.archunit.junit.internal.filtering.TestSourceFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.Filter; @@ -43,6 +47,7 @@ import org.junit.platform.engine.discovery.PackageSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllFields; import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllMethods; @@ -67,6 +72,22 @@ public final class ArchUnitTestEngine extends HierarchicalTestEngine { static final String UNIQUE_ID = "archunit"; + private static final Collection ABORTING_THROWABLE_NAMES = Arrays.asList( + "org.junit.internal.AssumptionViolatedException", + "org.junit.AssumptionViolatedException", + "org.opentest4j.TestAbortedException" + ); + + private final Collection> abortingThrowables; + + public ArchUnitTestEngine() { + abortingThrowables = ABORTING_THROWABLE_NAMES.stream() + .map(this::maybeLoadClass) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + } + private SharedCache cache = new SharedCache(); // NOTE: We want to change this in tests -> no static/final reference @Override @@ -77,6 +98,7 @@ public String getId() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { ArchUnitEngineDescriptor result = new ArchUnitEngineDescriptor(uniqueId); + result.setAdditionalFilter(TestSourceFilter.forRequest(discoveryRequest)); resolveRequestedClasspathRoot(discoveryRequest, uniqueId, result); resolveRequestedPackages(discoveryRequest, uniqueId, result); @@ -93,7 +115,7 @@ private void resolveRequestedClasspathRoot(EngineDiscoveryRequest discoveryReque .flatMap(this::getContainedClasses); filterCandidatesAndLoadClasses(classes, discoveryRequest) .forEach(clazz -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, clazz), cache.get())); + result, ElementResolver.create(result, uniqueId, clazz), cache.get(), result.getAdditionalFilter())); } private void resolveRequestedPackages(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) { @@ -104,7 +126,7 @@ private void resolveRequestedPackages(EngineDiscoveryRequest discoveryRequest, U filterCandidatesAndLoadClasses(classes, discoveryRequest) .forEach(clazz -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, clazz), cache.get())); + result, ElementResolver.create(result, uniqueId, clazz), cache.get(), result.getAdditionalFilter())); } private Stream> filterCandidatesAndLoadClasses(Stream classes, EngineDiscoveryRequest discoveryRequest) { @@ -119,28 +141,30 @@ private void resolveRequestedClasses(EngineDiscoveryRequest discoveryRequest, Un .map(ClassSelector::getJavaClass) .filter(this::isArchUnitTestCandidate) .forEach(clazz -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, clazz), cache.get())); + result, ElementResolver.create(result, uniqueId, clazz), cache.get(), result.getAdditionalFilter())); } private void resolveRequestedMethods(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) { discoveryRequest.getSelectorsByType(MethodSelector.class).stream() .filter(s -> s.getJavaMethod().isAnnotationPresent(ArchTest.class)) .forEach(selector -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, selector.getJavaClass(), selector.getJavaMethod()), cache.get())); + result, ElementResolver.create(result, uniqueId, selector.getJavaClass(), selector.getJavaMethod()), cache.get(), + result.getAdditionalFilter())); } private void resolveRequestedFields(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) { discoveryRequest.getSelectorsByType(FieldSelector.class).stream() .filter(s -> s.getJavaField().isAnnotationPresent(ArchTest.class)) .forEach(selector -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, selector.getJavaClass(), selector.getJavaField()), cache.get())); + result, ElementResolver.create(result, uniqueId, selector.getJavaClass(), selector.getJavaField()), cache.get(), + result.getAdditionalFilter())); } private void resolveRequestedUniqueIds(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) { discoveryRequest.getSelectorsByType(UniqueIdSelector.class).stream() .filter(selector -> selector.getUniqueId().getEngineId().equals(Optional.of(getId()))) .forEach(selector -> ArchUnitTestDescriptor.resolve( - result, ElementResolver.create(result, uniqueId, selector.getUniqueId()), cache.get())); + result, ElementResolver.create(result, uniqueId, selector.getUniqueId()), cache.get(), result.getAdditionalFilter())); } private Stream getContainedClasses(String[] packages) { @@ -191,12 +215,27 @@ private URL toUrl(URI uri) { } } + @Override + protected ThrowableCollector.Factory createThrowableCollectorFactory(ExecutionRequest request) { + return () -> new ThrowableCollector(throwable -> abortingThrowables.stream() + .anyMatch(abortingThrowable -> abortingThrowable.isInstance(throwable))); + } + @Override protected ArchUnitEngineExecutionContext createExecutionContext(ExecutionRequest request) { return new ArchUnitEngineExecutionContext(); } + private Optional> maybeLoadClass(String name) { + try { + return Optional.of(Class.forName(name)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } + static class SharedCache { + private static final ClassCache cache = new ClassCache(); ClassCache get() { diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java index 39a97ab7de..6ac8de4ee8 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java @@ -15,6 +15,8 @@ */ package com.tngtech.archunit.junit.internal; +import com.tngtech.archunit.junit.internal.filtering.TestSourceFilter; + interface CreatesChildren { - void createChildren(ElementResolver resolver); + void createChildren(ElementResolver resolver, TestSourceFilter filter); } diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java new file mode 100644 index 0000000000..98cecfc46e --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java @@ -0,0 +1,105 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering;/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestSource; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.PostDiscoveryFilter; + +public abstract class AbstractTestNameFilter implements TestSourceFilter { + private static final Collection SELECTOR_FACTORIES = Arrays.asList( + new MethodSelectorFactory(), + new FieldSelectorFactory() + ); + private final String discoveryFilterClassName; + + public AbstractTestNameFilter(EngineDiscoveryRequest request, String discoveryFilterClassName) throws Exception { + this.discoveryFilterClassName = discoveryFilterClassName; + PostDiscoveryFilter postDiscoveryFilter = findPostDiscoveryFilter(request); + PostDiscoveryFilter replacement = initialize(postDiscoveryFilter); + if (replacement != postDiscoveryFilter) { + replaceFilter(request, postDiscoveryFilter, replacement); + } + } + + private void replaceFilter( + EngineDiscoveryRequest discoveryRequest, + PostDiscoveryFilter postDiscoveryFilter, + PostDiscoveryFilter replacement) throws ReflectiveOperationException { + LauncherDiscoveryRequest request = (LauncherDiscoveryRequest) discoveryRequest; + List filters = new ArrayList<>(request.getPostDiscoveryFilters()); + filters.remove(postDiscoveryFilter); + filters.add(replacement); + ReflectionUtils.setField(discoveryRequest, "postDiscoveryFilters", filters); + } + + public boolean shouldRun(TestSource source) { + return resolveFactory(source) + .map(factory -> factory.createSelector(source)) + .map(this::shouldRunAccordingToTestingTool) + .orElse(true); + } + + public static boolean checkApplicability(EngineDiscoveryRequest discoveryRequest, String discoveryFilterClassName) { + if (!(discoveryRequest instanceof LauncherDiscoveryRequest)) { + return false; + } + LauncherDiscoveryRequest request = (LauncherDiscoveryRequest) discoveryRequest; + return request.getPostDiscoveryFilters().stream() + .anyMatch(filter -> filter.getClass().getName().equals(discoveryFilterClassName)); + } + + private PostDiscoveryFilter findPostDiscoveryFilter(EngineDiscoveryRequest request) { + return ((LauncherDiscoveryRequest) request).getPostDiscoveryFilters().stream() + .filter(this::matches) + .findAny().get(); + } + + boolean matches(PostDiscoveryFilter filter) { + return discoveryFilterClassName.equals(filter.getClass().getName()); + } + + protected abstract PostDiscoveryFilter initialize(PostDiscoveryFilter filter) throws Exception; + + protected abstract boolean shouldRunAccordingToTestingTool(TestSelectorFactory.TestSelector selector); + + private Optional resolveFactory(TestSource source) { + return SELECTOR_FACTORIES.stream() + .filter(factory -> factory.supports(source)) + .findAny(); + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/FieldSelectorFactory.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/FieldSelectorFactory.java new file mode 100644 index 0000000000..e81e38b178 --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/FieldSelectorFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering;/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.tngtech.archunit.junit.engine_api.FieldSource; +import org.junit.platform.engine.TestSource; + +public class FieldSelectorFactory implements TestSelectorFactory { + @Override + public boolean supports(TestSource testSource) { + return testSource instanceof FieldSource; + } + + @Override + public String getContainerName(TestSource testSource) { + return ((FieldSource) testSource).getClassName(); + } + + @Override + public String getSelectorName(TestSource testSource) { + return ((FieldSource) testSource).getFieldName(); + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/MethodSelectorFactory.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/MethodSelectorFactory.java new file mode 100644 index 0000000000..f7a886e148 --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/MethodSelectorFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering;/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.support.descriptor.MethodSource; + +public class MethodSelectorFactory implements TestSelectorFactory { + @Override + public boolean supports(TestSource source) { + return source instanceof MethodSource; + } + + @Override + public String getContainerName(TestSource source) { + return ((MethodSource) source).getClassName(); + } + + @Override + public String getSelectorName(TestSource source) { + return ((MethodSource) source).getMethodName(); + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ReflectionUtils.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ReflectionUtils.java new file mode 100644 index 0000000000..192f9167aa --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ReflectionUtils.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public abstract class ReflectionUtils { + + private ReflectionUtils() {} + + @SuppressFBWarnings("REFLF_REFLECTION_MAY_INCREASE_ACCESSIBILITY_OF_FIELD") + public static void setField(Object target, String fieldName, Object value) throws ReflectiveOperationException { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + makeNonFinal(field); + field.set(target, value); + } + + private static void makeNonFinal(Field field) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { + if (Modifier.isFinal(field.getModifiers())) { + Field modifiersField = getModifiersField(); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } + } + + private static Field getModifiersField() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { + try { + return Field.class.getDeclaredField("modifiers"); + } catch (NoSuchFieldException e) { //JDK 9+ + Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + getDeclaredFields0.setAccessible(true); + Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); + for (Field field : fields) { + if ("modifiers".equals(field.getName())) { + return field; + } + } + throw new NoSuchFieldException(); + } + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java new file mode 100644 index 0000000000..e19a0f6f7c --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering;/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Objects; + +import org.junit.platform.engine.TestSource; + +public interface TestSelectorFactory { + + boolean supports(TestSource source); + + String getContainerName(TestSource source); + + String getSelectorName(TestSource source); + + default TestSelector createSelector(TestSource source) { + return new TestSelector( + getContainerName(source), + getSelectorName(source)); + } + + /** + * Represents a single test case selector + * (e.g. a fully-qualified class name + test-annotated method name) + */ + class TestSelector { + private final String containerName; + private final String selectorName; + + public TestSelector(String containerName, String selectorName) { + this.containerName = containerName; + this.selectorName = selectorName; + } + + public String getContainerName() { + return containerName; + } + + public String getSelectorName() { + return selectorName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TestSelector that = (TestSelector) o; + return containerName.equals(that.containerName) && Objects.equals(selectorName, that.selectorName); + } + + @Override + public int hashCode() { + return Objects.hash(containerName, selectorName); + } + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java new file mode 100644 index 0000000000..1a2f345fea --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering; + +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestSource; + +@FunctionalInterface +public interface TestSourceFilter { + TestSourceFilter NOOP = (descriptor -> true); + + default boolean shouldRun(TestDescriptor descriptor) { + return descriptor.getSource() + .map(this::shouldRun) + .orElse(true); + } + + boolean shouldRun(TestSource source); + + static TestSourceFilter forRequest(EngineDiscoveryRequest discoveryRequest) { + return TestSourceFilter.NOOP; + } +} diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ReflectionUtils.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ReflectionUtils.java index e05bf41dad..f419d6f182 100644 --- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ReflectionUtils.java +++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/internal/ReflectionUtils.java @@ -21,20 +21,31 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.tngtech.archunit.base.ArchUnitException.ReflectionException; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; -class ReflectionUtils { +public class ReflectionUtils { + + private static final com.google.common.base.Function> EXTRACTING_CLASS = new com.google.common.base.Function>() { + @Override + public Class apply(Object input) { + return input.getClass(); + } + }; + private ReflectionUtils() { } @@ -82,7 +93,7 @@ private static T getValue(Field field, Object owner) { } } - static T getValueOrThrowException(Field field, Class fieldOwner, Function exceptionConverter) { + public static T getValueOrThrowException(Field field, Class fieldOwner, Function exceptionConverter) { try { if (Modifier.isStatic(field.getModifiers())) { return getValue(field, null); @@ -96,14 +107,14 @@ static T getValueOrThrowException(Field field, Class fieldOwner, Function static T invokeMethod(Method method, Class methodOwner, Object... args) { if (Modifier.isStatic(method.getModifiers())) { - return invoke(null, method, args); + return invokeMethod(null, method, args); } else { - return invoke(newInstanceOf(methodOwner), method, args); + return invokeMethod(newInstanceOf(methodOwner), method, args); } } @SuppressWarnings("unchecked") // callers must know, what they do here, we can't make this compile safe anyway - private static T invoke(Object owner, Method method, Object... args) { + public static T invokeMethod(Object owner, Method method, Object... args) { method.setAccessible(true); try { return (T) method.invoke(owner, args); @@ -115,6 +126,22 @@ private static T invoke(Object owner, Method method, Object... args) { } } + public static T invokeMethod(Object owner, String methodName, Object... args) { + return invokeMethod(owner, Objects.requireNonNull(findMethod(owner, methodName, args)), args); + } + + private static Method findMethod(Object owner, String methodName, Object[] args) { + try { + return owner.getClass().getMethod( + methodName, + Iterables.toArray(Iterables.transform(Arrays.asList(args), EXTRACTING_CLASS), Class.class) + ); + } catch (NoSuchMethodException e) { + ReflectionUtils.rethrowUnchecked(e); + return null; // will never be reached + } + } + // Certified Hack(TM) to rethrow any exception unchecked. Uses a hole in the JLS with respect to Generics. @SuppressWarnings("unchecked") private static void rethrowUnchecked(Throwable throwable) throws T { diff --git a/archunit-tooling-test/build.gradle b/archunit-tooling-test/build.gradle index 4431f2d8bb..1b959a8a68 100644 --- a/archunit-tooling-test/build.gradle +++ b/archunit-tooling-test/build.gradle @@ -46,7 +46,6 @@ processTestFixturesResources { } dependencies { - implementation project(path: ':archunit-junit5-engine-api') implementation project(path: ':archunit-junit5-engine') implementation project(path: ':archunit-junit4') implementation dependency.junit5JupiterEngine diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/gradle/GradleEngine.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/gradle/GradleEngine.java index 06dfd0c51a..80b912cbc7 100644 --- a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/gradle/GradleEngine.java +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/gradle/GradleEngine.java @@ -10,6 +10,7 @@ import com.tngtech.archunit.tooling.TestFile; import com.tngtech.archunit.tooling.TestReport; import com.tngtech.archunit.tooling.utils.AntReportParserAdapter; +import com.tngtech.archunit.tooling.utils.JUnitEngineResolver; import com.tngtech.archunit.tooling.utils.TemporaryDirectoryUtils; import org.gradle.tooling.BuildLauncher; import org.gradle.tooling.GradleConnector; @@ -29,6 +30,7 @@ public enum GradleEngine implements TestEngine { private static final String DOT = "."; private final AntReportParserAdapter parser = new AntReportParserAdapter(); + private final JUnitEngineResolver engineResolver = new JUnitEngineResolver(); private final GradleProjectLayout projectLayout; GradleEngine(GradleProjectLayout projectLayout) { @@ -59,7 +61,8 @@ private BuildLauncher prepareLauncher(TestFile testFile, ProjectConnection conne .withArguments( "--debug", "--full-stacktrace", - "-Ppatterns=" + String.join(",", toTestFilterArgument(testFile))); + "-Ppatterns=" + String.join(",", toTestFilterArgument(testFile)), + "-Pengines=" + String.join(",", engineResolver.resolveJUnitEngines(testFile))); } private Collection toTestFilterArgument(TestFile testFile) { diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/surefire/MavenSurefireEngine.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/surefire/MavenSurefireEngine.java index 247e6146b9..0cdb22ba72 100644 --- a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/surefire/MavenSurefireEngine.java +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/surefire/MavenSurefireEngine.java @@ -4,20 +4,17 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Properties; -import javax.annotation.Nonnull; - import com.tngtech.archunit.tooling.TestEngine; import com.tngtech.archunit.tooling.TestFile; import com.tngtech.archunit.tooling.TestReport; import com.tngtech.archunit.tooling.engines.surefire.MavenProjectLayout.MavenProject; import com.tngtech.archunit.tooling.utils.AntReportParserAdapter; +import com.tngtech.archunit.tooling.utils.JUnitEngineResolver; import com.tngtech.archunit.tooling.utils.TemporaryDirectoryUtils; import com.tngtech.archunit.tooling.utils.TemporaryDirectoryUtils.ThrowableFunction; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -43,6 +40,7 @@ public enum MavenSurefireEngine implements TestEngine { private static final Logger LOG = LoggerFactory.getLogger(MavenSurefireEngine.class); private final AntReportParserAdapter parser = new AntReportParserAdapter(); + private final JUnitEngineResolver engineResolver = new JUnitEngineResolver(); private final MavenProjectLayout projectLayout; private final Invoker invoker; @@ -117,26 +115,10 @@ private InvocationRequest prepareInvocationRequest(MavenProject mavenProject, Te private Properties propertiesForTest(TestFile testFile) { Properties result = new Properties(); result.setProperty("test", toIncludePattern(testFile)); - result.setProperty("surefire.includeJUnit5Engines", String.join(",", resolveJUnitEngines(testFile))); + result.setProperty("surefire.includeJUnit5Engines", String.join(",", engineResolver.resolveJUnitEngines(testFile))); return result; } - @Nonnull - private List resolveJUnitEngines(TestFile testFile) { - /* TODO configuration issue: - If archunit and junit-vintage are both included, then ArchUnit tests are being run twice (since they are discoverable by both engines). - This behavior should either be suppressed somehow or, at the very least, clearly stated in the docs. - - (the former could be done by having ArchUnitTestEngine detect if JUnit Vintage is configured to run, and if so - skip JUnit 4 tests - during discovery. There does not, however, seem to exist a foolproof way of detecting whether a given engine is configured to run) - */ - if (TestFile.TestingFramework.JUNIT4.equals(testFile.getTestingFramework()) - && testFile.getFixture().getSimpleName().contains("Arch")) { - return Arrays.asList("junit-jupiter", "archunit"); - } - return Arrays.asList("junit-jupiter", "junit-vintage", "archunit"); - } - private String toIncludePattern(TestFile testFile) { String result = testFile.getFixture().getSimpleName(); if (testFile.hasTestCasesFilter()) { diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/utils/JUnitEngineResolver.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/utils/JUnitEngineResolver.java new file mode 100644 index 0000000000..00a228d441 --- /dev/null +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/utils/JUnitEngineResolver.java @@ -0,0 +1,27 @@ +package com.tngtech.archunit.tooling.utils; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nonnull; + +import com.tngtech.archunit.tooling.TestFile; + +public class JUnitEngineResolver { + + @Nonnull + public List resolveJUnitEngines(TestFile testFile) { + /* TODO configuration issue: + If archunit and junit-vintage are both included, then ArchUnit tests are being run twice (since they are discoverable by both engines). + This behavior should either be suppressed somehow or, at the very least, clearly stated in the docs. + + (the former could be done by having ArchUnitTestEngine detect if JUnit Vintage is configured to run, and if so - skip JUnit 4 tests + during discovery. There does not, however, seem to exist a foolproof way of detecting whether a given engine is configured to run) + */ + if (TestFile.TestingFramework.JUNIT4.equals(testFile.getTestingFramework()) + && testFile.getFixture().getSimpleName().contains("Arch")) { + return Arrays.asList("junit-jupiter", "archunit"); + } + return Arrays.asList("junit-jupiter", "junit-vintage", "archunit"); + } +} diff --git a/archunit-tooling-test/src/testFixtures/resources/project/build.gradle b/archunit-tooling-test/src/testFixtures/resources/project/build.gradle index 6d61223a59..2a6617994e 100644 --- a/archunit-tooling-test/src/testFixtures/resources/project/build.gradle +++ b/archunit-tooling-test/src/testFixtures/resources/project/build.gradle @@ -16,7 +16,9 @@ dependencies { } test { - useJUnitPlatform() + useJUnitPlatform { + includeEngines((project.getProperty('engines') as String).split(',')) + } jvmArgs = ['--add-opens', 'java.base/java.util=ALL-UNNAMED', '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.base/java.lang.reflect=ALL-UNNAMED'] ignoreFailures = true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..00e33edef6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 482fac8321535cee8cf483c27b5abf3432b67b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Thu, 23 Jun 2022 15:50:22 +0200 Subject: [PATCH 02/10] Base filtering mechanism (2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../internal/ArchUnitEngineDescriptor.java | 11 ++++- .../ArchUnitEngineExecutionContext.java | 5 +++ .../junit/internal/ArchUnitTestEngine.java | 2 +- .../filtering/AbstractTestNameFilter.java | 40 +++++++++++++++---- .../internal/filtering/TestSourceFilter.java | 4 +- 5 files changed, 50 insertions(+), 12 deletions(-) diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java index 5e534e46c4..f50c176e50 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java @@ -15,13 +15,16 @@ */ 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 { - ArchUnitEngineDescriptor(UniqueId uniqueId) { +public class ArchUnitEngineDescriptor extends EngineDescriptor implements Node { + public ArchUnitEngineDescriptor(UniqueId uniqueId) { super(uniqueId, "ArchUnit JUnit 5"); } @@ -34,4 +37,8 @@ public void setAdditionalFilter(TestSourceFilter additionalFilter) { public TestSourceFilter getAdditionalFilter() { return additionalFilter; } + + public Properties getConfiguration() { + return ArchConfiguration.get().getSubProperties(ArchConfiguration.JUNIT_PREFIX); + } } diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java index a6ca8b63f1..05992c05ec 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java @@ -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(); + } } diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java index 475c133eab..7a3fde6c42 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java @@ -98,7 +98,7 @@ public String getId() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { ArchUnitEngineDescriptor result = new ArchUnitEngineDescriptor(uniqueId); - result.setAdditionalFilter(TestSourceFilter.forRequest(discoveryRequest)); + result.setAdditionalFilter(TestSourceFilter.forRequest(discoveryRequest, result)); resolveRequestedClasspathRoot(discoveryRequest, uniqueId, result); resolveRequestedPackages(discoveryRequest, uniqueId, result); diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java index 98cecfc46e..c9bb0d68c1 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java @@ -47,12 +47,34 @@ public abstract class AbstractTestNameFilter implements TestSourceFilter { ); private final String discoveryFilterClassName; - public AbstractTestNameFilter(EngineDiscoveryRequest request, String discoveryFilterClassName) throws Exception { + public AbstractTestNameFilter(EngineDiscoveryRequest request, String discoveryFilterClassName) { this.discoveryFilterClassName = discoveryFilterClassName; - PostDiscoveryFilter postDiscoveryFilter = findPostDiscoveryFilter(request); - PostDiscoveryFilter replacement = initialize(postDiscoveryFilter); - if (replacement != postDiscoveryFilter) { - replaceFilter(request, postDiscoveryFilter, replacement); + Optional postDiscoveryFilter = findPostDiscoveryFilter(request); + Optional replacement = postDiscoveryFilter.flatMap(this::throwingInitialize); + replacement.ifPresent( + newFilter -> postDiscoveryFilter.ifPresent( + oldFilter -> replaceFilterIfNeeded(request, newFilter, oldFilter))); + } + + public AbstractTestNameFilter(EngineDiscoveryRequest request) { + this(request, ""); + } + + private void replaceFilterIfNeeded(EngineDiscoveryRequest request, PostDiscoveryFilter newFilter, PostDiscoveryFilter oldFilter) { + if (!oldFilter.equals(newFilter)) { + try { + replaceFilter(request, oldFilter, newFilter); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + + private Optional throwingInitialize(PostDiscoveryFilter filter) { + try { + return initialize(filter); + } catch (Exception e) { + throw new RuntimeException(e); } } @@ -83,17 +105,19 @@ public static boolean checkApplicability(EngineDiscoveryRequest discoveryRequest .anyMatch(filter -> filter.getClass().getName().equals(discoveryFilterClassName)); } - private PostDiscoveryFilter findPostDiscoveryFilter(EngineDiscoveryRequest request) { + private Optional findPostDiscoveryFilter(EngineDiscoveryRequest request) { return ((LauncherDiscoveryRequest) request).getPostDiscoveryFilters().stream() .filter(this::matches) - .findAny().get(); + .findAny(); } boolean matches(PostDiscoveryFilter filter) { return discoveryFilterClassName.equals(filter.getClass().getName()); } - protected abstract PostDiscoveryFilter initialize(PostDiscoveryFilter filter) throws Exception; + protected Optional initialize(PostDiscoveryFilter filter) { + return Optional.empty(); + } protected abstract boolean shouldRunAccordingToTestingTool(TestSelectorFactory.TestSelector selector); diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java index 1a2f345fea..393fdbc9de 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java @@ -15,6 +15,8 @@ */ package com.tngtech.archunit.junit.internal.filtering; +import com.tngtech.archunit.junit.internal.ArchUnitEngineDescriptor; +import com.tngtech.archunit.junit.internal.ArchUnitTestEngine; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; @@ -31,7 +33,7 @@ default boolean shouldRun(TestDescriptor descriptor) { boolean shouldRun(TestSource source); - static TestSourceFilter forRequest(EngineDiscoveryRequest discoveryRequest) { + static TestSourceFilter forRequest(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor) { return TestSourceFilter.NOOP; } } From 5849332f19ac0f702434b95dbea91bad6c653c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Thu, 23 Jun 2022 18:21:46 +0200 Subject: [PATCH 03/10] Support for filtering via system properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- archunit-junit/junit5/engine/build.gradle | 8 +- .../ArchUnitPropsTestSourceFilter.java | 137 ++++++++++++++ .../filtering/TestSelectorFactory.java | 4 + .../internal/filtering/TestSourceFilter.java | 12 ++ .../ArchUnitPropsTestSourceFilterTest.java | 177 ++++++++++++++++++ .../java/com/another/AnotherClass.java | 19 ++ .../java/com/example/AnotherClass.java | 15 ++ .../java/com/example/SomeClass.java | 19 ++ .../tngtech/archunit/ArchConfiguration.java | 9 + .../DependencyResolutionProcessTestUtils.java | 4 +- 10 files changed, 396 insertions(+), 8 deletions(-) create mode 100644 archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java create mode 100644 archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java create mode 100644 archunit-junit/junit5/engine/src/testFixtures/java/com/another/AnotherClass.java create mode 100644 archunit-junit/junit5/engine/src/testFixtures/java/com/example/AnotherClass.java create mode 100644 archunit-junit/junit5/engine/src/testFixtures/java/com/example/SomeClass.java diff --git a/archunit-junit/junit5/engine/build.gradle b/archunit-junit/junit5/engine/build.gradle index b2f668a6dc..4b5fceb5f2 100644 --- a/archunit-junit/junit5/engine/build.gradle +++ b/archunit-junit/junit5/engine/build.gradle @@ -1,10 +1,11 @@ plugins { id 'archunit.java-release-conventions' + id 'java-test-fixtures' } ext.moduleName = 'com.tngtech.archunit.junit5.engine' -ext.minimumJavaVersion = JavaVersion.VERSION_1_8 +ext.minimumJavaVersion = JavaVersion.VERSION_1_9 dependencies { api project(path: ':archunit') @@ -22,6 +23,7 @@ dependencies { testImplementation dependency.assertj testImplementation dependency.mockito testImplementation dependency.junit5JupiterApi + testImplementation dependency.junitJupiterParams testRuntimeOnly dependency.junit5JupiterEngine } @@ -66,7 +68,3 @@ def configureDependencies = { deps -> } } this.with project(':archunit-junit').configureJUnitArchive(configureDependencies) - -singlePackageExport { - exportedPackage = 'com.tngtech.archunit.junit.internal' -} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java new file mode 100644 index 0000000000..0a7ba0ccfe --- /dev/null +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java @@ -0,0 +1,137 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.junit.internal.filtering; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.google.common.base.Strings; +import com.tngtech.archunit.ArchConfiguration; +import com.tngtech.archunit.junit.internal.ArchUnitEngineDescriptor; +import com.tngtech.archunit.junit.internal.filtering.TestSelectorFactory.TestSelector; +import org.junit.platform.engine.EngineDiscoveryRequest; + +public class ArchUnitPropsTestSourceFilter extends AbstractTestNameFilter { + + private static final String ARCHUNIT_PREFIX = "archunit"; + public static final Predicate NON_EMPTY_STRING = ((Predicate) Strings::isNullOrEmpty).negate(); + private final SelectorMatcher include; + private final SelectorMatcher exclude; + + public ArchUnitPropsTestSourceFilter(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor) { + super(discoveryRequest); + this.include = buildMatcher(discoveryRequest, engineDescriptor, ArchConfiguration.JUNIT_INCLUDE_TESTS_MATCHING, SelectorMatcher.ACCEPT_ALL); + this.exclude = buildMatcher(discoveryRequest, engineDescriptor, ArchConfiguration.JUNIT_EXCLUDE_TESTS_MATCHING, SelectorMatcher.EMPTY); + } + + @Override + protected boolean shouldRunAccordingToTestingTool(TestSelector selector) { + return include.matches(selector) && !exclude.matches(selector); + } + + private SelectorMatcher buildMatcher(EngineDiscoveryRequest discoveryRequest, + ArchUnitEngineDescriptor engineDescriptor, + String property, SelectorMatcher + defaultMatcher) { + return readProperty(discoveryRequest, engineDescriptor, property) + .map(ArchUnitPropsTestSourceFilter::toMatcher) + .orElse(defaultMatcher); + } + + private static SelectorMatcher toMatcher(String commaSeparatedPatterns) { + return new SelectorMatcher(toPatterns(commaSeparatedPatterns)); + } + + private static String[] toPatterns(String commaSeparatedPatterns) { + return commaSeparatedPatterns.split(","); + } + + + public static boolean appliesTo(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor) { + return isProvided(discoveryRequest, engineDescriptor, ArchConfiguration.JUNIT_INCLUDE_TESTS_MATCHING) + || isProvided(discoveryRequest, engineDescriptor, ArchConfiguration.JUNIT_EXCLUDE_TESTS_MATCHING); + } + + private static boolean isProvided(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor, String key) { + return readProperty(discoveryRequest, engineDescriptor, key).isPresent(); + } + + private static Optional readProperty(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor, String key) { + return readFromConfigurationParams(discoveryRequest, key) + .or(() -> ArchUnitPropsTestSourceFilter.readFromEngineDescriptor(engineDescriptor, key)); + } + + private static Optional readFromEngineDescriptor(ArchUnitEngineDescriptor engineDescriptor, String key) { + return Optional.ofNullable(engineDescriptor.getConfiguration().getOrDefault(key, null)) + .map(String.class::cast) + .filter(NON_EMPTY_STRING); + } + + private static Optional readFromConfigurationParams(EngineDiscoveryRequest discoveryRequest, String key) { + return discoveryRequest.getConfigurationParameters().get(toSystemPropertyName(key)) + .filter(NON_EMPTY_STRING); + } + + private static String toSystemPropertyName(String key) { + return String.join(".", ARCHUNIT_PREFIX, ArchConfiguration.JUNIT_PREFIX, key); + } + + private static class SelectorMatcher { + + private static final String SINGLE_ESCAPED_ASTERISK_STRING = "\\\\*"; + private static final Pattern MULTIPLE_ESCAPED_ASTERISK = Pattern.compile("(\\\\\\*)+"); + private static final Pattern SINGLE_ESCAPED_ASTERISK = Pattern.compile("\\*", Pattern.LITERAL); + + private static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]"); + private static final SelectorMatcher EMPTY = new SelectorMatcher(new String[0]); + private static final SelectorMatcher ACCEPT_ALL = new SelectorMatcher(new String[] {"*"}); + private static final String ESCAPED_INPUT = "\\\\$0"; + private static final String MATCH_ALL_CHARACTERS = "\\.\\*"; + + private final Collection patterns; + + public SelectorMatcher(String[] patterns) { + this.patterns = prepareRegexes(patterns); + } + + private Collection prepareRegexes(String[] patterns) { + return Arrays.stream(patterns) + .map(this::prepareRegex) + .collect(Collectors.toList()); + } + + private Pattern prepareRegex(String pattern) { + String escaped = SPECIAL_REGEX_CHARS.matcher(pattern).replaceAll(ESCAPED_INPUT); + escaped = MULTIPLE_ESCAPED_ASTERISK.matcher(escaped).replaceAll(SINGLE_ESCAPED_ASTERISK_STRING); + escaped = SINGLE_ESCAPED_ASTERISK.matcher(escaped).replaceAll(MATCH_ALL_CHARACTERS); + return Pattern.compile("(?:^|\\.)" + escaped + "$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + } + + boolean matches(TestSelector selector) { + return patterns.stream().anyMatch(pattern -> matchesSelector(pattern, selector)); + } + + private boolean matchesSelector(Pattern pattern, TestSelector selector) { + return + pattern.matcher(selector.getFullyQualifiedName()).matches() + || pattern.matcher(selector.getContainerName()).matches(); + } + } +} diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java index e19a0f6f7c..0209f158f1 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSelectorFactory.java @@ -68,6 +68,10 @@ public String getSelectorName() { return selectorName; } + public String getFullyQualifiedName() { + return getContainerName() + "." + getSelectorName(); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java index 393fdbc9de..d1ed646e96 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java @@ -20,9 +20,13 @@ import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @FunctionalInterface public interface TestSourceFilter { + Logger LOG = LoggerFactory.getLogger(ArchUnitTestEngine.class); + TestSourceFilter NOOP = (descriptor -> true); default boolean shouldRun(TestDescriptor descriptor) { @@ -34,6 +38,14 @@ default boolean shouldRun(TestDescriptor descriptor) { boolean shouldRun(TestSource source); static TestSourceFilter forRequest(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor) { + try { + if (ArchUnitPropsTestSourceFilter.appliesTo(discoveryRequest, engineDescriptor)) { + return new ArchUnitPropsTestSourceFilter(discoveryRequest, engineDescriptor); + } + } catch (Exception e) { + LOG.warn("Received error trying to apply test name filter from testing tool", e); + } + return TestSourceFilter.NOOP; } } diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java new file mode 100644 index 0000000000..6cf92b909d --- /dev/null +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java @@ -0,0 +1,177 @@ +package com.tngtech.archunit.junit.internal.filtering; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.tngtech.archunit.ArchConfiguration; +import com.tngtech.archunit.junit.engine_api.FieldSelector; +import com.tngtech.archunit.junit.engine_api.FieldSource; +import com.tngtech.archunit.junit.internal.ArchUnitEngineDescriptor; +import com.tngtech.archunit.junit.internal.testutil.MockitoExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ArgumentConversionException; +import org.junit.jupiter.params.converter.ArgumentConverter; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.mockito.Mock; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ArchUnitPropsTestSourceFilterTest { + + @Mock + public LauncherDiscoveryRequest discoveryRequest; + + @Mock + public ConfigurationParameters configurationParameters; + + @BeforeEach + public void setup() { + when(discoveryRequest.getConfigurationParameters()).thenReturn(configurationParameters); + } + + @Nested + class Reads { + + @Test + void filters_from_archunit_config() { + testFiltering(() -> mockIncludeFilterInArchConfig("com.example.SomeClass.*"), + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#someField")), + List.of( + TestSelectorConverter.toTestSelector("com.example.AnotherClass#someField"))); + } + + @Test + void filters_from_discovery_request() { + testFiltering(() -> mockIncludeFilterInConfigParams("com.example.SomeClass.*"), + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#someField")), + List.of( + TestSelectorConverter.toTestSelector("com.example.AnotherClass#someField"))); + } + } + + @Nested + class Filters { + + @ParameterizedTest(name = "by {0} filter") + @CsvSource(value = { + //"test scenario include filter accepted selector rejected selector", + "simple class name, com.example.SomeClass, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "FQ class name, com.example.SomeClass, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "FQ class and field name, com.example.SomeClass.someField, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "simple class and field name, com.example.SomeClass.someField, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "package name, com.example.*, com.example.SomeClass#someField, com.another.AnotherClass#someField", + "partial wildcard class name, com.example.An*Class, com.example.AnotherClass#someField, com.example.SomeClass#someField", + "wildcard field name, com.example.SomeClass.*, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "partial wildcard field name, com.example.SomeClass.some*, com.example.SomeClass#someField, com.example.SomeClass#anotherField" + }) + void by_filter_type(String testScenario, + String includeFilter, + @ConvertWith(TestSelectorConverter.class) TestSource acceptedSelector, + @ConvertWith(TestSelectorConverter.class) TestSource rejectedSelector) { + testFiltering(includeFilter, null, Collections.singleton(acceptedSelector), Collections.singleton(rejectedSelector)); + } + + } + + @Nested + class Combines { + @Test + void multiple_includes_as_alternatives() { + testFiltering("com.example.SomeClass.someField,com.example.SomeClass.anotherField", null, + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#someField"), + TestSelectorConverter.toTestSelector("com.example.SomeClass#anotherField")), + List.of( + TestSelectorConverter.toTestSelector("com.example.AnotherClass#someField") + )); + } + + @Test + void multiple_excludes_as_conjunction() { + testFiltering(null, "com.example.SomeClass.someField,com.example.SomeClass.anotherField", + List.of( + TestSelectorConverter.toTestSelector("com.example.AnotherClass#someField")), + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#someField"), + TestSelectorConverter.toTestSelector("com.example.SomeClass#anotherField"))); + } + + @Test + void includes_and_excludes_by_resolving_against_both() { + testFiltering("com.example.SomeClass.*", "com.example.SomeClass.anotherField", + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#someField")), + List.of( + TestSelectorConverter.toTestSelector("com.example.SomeClass#anotherField"), + TestSelectorConverter.toTestSelector("com.example.AnotherClass#someField"))); + } + } + + private void testFiltering( + String includeFilter, + String excludeFilter, + Collection acceptedSelectors, + Collection rejectedSelectors) { + testFiltering(() -> { + mockIncludeFilterInConfigParams(includeFilter); + mockExcludeFilterInConfigParams(excludeFilter); + }, acceptedSelectors, rejectedSelectors); + } + + private void testFiltering(Runnable setup, Collection acceptedSelectors, Collection rejectedSelectors) { + // given + setup.run(); + ArchUnitPropsTestSourceFilter filter = new ArchUnitPropsTestSourceFilter(discoveryRequest, + new ArchUnitEngineDescriptor(UniqueId.forEngine("archunit"))); + + // when + Collection acceptedResults = acceptedSelectors.stream().filter(source -> !filter.shouldRun(source)).collect(Collectors.toList()); + Collection rejectedResults = rejectedSelectors.stream().filter(filter::shouldRun).collect(Collectors.toList()); + + // then + assertThat(acceptedResults).as("Wrongly rejected results").isEmpty(); + assertThat(rejectedResults).as("Wrongly accepted results").isEmpty(); + } + + private void mockIncludeFilterInConfigParams(String filter) { + when(discoveryRequest.getConfigurationParameters().get("archunit.junit.includeTestsMatching")).thenReturn(Optional.ofNullable(filter)); + } + + private void mockExcludeFilterInConfigParams(String filter) { + when(discoveryRequest.getConfigurationParameters().get("archunit.junit.excludeTestsMatching")).thenReturn(Optional.ofNullable(filter)); + } + + private void mockIncludeFilterInArchConfig(String filter) { + ArchConfiguration.get().setProperty(ArchConfiguration.JUNIT_PREFIX + "." + ArchConfiguration.JUNIT_INCLUDE_TESTS_MATCHING, filter); + } + + private static class TestSelectorConverter implements ArgumentConverter { + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + return toTestSelector((String) source); + } + + static FieldSource toTestSelector(String source) { + String[] segments = source.split("#"); + return FieldSource.from(FieldSelector.selectField(segments[0], segments[1]).getJavaField()); + } + } +} diff --git a/archunit-junit/junit5/engine/src/testFixtures/java/com/another/AnotherClass.java b/archunit-junit/junit5/engine/src/testFixtures/java/com/another/AnotherClass.java new file mode 100644 index 0000000000..15b598ee9a --- /dev/null +++ b/archunit-junit/junit5/engine/src/testFixtures/java/com/another/AnotherClass.java @@ -0,0 +1,19 @@ +package com.another; + +import java.io.Serializable; + +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +public class AnotherClass { + + @ArchTest + public static final ArchRule someField = classes().that().implement(Serializable.class) + .should().implement(Serializable.class); + + @ArchTest + public static final ArchRule anotherField = classes().that().implement(Serializable.class) + .should().implement(Serializable.class); +} diff --git a/archunit-junit/junit5/engine/src/testFixtures/java/com/example/AnotherClass.java b/archunit-junit/junit5/engine/src/testFixtures/java/com/example/AnotherClass.java new file mode 100644 index 0000000000..fe47a224a2 --- /dev/null +++ b/archunit-junit/junit5/engine/src/testFixtures/java/com/example/AnotherClass.java @@ -0,0 +1,15 @@ +package com.example; + +import java.io.Serializable; + +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +public class AnotherClass { + + @ArchTest + public static final ArchRule someField = classes().that().implement(Serializable.class) + .should().implement(Serializable.class); +} diff --git a/archunit-junit/junit5/engine/src/testFixtures/java/com/example/SomeClass.java b/archunit-junit/junit5/engine/src/testFixtures/java/com/example/SomeClass.java new file mode 100644 index 0000000000..5f80491b1a --- /dev/null +++ b/archunit-junit/junit5/engine/src/testFixtures/java/com/example/SomeClass.java @@ -0,0 +1,19 @@ +package com.example; + +import java.io.Serializable; + +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +public class SomeClass { + + @ArchTest + public static final ArchRule someField = classes().that().implement(Serializable.class) + .should().implement(Serializable.class); + + @ArchTest + public static final ArchRule anotherField = classes().that().implement(Serializable.class) + .should().implement(Serializable.class); +} diff --git a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java index f70aadb3cc..8719817554 100644 --- a/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java +++ b/archunit/src/main/java/com/tngtech/archunit/ArchConfiguration.java @@ -46,10 +46,19 @@ public final class ArchConfiguration { public static final String ARCHUNIT_PROPERTIES_RESOURCE_NAME = "archunit.properties"; @Internal // {@value ...} does not work on non public constants outside of the package public static final String RESOLVE_MISSING_DEPENDENCIES_FROM_CLASS_PATH = "resolveMissingDependenciesFromClassPath"; + @Internal + public static final String JUNIT_PREFIX = "junit"; static final String CLASS_RESOLVER = "classResolver"; static final String CLASS_RESOLVER_ARGS = "classResolver.args"; @Internal public static final String ENABLE_MD5_IN_CLASS_SOURCES = "enableMd5InClassSources"; + + @Internal + public static final String JUNIT_INCLUDE_TESTS_MATCHING = "includeTestsMatching"; + + @Internal + public static final String JUNIT_EXCLUDE_TESTS_MATCHING = "excludeTestsMatching"; + private static final String EXTENSION_PREFIX = "extension"; private static final Logger LOG = LoggerFactory.getLogger(ArchConfiguration.class); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/DependencyResolutionProcessTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/DependencyResolutionProcessTestUtils.java index ac8d2cd781..d5de9ae242 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/DependencyResolutionProcessTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/DependencyResolutionProcessTestUtils.java @@ -76,9 +76,7 @@ private void setAllIterationsToZeroExcept(Set propertyNames) { setResolutionProperty(propertyNameToDisable, 0); } - if (number.isPresent()) { - setResolutionProperty(getOnlyElement(propertyNames), number.get()); - } + number.ifPresent(integer -> setResolutionProperty(getOnlyElement(propertyNames), integer)); } private void setResolutionProperty(String propertyName, int number) { From 2252dfbe552c882b5e03867c06fb6ca1f84bf6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Thu, 23 Jun 2022 18:50:23 +0200 Subject: [PATCH 04/10] Fixed matching by simple class name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../ArchUnitPropsTestSourceFilter.java | 4 +- .../ArchUnitPropsTestSourceFilterTest.java | 6 +-- .../engines/jupiter/JUnitJupiterEngine.java | 51 +++++-------------- .../archunit/tooling/ArchUnitToolingTest.java | 5 +- 4 files changed, 22 insertions(+), 44 deletions(-) diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java index 0a7ba0ccfe..fb6067e895 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java @@ -130,8 +130,8 @@ boolean matches(TestSelector selector) { private boolean matchesSelector(Pattern pattern, TestSelector selector) { return - pattern.matcher(selector.getFullyQualifiedName()).matches() - || pattern.matcher(selector.getContainerName()).matches(); + pattern.matcher(selector.getFullyQualifiedName()).find() + || pattern.matcher(selector.getContainerName()).find(); } } } diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java index 6cf92b909d..c9fbe1526c 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilterTest.java @@ -72,10 +72,10 @@ class Filters { @ParameterizedTest(name = "by {0} filter") @CsvSource(value = { //"test scenario include filter accepted selector rejected selector", - "simple class name, com.example.SomeClass, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "simple class name, SomeClass, com.example.SomeClass#someField, com.example.AnotherClass#someField", "FQ class name, com.example.SomeClass, com.example.SomeClass#someField, com.example.AnotherClass#someField", "FQ class and field name, com.example.SomeClass.someField, com.example.SomeClass#someField, com.example.AnotherClass#someField", - "simple class and field name, com.example.SomeClass.someField, com.example.SomeClass#someField, com.example.AnotherClass#someField", + "simple class and field name, SomeClass.someField, com.example.SomeClass#someField, com.example.AnotherClass#someField", "package name, com.example.*, com.example.SomeClass#someField, com.another.AnotherClass#someField", "partial wildcard class name, com.example.An*Class, com.example.AnotherClass#someField, com.example.SomeClass#someField", "wildcard field name, com.example.SomeClass.*, com.example.SomeClass#someField, com.example.AnotherClass#someField", @@ -159,7 +159,7 @@ private void mockExcludeFilterInConfigParams(String filter) { } private void mockIncludeFilterInArchConfig(String filter) { - ArchConfiguration.get().setProperty(ArchConfiguration.JUNIT_PREFIX + "." + ArchConfiguration.JUNIT_INCLUDE_TESTS_MATCHING, filter); + ArchConfiguration.get().setProperty("junit.includeTestsMatching", filter); } private static class TestSelectorConverter implements ArgumentConverter { diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java index 6b1565998d..da9196bea9 100644 --- a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java @@ -1,13 +1,7 @@ package com.tngtech.archunit.tooling.engines.jupiter; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import com.tngtech.archunit.junit.engine_api.FieldSource; import com.tngtech.archunit.tooling.ExecutedTestFile.TestResult; @@ -28,14 +22,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.tngtech.archunit.junit.engine_api.FieldSelector.selectField; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; -import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; public enum JUnitJupiterEngine implements TestEngine { INSTANCE; private static final Logger LOG = LoggerFactory.getLogger(JUnitJupiterEngine.class); + private static final String ARCHUNIT_INCLUDES_PARAMETER_NAME = "archunit.junit.includeTestsMatching"; private final Launcher launcher = LauncherFactory.create(); @@ -48,42 +41,24 @@ public TestReport execute(TestFile testFiles) { } private LauncherDiscoveryRequest toDiscoveryRequest(TestFile testFile) { - List selectors = toSelectors(testFile) - .collect(Collectors.toList()); - LOG.info("Executing request with selectors {}", selectors); - return LauncherDiscoveryRequestBuilder.request() - .selectors(selectors) - .filters() - .build(); - } - - private Stream toSelectors(TestFile testFile) { + DiscoverySelector selector = toTestClassSelector(testFile); + LOG.info("Executing request with selectors {}", selector); + LauncherDiscoveryRequestBuilder builder = LauncherDiscoveryRequestBuilder.request() + .selectors(selector); if (testFile.hasTestCasesFilter()) { - return testFile.getTestCases().stream().map(testCase -> toSelector(testFile.getFixture(), testCase)); + String testCaseFilter = toTestCaseFilter(testFile); + builder = builder.configurationParameter(ARCHUNIT_INCLUDES_PARAMETER_NAME, testCaseFilter); + LOG.info("Executing request with test case filter {}", testCaseFilter); } - return Stream.of(selectClass(testFile.getFixture())); + return builder.build(); } - private DiscoverySelector toSelector(Class fixture, String testCase) { - return getMethodByName(fixture, testCase) - .map(method -> selectMethod(fixture, method)) - .orElseGet(() -> getFieldByName(fixture, testCase) - .map(field -> selectField(fixture, field.getName())) - .orElseThrow(RuntimeException::new)); + private String toTestCaseFilter(TestFile testFile) { + return testFile.getTestCases().stream().map((testFile.getFixture().getSimpleName() + ".")::concat).collect(Collectors.joining(",")); } - private Optional getMethodByName(Class owner, String name) { - return Arrays.stream(owner.getDeclaredMethods()) - .filter(method -> name.equals(method.getName())) - .findFirst(); - } - - private Optional getFieldByName(Class owner, String name) { - try { - return Optional.of(owner.getDeclaredField(name)); - } catch (NoSuchFieldException e) { - return Optional.empty(); - } + private DiscoverySelector toTestClassSelector(TestFile testFile) { + return selectClass(testFile.getFixture()); } private static class TestExecutionCollector implements TestExecutionListener { diff --git a/archunit-tooling-test/src/test/java/com/tngtech/archunit/tooling/ArchUnitToolingTest.java b/archunit-tooling-test/src/test/java/com/tngtech/archunit/tooling/ArchUnitToolingTest.java index a74f9af8a7..134b8b9528 100644 --- a/archunit-tooling-test/src/test/java/com/tngtech/archunit/tooling/ArchUnitToolingTest.java +++ b/archunit-tooling-test/src/test/java/com/tngtech/archunit/tooling/ArchUnitToolingTest.java @@ -3,6 +3,7 @@ import java.util.Map; import java.util.stream.Stream; +import com.tngtech.archunit.tooling.engines.jupiter.JUnitJupiterEngine; import com.tngtech.archunit.tooling.examples.ArchJUnit4SuiteTest; import com.tngtech.archunit.tooling.examples.ArchJUnit4Test; import com.tngtech.archunit.tooling.examples.ArchJUnit5SuiteTest; @@ -11,6 +12,8 @@ import org.junitpioneer.jupiter.cartesian.ArgumentSets; import org.junitpioneer.jupiter.cartesian.CartesianTest; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + public class ArchUnitToolingTest extends BaseTest { @CartesianTest @@ -21,8 +24,8 @@ void shouldReportCorrectTestResults(TestEngine engine, Class fixture) throws @CartesianTest @CartesianTest.MethodFactory("enginesAndFixtures") - @Disabled void shouldOnlyExecuteSelectedTests(TestEngine engine, Class fixture) throws Exception { + assumeTrue(engine instanceof JUnitJupiterEngine); super.shouldOnlyExecuteSelectedTests(engine, fixture); } From f08053f176da38937ed16e81b16a70c11c28b557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Thu, 23 Jun 2022 19:01:03 +0200 Subject: [PATCH 05/10] Fixed test config for JUnit 4 (only execute via Vintage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../engines/jupiter/JUnitJupiterEngine.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java index da9196bea9..db37b5127f 100644 --- a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java @@ -1,13 +1,17 @@ package com.tngtech.archunit.tooling.engines.jupiter; +import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import com.tngtech.archunit.junit.engine_api.FieldSource; import com.tngtech.archunit.tooling.ExecutedTestFile.TestResult; import com.tngtech.archunit.tooling.TestEngine; import com.tngtech.archunit.tooling.TestFile; import com.tngtech.archunit.tooling.TestReport; +import com.tngtech.archunit.tooling.utils.JUnitEngineResolver; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestSource; @@ -17,8 +21,10 @@ import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; +import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,20 +36,33 @@ public enum JUnitJupiterEngine implements TestEngine { private static final Logger LOG = LoggerFactory.getLogger(JUnitJupiterEngine.class); private static final String ARCHUNIT_INCLUDES_PARAMETER_NAME = "archunit.junit.includeTestsMatching"; - private final Launcher launcher = LauncherFactory.create(); + private final JUnitEngineResolver engineResolver = new JUnitEngineResolver(); @Override - public TestReport execute(TestFile testFiles) { - LauncherDiscoveryRequest request = toDiscoveryRequest(testFiles); + public TestReport execute(TestFile testFile) { + Launcher launcher = LauncherFactory.create(LauncherConfig.builder() + .enableTestEngineAutoRegistration(false) + .addTestEngines(manuallyLoadCorrectTestEngines(testFile)) + .build()); + LauncherDiscoveryRequest request = toDiscoveryRequest(testFile); TestExecutionCollector testExecutionCollector = new TestExecutionCollector(); launcher.execute(launcher.discover(request), testExecutionCollector); return testExecutionCollector.getReport(); } + private org.junit.platform.engine.TestEngine[] manuallyLoadCorrectTestEngines(TestFile testFile) { + List engineIds = engineResolver.resolveJUnitEngines(testFile); + Iterable testEngines = new ServiceLoaderTestEngineRegistry().loadTestEngines(); + return StreamSupport.stream(testEngines::spliterator, 0, false) + .filter(engine -> engineIds.contains(engine.getId())) + .toArray(org.junit.platform.engine.TestEngine[]::new); + } + private LauncherDiscoveryRequest toDiscoveryRequest(TestFile testFile) { DiscoverySelector selector = toTestClassSelector(testFile); LOG.info("Executing request with selectors {}", selector); LauncherDiscoveryRequestBuilder builder = LauncherDiscoveryRequestBuilder.request() + .selectors() .selectors(selector); if (testFile.hasTestCasesFilter()) { String testCaseFilter = toTestCaseFilter(testFile); From d064bc204f66a1fdb8d6f77e6ef637798f4c4576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Mon, 27 Jun 2022 10:11:47 +0200 Subject: [PATCH 06/10] Fixed baseline tests, downgraded Java version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- archunit-junit/junit5/engine/build.gradle | 2 +- .../engines/jupiter/JUnitJupiterEngine.java | 50 +++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/archunit-junit/junit5/engine/build.gradle b/archunit-junit/junit5/engine/build.gradle index 4b5fceb5f2..69eb1d25ec 100644 --- a/archunit-junit/junit5/engine/build.gradle +++ b/archunit-junit/junit5/engine/build.gradle @@ -5,7 +5,7 @@ plugins { ext.moduleName = 'com.tngtech.archunit.junit5.engine' -ext.minimumJavaVersion = JavaVersion.VERSION_1_9 +ext.minimumJavaVersion = JavaVersion.VERSION_1_8 dependencies { api project(path: ':archunit') diff --git a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java index db37b5127f..275fefe28f 100644 --- a/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java +++ b/archunit-tooling-test/src/main/java/com/tngtech/archunit/tooling/engines/jupiter/JUnitJupiterEngine.java @@ -1,9 +1,13 @@ package com.tngtech.archunit.tooling.engines.jupiter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; +import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.tngtech.archunit.junit.engine_api.FieldSource; @@ -28,7 +32,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.tngtech.archunit.junit.engine_api.FieldSelector.selectField; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; public enum JUnitJupiterEngine implements TestEngine { INSTANCE; @@ -59,11 +65,11 @@ private org.junit.platform.engine.TestEngine[] manuallyLoadCorrectTestEngines(Te } private LauncherDiscoveryRequest toDiscoveryRequest(TestFile testFile) { - DiscoverySelector selector = toTestClassSelector(testFile); - LOG.info("Executing request with selectors {}", selector); + List selectors = toSelectors(testFile) + .collect(Collectors.toList()); + LOG.info("Executing request with selectors {}", selectors); LauncherDiscoveryRequestBuilder builder = LauncherDiscoveryRequestBuilder.request() - .selectors() - .selectors(selector); + .selectors(selectors); if (testFile.hasTestCasesFilter()) { String testCaseFilter = toTestCaseFilter(testFile); builder = builder.configurationParameter(ARCHUNIT_INCLUDES_PARAMETER_NAME, testCaseFilter); @@ -76,10 +82,44 @@ private String toTestCaseFilter(TestFile testFile) { return testFile.getTestCases().stream().map((testFile.getFixture().getSimpleName() + ".")::concat).collect(Collectors.joining(",")); } + private Stream toSelectors(TestFile testFile) { + if (shouldApplyTestMethodNameSelector(testFile) && testFile.hasTestCasesFilter()) { + return testFile.getTestCases().stream().map(testCase -> toSelector(testFile.getFixture(), testCase)); + } + return Stream.of(toTestClassSelector(testFile)); + } + private DiscoverySelector toTestClassSelector(TestFile testFile) { return selectClass(testFile.getFixture()); } + private boolean shouldApplyTestMethodNameSelector(TestFile testFile) { + // skip for ArchUnit tests because archunit.junit.includeTestsMatching will be used instead + return testFile.getFixture().getSimpleName().contains("Regular"); + } + + private DiscoverySelector toSelector(Class fixture, String testCase) { + return getMethodByName(fixture, testCase) + .map(method -> selectMethod(fixture, method)) + .orElseGet(() -> getFieldByName(fixture, testCase) + .map(field -> selectField(fixture, field.getName())) + .orElseThrow(RuntimeException::new)); + } + + private Optional getMethodByName(Class owner, String name) { + return Arrays.stream(owner.getDeclaredMethods()) + .filter(method -> name.equals(method.getName())) + .findFirst(); + } + + private Optional getFieldByName(Class owner, String name) { + try { + return Optional.of(owner.getDeclaredField(name)); + } catch (NoSuchFieldException e) { + return Optional.empty(); + } + } + private static class TestExecutionCollector implements TestExecutionListener { private final TestReport report = new TestReport(); From bec9d2984b32ee5c4781da25534357c324c40b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Mon, 27 Jun 2022 10:41:52 +0200 Subject: [PATCH 07/10] Fixed test config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../archunit/junit/internal/ArchUnitTestEngineTest.java | 4 ++-- archunit-tooling-test/build.gradle | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java index 87b76a8917..e99baf227e 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java @@ -152,7 +152,7 @@ void a_single_test_class() { TestDescriptor child = getOnlyElement(descriptor.getChildren()); assertThat(child).isInstanceOf(ArchUnitTestDescriptor.class); assertThat(child.getUniqueId()).isEqualTo(engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName())); - assertThat(child.getDisplayName()).isEqualTo(SimpleRuleField.class.getSimpleName()); + assertThat(child.getDisplayName()).isEqualTo(SimpleRuleField.class.getName()); assertThat(child.getType()).isEqualTo(CONTAINER); assertThat(child.getParent().get()).isEqualTo(descriptor); } @@ -177,7 +177,7 @@ void multiple_test_classes() { TestDescriptor descriptor = testEngine.discover(discoveryRequest, createEngineId()); Set displayNames = descriptor.getChildren().stream().map(TestDescriptor::getDisplayName).collect(toSet()); - assertThat(displayNames).containsOnly(SimpleRuleField.class.getSimpleName(), SimpleRuleMethod.class.getSimpleName()); + assertThat(displayNames).containsOnly(SimpleRuleField.class.getName(), SimpleRuleMethod.class.getName()); } @Test diff --git a/archunit-tooling-test/build.gradle b/archunit-tooling-test/build.gradle index 1b959a8a68..b3f3fc94ce 100644 --- a/archunit-tooling-test/build.gradle +++ b/archunit-tooling-test/build.gradle @@ -126,13 +126,6 @@ task publishJUnitDepsToMavenLocal { dependsOn ':archunit:publishMavenJavaPublicationToMavenLocal' } -gradle.taskGraph.whenReady { graph -> - if (graph.hasTask(publishJUnitDepsToMavenLocal)) { - def externalTests = graph.allTasks.findAll { task -> task.name.toLowerCase().endsWith("test") && !task.path.startsWith(":archunit-tooling-test:") } - externalTests.each { externalTest -> externalTest.enabled = false } - } -} - clean { dependsOn removeFromMavenLocal } From fd50877bb363fb10d597a8dd3009d8ae09a25e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Mon, 27 Jun 2022 10:54:04 +0200 Subject: [PATCH 08/10] Fixed test config (2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- archunit-tooling-test/build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/archunit-tooling-test/build.gradle b/archunit-tooling-test/build.gradle index b3f3fc94ce..804c700d3e 100644 --- a/archunit-tooling-test/build.gradle +++ b/archunit-tooling-test/build.gradle @@ -118,12 +118,12 @@ task removeFromMavenLocal { task publishJUnitDepsToMavenLocal { group = 'other' description = 'Publishes ArchUnit JUnit artifacts to local Maven repo' - dependsOn ':archunit-junit5-engine-api:publishMavenJavaPublicationToMavenLocal' - dependsOn ':archunit-junit5-engine:publishMavenJavaPublicationToMavenLocal' - dependsOn ':archunit-junit5-api:publishMavenJavaPublicationToMavenLocal' - dependsOn ':archunit-junit5:publishMavenJavaPublicationToMavenLocal' - dependsOn ':archunit-junit4:publishMavenJavaPublicationToMavenLocal' - dependsOn ':archunit:publishMavenJavaPublicationToMavenLocal' + dependsOn project(':archunit-junit5-engine-api').tasks.publishMavenJavaPublicationToMavenLocal + dependsOn project(':archunit-junit5-engine').tasks.publishMavenJavaPublicationToMavenLocal + dependsOn project(':archunit-junit5-api').tasks.publishMavenJavaPublicationToMavenLocal + dependsOn project(':archunit-junit5').tasks.publishMavenJavaPublicationToMavenLocal + dependsOn project(':archunit-junit4').tasks.publishMavenJavaPublicationToMavenLocal + dependsOn project(':archunit').tasks.publishMavenJavaPublicationToMavenLocal } clean { From ac37117e648b93b0a4e36027f481f60dfc4b6bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Mon, 27 Jun 2022 12:19:33 +0200 Subject: [PATCH 09/10] Bugfixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../junit/internal/ArchUnitTestDescriptor.java | 10 +++++----- .../junit/internal/filtering/TestSourceFilter.java | 12 +++++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java index 6a39e3a36d..9a627656b6 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java @@ -64,11 +64,11 @@ private ArchUnitTestDescriptor(ElementResolver resolver, Class testClass, Cla static void resolve(TestDescriptor parent, ElementResolver resolver, ClassCache classCache, TestSourceFilter additionalFilter) { resolver.resolveClass() - .ifRequestedAndResolved((resolvedMember, elementResolver) -> resolvedMember.createChildren(resolver, additionalFilter)) + .ifRequestedAndResolved((resolvedMember, childResolver) -> resolvedMember.createChildren(childResolver, 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 resolver, TestSourceFilter additionalFilter) { if (clazz.getAnnotation(AnalyzeClasses.class) == null) { LOG.warn("Class {} is not annotated with @{} and thus cannot run as a top level test. " @@ -79,9 +79,9 @@ private static void createTestDescriptor(TestDescriptor parent, ClassCache class return; } - ArchUnitTestDescriptor classDescriptor = new ArchUnitTestDescriptor(childResolver, clazz, classCache); + ArchUnitTestDescriptor classDescriptor = new ArchUnitTestDescriptor(resolver, clazz, classCache); parent.addChild(classDescriptor); - classDescriptor.createChildren(childResolver, additionalFilter); + classDescriptor.createChildren(resolver, additionalFilter); } @Override @@ -134,7 +134,7 @@ private static void resolveArchRules( } DeclaredArchTests archTests = getDeclaredArchTests(field); resolver.resolveClass(archTests.getDefinitionLocation()) - .ifRequestedAndResolved((resolvedMember, elementResolver) -> resolvedMember.createChildren(resolver, filter)) + .ifRequestedAndResolved((resolvedMember, childResolver) -> resolvedMember.createChildren(childResolver, TestSourceFilter.NOOP)) .ifRequestedButUnresolved((clazz, childResolver) -> { ArchUnitArchTestsDescriptor rulesDescriptor = new ArchUnitArchTestsDescriptor(childResolver, archTests, classes, field); parent.addChild(rulesDescriptor); diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java index d1ed646e96..3faa83bed7 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/TestSourceFilter.java @@ -27,7 +27,17 @@ public interface TestSourceFilter { Logger LOG = LoggerFactory.getLogger(ArchUnitTestEngine.class); - TestSourceFilter NOOP = (descriptor -> true); + TestSourceFilter NOOP = new TestSourceFilter() { + @Override + public boolean shouldRun(TestSource source) { + return true; + } + + @Override + public String toString() { + return "NOOP"; + } + }; default boolean shouldRun(TestDescriptor descriptor) { return descriptor.getSource() From b26665b1e8d4c3a3347811a3a53e5f97e7755844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sierszen=CC=81?= Date: Mon, 27 Jun 2022 15:01:35 +0200 Subject: [PATCH 10/10] Added test engine test for filtgering via config properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Sierszeń --- .../filtering/AbstractTestNameFilter.java | 6 ++++- .../ArchUnitPropsTestSourceFilter.java | 9 +++++-- .../internal/ArchUnitTestEngineTest.java | 19 +++++++++++++++ .../internal/EngineDiscoveryTestRequest.java | 24 +++++++++++++++---- .../testexamples/subtwo/SimpleRules.java | 4 ++++ 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java index c9bb0d68c1..ac373605a1 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/AbstractTestNameFilter.java @@ -34,6 +34,7 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestSource; @@ -57,7 +58,7 @@ public AbstractTestNameFilter(EngineDiscoveryRequest request, String discoveryFi } public AbstractTestNameFilter(EngineDiscoveryRequest request) { - this(request, ""); + this(request, null); } private void replaceFilterIfNeeded(EngineDiscoveryRequest request, PostDiscoveryFilter newFilter, PostDiscoveryFilter oldFilter) { @@ -106,6 +107,9 @@ public static boolean checkApplicability(EngineDiscoveryRequest discoveryRequest } private Optional findPostDiscoveryFilter(EngineDiscoveryRequest request) { + if (discoveryFilterClassName == null) { + return Optional.empty(); + } return ((LauncherDiscoveryRequest) request).getPostDiscoveryFilters().stream() .filter(this::matches) .findAny(); diff --git a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java index fb6067e895..34417875b6 100644 --- a/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java +++ b/archunit-junit/junit5/engine/src/main/java/com/tngtech/archunit/junit/internal/filtering/ArchUnitPropsTestSourceFilter.java @@ -74,8 +74,8 @@ private static boolean isProvided(EngineDiscoveryRequest discoveryRequest, ArchU } private static Optional readProperty(EngineDiscoveryRequest discoveryRequest, ArchUnitEngineDescriptor engineDescriptor, String key) { - return readFromConfigurationParams(discoveryRequest, key) - .or(() -> ArchUnitPropsTestSourceFilter.readFromEngineDescriptor(engineDescriptor, key)); + return Optional.ofNullable(readFromConfigurationParams(discoveryRequest, key)) + .orElseGet(() -> ArchUnitPropsTestSourceFilter.readFromEngineDescriptor(engineDescriptor, key)); } private static Optional readFromEngineDescriptor(ArchUnitEngineDescriptor engineDescriptor, String key) { @@ -133,5 +133,10 @@ private boolean matchesSelector(Pattern pattern, TestSelector selector) { pattern.matcher(selector.getFullyQualifiedName()).find() || pattern.matcher(selector.getContainerName()).find(); } + + @Override + public String toString() { + return patterns.toString(); + } } } diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java index e99baf227e..5038471cc8 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java @@ -109,6 +109,9 @@ @ExtendWith(MockitoExtension.class) class ArchUnitTestEngineTest { + + private static final String INCLUDE_TESTS_ENV_VARIABLE = "archunit.junit.includeTestsMatching"; + @Mock private ClassCache classCache; @Mock @@ -714,6 +717,22 @@ void filtering_included_packages() { simpleRulesId(engineId)); } + @Test + void filtering_fields_included_by_config_property() { + // given + EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest() + .withConfigurationParam(INCLUDE_TESTS_ENV_VARIABLE, SimpleRules.SIMPLE_RULE_FIELD_ONE_QUALIFIED_NAME) + .withClass(SimpleRules.class); + + // when + TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId); + TestDescriptor classDescriptor = getOnlyElement(rootDescriptor.getChildren()); + + // then + assertThat(toUniqueIds(classDescriptor)).containsOnly( + simpleRulesId(engineId).append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME)); + } + @Test void all_without_filters() { EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest() diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java index 7b3d5f0b33..b95f14935d 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Properties; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.junit.engine_api.FieldSelector; @@ -41,6 +42,8 @@ class EngineDiscoveryTestRequest implements EngineDiscoveryRequest { private final List classNameFilters = new ArrayList<>(); private final List packageNameFilters = new ArrayList<>(); + private final Properties configurationParams = new Properties(); + @Override @SuppressWarnings("unchecked") // compatibility is explicitly checked public List getSelectorsByType(Class selectorType) { @@ -103,7 +106,7 @@ public > List getFiltersByType(Class filterTy @Override public ConfigurationParameters getConfigurationParameters() { - return new EmptyConfigurationParameters(); + return new PropertiesBackedConfigurationParameters(configurationParams); } EngineDiscoveryTestRequest withClasspathRoot(URI uri) { @@ -154,20 +157,31 @@ EngineDiscoveryTestRequest withField(Class clazz, String fieldName) { return this; } - private static class EmptyConfigurationParameters implements ConfigurationParameters { + EngineDiscoveryTestRequest withConfigurationParam(String key, String value) { + configurationParams.put(key, value); + return this; + } + + private static class PropertiesBackedConfigurationParameters implements ConfigurationParameters { + private final Properties properties; + + public PropertiesBackedConfigurationParameters(Properties properties) { + this.properties = properties; + } + @Override public Optional get(String key) { - return Optional.empty(); + return Optional.ofNullable(properties.getProperty(key, null)); } @Override public Optional getBoolean(String key) { - return Optional.empty(); + return get(key).map(Boolean::parseBoolean); } @Override public int size() { - return 0; + return properties.size(); } } } diff --git a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java index 582bf8f8a2..6013d06233 100644 --- a/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java +++ b/archunit-junit/junit5/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java @@ -33,4 +33,8 @@ public static void simple_rule_method_two(JavaClasses classes) { public static final Set RULE_FIELD_NAMES = ImmutableSet.of(SIMPLE_RULE_FIELD_ONE_NAME, SIMPLE_RULE_FIELD_TWO_NAME); public static final String SIMPLE_RULE_METHOD_ONE_NAME = "simple_rule_method_one"; public static final Set RULE_METHOD_NAMES = ImmutableSet.of(SIMPLE_RULE_METHOD_ONE_NAME, "simple_rule_method_two"); + + // must be a compile-time constant, unfortunately + private static final String SIMPLE_RULES_CLASS_NAME = "com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules"; + public static final String SIMPLE_RULE_FIELD_ONE_QUALIFIED_NAME = SIMPLE_RULES_CLASS_NAME + "." + SIMPLE_RULE_FIELD_ONE_NAME; }