diff --git a/archunit-junit/junit6/aggregator/build.gradle b/archunit-junit/junit6/aggregator/build.gradle
new file mode 100644
index 0000000000..6f8c003466
--- /dev/null
+++ b/archunit-junit/junit6/aggregator/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+ id 'archunit.java-release-conventions'
+}
+
+ext.moduleName = 'com.tngtech.archunit.junit6'
+
+ext.minimumJavaVersion = JavaVersion.VERSION_17
+
+dependencies {
+ api(project(":archunit-junit6-api"))
+ implementation(project(":archunit-junit6-engine"))
+}
+
+shadowJar {
+ exclude '**'
+}
diff --git a/archunit-junit/junit6/api/build.gradle b/archunit-junit/junit6/api/build.gradle
new file mode 100644
index 0000000000..a26fa4d17b
--- /dev/null
+++ b/archunit-junit/junit6/api/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+ id 'archunit.java-release-conventions'
+}
+
+ext.moduleName = 'com.tngtech.archunit.junit6.api'
+
+ext.minimumJavaVersion = JavaVersion.VERSION_17
+
+dependencies {
+ api project(path: ':archunit')
+ api project(path: ':archunit-junit', configuration: 'archJunitApi')
+ api libs.junit6PlatformCommons
+}
+
+javadoc {
+ source(project(':archunit-junit').sourceSets.archJunitApi.allJava)
+}
+sourcesJar {
+ from project(':archunit-junit').sourceSets.archJunitApi.allSource
+}
+
+shadowJar {
+ exclude 'META-INF/maven/**'
+
+ dependencies {
+ exclude(dependency { !it.name.contains('archunit-junit') })
+ }
+}
+
+def configureDependencies = { deps ->
+ deps.children().removeIf { dep ->
+ dep.scope.text() != 'compile' || !(dep.artifactId.text() in ['archunit'])
+ }
+}
+this.with project(':archunit-junit').configureJUnitArchive(configureDependencies)
+
+singlePackageExport {
+ exportedPackage = 'com.tngtech.archunit.junit'
+}
diff --git a/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/AnalyzeClasses.java b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/AnalyzeClasses.java
new file mode 100644
index 0000000000..e26f61f50b
--- /dev/null
+++ b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/AnalyzeClasses.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.PublicAPI;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.ImportOption;
+import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeJars;
+import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests;
+import org.junit.platform.commons.annotation.Testable;
+
+import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Specifies which packages/locations should be scanned and tested when running a JUnit 5 test.
+ *
+ * To ignore certain classes (e.g. classes in test scope) see {@link #importOptions()}, in particular {@link DoNotIncludeTests} and
+ * {@link DoNotIncludeJars}.
+ *
+ * When checking rules, it is important to remember that all relevant information/classes need to be imported for the rules
+ * to work. For example, if class A accesses class B and class B extends class C, but class B is not imported, then
+ * a rule checking for no accesses to classes assignable to C will not fail, since ArchUnit does not know about the details
+ * of class B, but only simple information like the fully qualified name. For information how to configure the import and
+ * resolution behavior of missing classes, compare {@link ClassFileImporter}.
+ *
+ * @see ClassFileImporter
+ */
+@Testable
+@Target(TYPE)
+@Retention(RUNTIME)
+@PublicAPI(usage = ACCESS)
+public @interface AnalyzeClasses {
+ /**
+ * @return Packages to look for within the classpath / modulepath
+ */
+ String[] packages() default {};
+
+ /**
+ * @return Classes that specify packages to look for within the classpath / modulepath
+ */
+ Class>[] packagesOf() default {};
+
+ /**
+ * @return Implementations of {@link LocationProvider}. Allows to completely customize the sources,
+ * where classes are imported from.
+ */
+ Class extends LocationProvider>[] locations() default {};
+
+ /**
+ * @return Whether to look for classes on the whole classpath.
+ * Warning: Without any further {@link #importOptions() filtering} this can require a lot of resources.
+ */
+ boolean wholeClasspath() default false;
+
+ /**
+ * Allows to filter the class import. The supplied types will be instantiated and used to create the
+ * {@link ImportOption ImportOptions} passed to the {@link ClassFileImporter}. Considering caching, compare the notes on
+ * {@link ImportOption}.
+ *
+ * @return The types of {@link ImportOption} to use for the import
+ */
+ Class extends ImportOption>[] importOptions() default {};
+
+ /**
+ * Controls, if {@link JavaClasses} should be cached by location,
+ * to be reused between several test classes, or just within the same class.
+ *
+ * @return The {@link CacheMode} to use for this test class.
+ */
+ CacheMode cacheMode() default CacheMode.FOREVER;
+}
diff --git a/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTag.java b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTag.java
new file mode 100644
index 0000000000..124b5b76e4
--- /dev/null
+++ b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTag.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.PublicAPI;
+
+import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * {@link ArchTag @ArchTag} is a {@linkplain Repeatable repeatable} annotation that allows
+ * tagging any {@link ArchTest @ArchTest} field/method/class. Sets of rules can be classified to run together this way.
+ *
+ * Rules could be tagged like
+ *
+ *
+ *{@literal @}ArchTag("dependencies")
+ * static ArchRule no_accesses_from_server_to_client = classes()...
+ *
+ *
+ * Users of {@link ArchTag} must follow the syntax conventions that the JUnit Platform Engine defines:
+ *
+ *
+ * - A tag must not be blank.
+ * - A trimmed tag must not contain whitespace.
+ * - A trimmed tag must not contain ISO control characters.
+ * - A trimmed tag must not contain any of the following
+ * reserved characters.
+ *
+ * - {@code ,}: comma
+ * - {@code (}: left parenthesis
+ * - {@code )}: right parenthesis
+ * - {@code &}: ampersand
+ * - {@code |}: vertical bar
+ * - {@code !}: exclamation point
+ *
+ *
+ *
+ */
+@Inherited
+@Documented
+@Retention(RUNTIME)
+@PublicAPI(usage = ACCESS)
+@Repeatable(ArchTags.class)
+@Target({TYPE, METHOD, FIELD})
+public @interface ArchTag {
+ /**
+ * The actual tag. It will first be {@linkplain String#trim() trimmed} and must then adhere to the
+ * {@linkplain ArchTag Syntax Rules for Tags}. Otherwise the tag will be ignored.
+ */
+ String value();
+}
diff --git a/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTags.java b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTags.java
new file mode 100644
index 0000000000..2346e72e7e
--- /dev/null
+++ b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTags.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.Internal;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Simply a container for {@link ArchTag}. Should never be used directly, but instead
+ * {@link ArchTag} should be used in a {@linkplain Repeatable repeatable} manner, e.g.
+ *
+ *
+ *{@literal @}ArchTag("foo")
+ *{@literal @}ArchTag("bar")
+ * static ArchRule example = classes()...
+ *
+ */
+@Internal
+@Inherited
+@Documented
+@Retention(RUNTIME)
+@Target({TYPE, METHOD, FIELD})
+public @interface ArchTags {
+ ArchTag[] value();
+}
diff --git a/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTest.java b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTest.java
new file mode 100644
index 0000000000..f72103ef5b
--- /dev/null
+++ b/archunit-junit/junit6/api/src/main/java/com/tngtech/archunit/junit/ArchTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.lang.ArchRule;
+import org.junit.platform.commons.annotation.Testable;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Marks ArchUnit tests to be executed by the test infrastructure. These tests can have the following form:
+ *
+ * -
+ * A static field of type {@link ArchRule} -> this rule will automatically be checked against the imported classes
+ *
+ * -
+ * A static method with one parameter {@link JavaClasses} -> this method will be called with the imported classes
+ *
+ *
+ *
Example:
+ *
+ *{@literal @}ArchTest
+ * public static final ArchRule someRule = classes()... ;
+ *
+ *{@literal @}ArchTest
+ * public static void someMethod(JavaClasses classes) {
+ * // do something with classes
+ * }
+ *
+ */
+@Testable
+@Target({FIELD, METHOD})
+@Retention(RUNTIME)
+public @interface ArchTest {
+}
diff --git a/archunit-junit/junit6/engine-api/build.gradle b/archunit-junit/junit6/engine-api/build.gradle
new file mode 100644
index 0000000000..90e03fb432
--- /dev/null
+++ b/archunit-junit/junit6/engine-api/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'archunit.java-release-conventions'
+}
+
+ext.moduleName = 'com.tngtech.archunit.junit6.engineapi'
+
+ext.minimumJavaVersion = JavaVersion.VERSION_17
+
+dependencies {
+ api libs.junit6PlatformEngine
+ implementation project(path: ':archunit')
+
+ testImplementation project(path: ':archunit-junit6-api')
+ testImplementation libs.assertj
+}
+
+compileJava.dependsOn project(':archunit-junit6-api').finishArchive
+
+test {
+ useJUnitPlatform() {
+ excludeEngines 'archunit'
+ }
+}
+
+shadowJar {
+ exclude 'META-INF/maven/**'
+
+ dependencies {
+ exclude(dependency { true })
+ }
+}
+
+// dependencies to archunit only cover annotations; we can skip those without breaking consumers to keep the dependency slim
+def configureDependencies = { deps ->
+ deps.children().removeIf{ dep ->
+ dep.artifactId.text() != 'junit-platform-engine'
+ }
+}
+this.with project(':archunit-junit').configureJUnitArchive(configureDependencies)
+
+singlePackageExport {
+ exportedPackage = 'com.tngtech.archunit.junit.engine_api'
+}
diff --git a/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSelector.java b/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSelector.java
new file mode 100644
index 0000000000..6568894eed
--- /dev/null
+++ b/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSelector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014-2026 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.engine_api;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+
+import com.tngtech.archunit.Internal;
+import com.tngtech.archunit.PublicAPI;
+import com.tngtech.archunit.base.ClassLoaders;
+import com.tngtech.archunit.base.MayResolveTypesViaReflection;
+import org.junit.platform.engine.DiscoverySelector;
+
+import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+
+@PublicAPI(usage = ACCESS)
+public final class FieldSelector implements DiscoverySelector {
+ private final Class> clazz;
+ private final Field field;
+
+ private FieldSelector(Class> clazz, Field field) {
+ this.clazz = clazz;
+ this.field = field;
+ }
+
+ @Internal
+ public Class> getJavaClass() {
+ return clazz;
+ }
+
+ @Internal
+ public Field getJavaField() {
+ return field;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(clazz, field);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ FieldSelector other = (FieldSelector) obj;
+ return Objects.equals(this.clazz, other.clazz)
+ && Objects.equals(this.field, other.field);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{" +
+ "clazz=" + clazz +
+ ", field=" + field +
+ '}';
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public static FieldSelector selectField(String className, String fieldName) {
+ return selectField(classForName(className), fieldName);
+ }
+
+ @MayResolveTypesViaReflection(reason = "Within the ArchUnitTestEngine we may resolve types via reflection, since they are needed anyway")
+ private static Class> classForName(String className) {
+ try {
+ return ClassLoaders.loadClass(className);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Could not load requested class " + className, e);
+ }
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public static FieldSelector selectField(Class> javaClass, String fieldName) {
+ Field selectedField = findFieldInHierarchy(javaClass, fieldName);
+ if (selectedField == null) {
+ throw new IllegalArgumentException(String.format("Could not find field %s.%s", javaClass.getName(), fieldName));
+ }
+ return selectField(javaClass, selectedField);
+ }
+
+ private static Field findFieldInHierarchy(Class> javaClass, String fieldName) {
+ Field selectedField = null;
+ Class> currentClass = javaClass;
+ while (selectedField == null && !currentClass.equals(Object.class)) {
+ try {
+ selectedField = currentClass.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException ignored) {
+ currentClass = currentClass.getSuperclass();
+ }
+ }
+ return selectedField;
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public static FieldSelector selectField(Class> javaClass, Field field) {
+ return new FieldSelector(javaClass, field);
+ }
+}
diff --git a/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSource.java b/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSource.java
new file mode 100644
index 0000000000..4025bc39bf
--- /dev/null
+++ b/archunit-junit/junit6/engine-api/src/main/java/com/tngtech/archunit/junit/engine_api/FieldSource.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014-2026 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.engine_api;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+
+import com.tngtech.archunit.Internal;
+import com.tngtech.archunit.PublicAPI;
+import org.junit.platform.engine.TestSource;
+
+import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+
+@PublicAPI(usage = ACCESS)
+public final class FieldSource implements TestSource {
+ private final Class> javaClass;
+ private final String fieldName;
+
+ private FieldSource(Field field) {
+ javaClass = field.getDeclaringClass();
+ fieldName = field.getName();
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public String getClassName() {
+ return javaClass.getName();
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public Class> getJavaClass() {
+ return javaClass;
+ }
+
+ @PublicAPI(usage = ACCESS)
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(javaClass, fieldName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ FieldSource other = (FieldSource) obj;
+ return Objects.equals(this.javaClass, other.javaClass)
+ && Objects.equals(this.fieldName, other.fieldName);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{" + getClassName() + '.' + fieldName + '}';
+ }
+
+ @Internal
+ public static FieldSource from(Field field) {
+ return new FieldSource(field);
+ }
+}
diff --git a/archunit-junit/junit6/engine-api/src/test/java/com/tngtech/archunit/junit/engine_api/FieldSelectorTest.java b/archunit-junit/junit6/engine-api/src/test/java/com/tngtech/archunit/junit/engine_api/FieldSelectorTest.java
new file mode 100644
index 0000000000..130b8256e5
--- /dev/null
+++ b/archunit-junit/junit6/engine-api/src/test/java/com/tngtech/archunit/junit/engine_api/FieldSelectorTest.java
@@ -0,0 +1,48 @@
+package com.tngtech.archunit.junit.engine_api;
+
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class FieldSelectorTest {
+
+ @Test
+ void select_simple_field() {
+ FieldSelector selector = FieldSelector.selectField(SomeParentTest.class.getName(), "someRule");
+
+ assertThat(selector.getJavaClass()).isEqualTo(SomeParentTest.class);
+ assertThat(selector.getJavaField().getName()).isEqualTo("someRule");
+ }
+
+ @Test
+ void select_parent_field_in_child() {
+ FieldSelector selector = FieldSelector.selectField(SomeChildTest.class.getName(), "someRule");
+
+ assertThat(selector.getJavaClass()).isEqualTo(SomeChildTest.class);
+ assertThat(selector.getJavaField().getName()).isEqualTo("someRule");
+ assertThat(selector.getJavaField().getDeclaringClass()).isEqualTo(SomeParentTest.class);
+ }
+
+ @Test
+ void select_shadowed_field() {
+ FieldSelector selector = FieldSelector.selectField(SomeChildTest.class.getName(), "someShadowedRule");
+
+ assertThat(selector.getJavaClass()).isEqualTo(SomeChildTest.class);
+ assertThat(selector.getJavaField().getName()).isEqualTo("someShadowedRule");
+ assertThat(selector.getJavaField().getDeclaringClass()).isEqualTo(SomeChildTest.class);
+ }
+
+ static class SomeParentTest {
+ @ArchTest
+ static final ArchRule someRule = null;
+ @ArchTest
+ static final ArchRule someShadowedRule = null;
+ }
+
+ static class SomeChildTest extends SomeParentTest {
+ @ArchTest
+ static final ArchRule someShadowedRule = null;
+ }
+}
diff --git a/archunit-junit/junit6/engine/build.gradle b/archunit-junit/junit6/engine/build.gradle
new file mode 100644
index 0000000000..59cb3d7234
--- /dev/null
+++ b/archunit-junit/junit6/engine/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'archunit.java-release-conventions'
+}
+
+ext.moduleName = 'com.tngtech.archunit.junit6.engine'
+
+ext.minimumJavaVersion = JavaVersion.VERSION_17
+
+dependencies {
+ api project(path: ':archunit')
+ api project(path: ':archunit-junit6-api')
+ api project(path: ':archunit-junit6-engine-api')
+ implementation project(path: ':archunit-junit')
+ dependency.addGuava { dependencyNotation, config -> implementation(dependencyNotation, config) }
+ implementation libs.slf4j
+
+ testImplementation project(path: ':archunit', configuration: 'tests')
+ testImplementation libs.assertj
+ testImplementation libs.mockito
+ testImplementation libs.mockito.junit5
+ testImplementation libs.log4j.core
+}
+
+gradle.projectsEvaluated {
+ compileJava.dependsOn project(':archunit-junit6-api').finishArchive
+ compileJava.dependsOn project(':archunit-junit6-engine-api').finishArchive
+}
+
+javadoc {
+ source(project(':archunit-junit').sourceSets.main.allJava)
+}
+sourcesJar {
+ from project(':archunit-junit').sourceSets.main.allSource
+}
+
+test {
+ useJUnitPlatform {
+ excludeEngines 'archunit'
+ }
+}
+
+shadowJar {
+ exclude 'META-INF/maven/**'
+
+ dependencies {
+ exclude(dependency {
+ def isApi = it.configuration == 'archJunitApi'
+ def isUnwantedDependency = it.moduleName != 'archunit-junit'
+ isUnwantedDependency || isApi
+ })
+ }
+}
+
+def configureDependencies = { deps ->
+ deps.children().removeIf { dep ->
+ dep.scope.text() != 'compile' || !(dep.artifactId.text() in ['archunit', 'archunit-junit6-api', 'archunit-junit6-engine-api'])
+ }
+}
+this.with project(':archunit-junit').configureJUnitArchive(configureDependencies)
+
+singlePackageExport {
+ exportedPackage = 'com.tngtech.archunit.junit.internal'
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/AbstractArchUnitTestDescriptor.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/AbstractArchUnitTestDescriptor.java
new file mode 100644
index 0000000000..74209be21d
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/AbstractArchUnitTestDescriptor.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.reflect.AnnotatedElement;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import com.tngtech.archunit.core.domain.Formatters;
+import com.tngtech.archunit.junit.ArchIgnore;
+import com.tngtech.archunit.junit.ArchTag;
+import org.junit.platform.commons.support.AnnotationSupport;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.TestTag;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
+import org.junit.platform.engine.support.hierarchical.Node;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
+
+abstract class AbstractArchUnitTestDescriptor extends AbstractTestDescriptor implements Node {
+ private final Set tags;
+ private final SkipResult skipResult;
+
+ AbstractArchUnitTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, AnnotatedElement... elements) {
+ super(uniqueId, displayName, source);
+ tags = Arrays.stream(elements).map(this::findTagsOn).flatMap(Collection::stream).collect(toSet());
+ skipResult = Arrays.stream(elements)
+ .map(e -> AnnotationSupport.findAnnotation(e, ArchIgnore.class))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst()
+ .map(ignore -> SkipResult.skip(ignore.reason()))
+ .orElse(SkipResult.doNotSkip());
+ }
+
+ private Set findTagsOn(AnnotatedElement annotatedElement) {
+ return AnnotationSupport.findRepeatableAnnotations(annotatedElement, ArchTag.class)
+ .stream()
+ .map(annotation -> TestTag.create(annotation.value()))
+ .collect(toSet());
+ }
+
+ @Override
+ public SkipResult shouldBeSkipped(ArchUnitEngineExecutionContext context) {
+ return skipResult;
+ }
+
+ @Override
+ public Set getTags() {
+ Set result = new HashSet<>(tags);
+ result.addAll(getParent().map(TestDescriptor::getTags).orElse(emptySet()));
+ return result;
+ }
+
+ static String formatWithPath(UniqueId uniqueId, String name) {
+ return Stream.concat(
+ uniqueId.getSegments().stream()
+ .filter(it -> it.getType().equals(ArchUnitTestDescriptor.CLASS_SEGMENT_TYPE))
+ .skip(1)
+ .map(UniqueId.Segment::getValue)
+ .map(Formatters::ensureSimpleName),
+ Stream.of(name)
+ ).collect(joining(" > "));
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java
new file mode 100644
index 0000000000..4b5dce2e58
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineDescriptor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014-2026 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;
+
+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) {
+ super(uniqueId, "ArchUnit JUnit 5");
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java
new file mode 100644
index 0000000000..3aa6161340
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitEngineExecutionContext.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014-2026 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;
+
+import org.junit.platform.engine.support.hierarchical.EngineExecutionContext;
+
+class ArchUnitEngineExecutionContext implements EngineExecutionContext {
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitSystemPropertyTestFilterJUnit5.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitSystemPropertyTestFilterJUnit5.java
new file mode 100644
index 0000000000..1f4bd56d8a
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitSystemPropertyTestFilterJUnit5.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.archunit.ArchConfiguration;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.UniqueId;
+
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.FIELD_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.METHOD_SEGMENT_TYPE;
+
+class ArchUnitSystemPropertyTestFilterJUnit6 {
+ private static final String JUNIT_TEST_FILTER_PROPERTY_NAME = "junit.testFilter";
+ private static final Set MEMBER_SEGMENT_TYPES = ImmutableSet.of(FIELD_SEGMENT_TYPE, METHOD_SEGMENT_TYPE);
+
+ void filter(TestDescriptor descriptor) {
+ ArchConfiguration configuration = ArchConfiguration.get();
+ if (!configuration.containsProperty(JUNIT_TEST_FILTER_PROPERTY_NAME)) {
+ return;
+ }
+
+ String testFilterProperty = configuration.getProperty(JUNIT_TEST_FILTER_PROPERTY_NAME);
+ List memberNames = Splitter.on(",").splitToList(testFilterProperty);
+ Predicate shouldRunPredicate = testDescriptor -> memberNameMatches(testDescriptor, memberNames);
+ removeNonMatching(descriptor, shouldRunPredicate);
+ }
+
+ private void removeNonMatching(TestDescriptor descriptor, Predicate shouldRunPredicate) {
+ ImmutableSet.copyOf(descriptor.getChildren())
+ .forEach(child -> removeNonMatching(child, shouldRunPredicate));
+
+ if (!descriptor.isRoot() && descriptor.getChildren().isEmpty() && !shouldRunPredicate.test(descriptor)) {
+ descriptor.removeFromHierarchy();
+ }
+ }
+
+ private static boolean memberNameMatches(TestDescriptor testDescriptor, List memberNames) {
+ UniqueId.Segment lastSegment = testDescriptor.getUniqueId().getLastSegment();
+ return MEMBER_SEGMENT_TYPES.contains(lastSegment.getType())
+ && memberNames.stream().anyMatch(it -> lastSegment.getValue().equals(it));
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java
new file mode 100644
index 0000000000..1ef59adacd
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestDescriptor.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ImportOption;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.CacheMode;
+import com.tngtech.archunit.junit.LocationProvider;
+import com.tngtech.archunit.junit.engine_api.FieldSource;
+import com.tngtech.archunit.lang.ArchRule;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.descriptor.MethodSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.tngtech.archunit.junit.internal.DisplayNameResolver.determineDisplayName;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.findAnnotation;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllFields;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllMethods;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.getValueOrThrowException;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.invokeMethod;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.tryFindAnnotation;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.withAnnotation;
+
+class ArchUnitTestDescriptor extends AbstractArchUnitTestDescriptor implements CreatesChildren {
+ private static final Logger LOG = LoggerFactory.getLogger(ArchUnitTestDescriptor.class);
+
+ static final String CLASS_SEGMENT_TYPE = "class";
+ static final String FIELD_SEGMENT_TYPE = "field";
+ static final String METHOD_SEGMENT_TYPE = "method";
+
+ private final Class> testClass;
+ @SuppressWarnings("FieldMayBeFinal") // We want to change this in tests
+ private ClassCache classCache;
+
+ private ArchUnitTestDescriptor(ElementResolver resolver, Class> testClass, ClassCache classCache) {
+ super(resolver.getUniqueId(), testClass.getSimpleName(), ClassSource.from(testClass), testClass);
+ this.testClass = testClass;
+ this.classCache = classCache;
+ }
+
+ static void resolve(TestDescriptor parent, ElementResolver resolver, ClassCache classCache) {
+ resolver.resolveClass()
+ .ifRequestedAndResolved(CreatesChildren::createChildren)
+ .ifRequestedButUnresolved((clazz, childResolver) -> createTestDescriptor(parent, classCache, clazz, childResolver));
+ }
+
+ private static void createTestDescriptor(TestDescriptor parent, ClassCache classCache, Class> clazz, ElementResolver childResolver) {
+ if (!tryFindAnnotation(clazz, AnalyzeClasses.class).isPresent()) {
+ LOG.warn("Class {} is not (meta-)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).",
+ clazz.getName(), AnalyzeClasses.class.getSimpleName(),
+ clazz.getSimpleName(), ArchTests.class.getSimpleName(), clazz.getSimpleName());
+
+ return;
+ }
+
+ ArchUnitTestDescriptor classDescriptor = new ArchUnitTestDescriptor(childResolver, clazz, classCache);
+ parent.addChild(classDescriptor);
+ classDescriptor.createChildren(childResolver);
+ }
+
+ @Override
+ public void createChildren(ElementResolver resolver) {
+ Supplier classes = () -> classCache.getClassesToAnalyzeFor(testClass, new JUnit6ClassAnalysisRequest(testClass));
+
+ getAllFields(testClass, withAnnotation(ArchTest.class))
+ .forEach(field -> resolveField(resolver, classes, new TestMember<>(testClass, field)));
+ getAllMethods(testClass, withAnnotation(ArchTest.class))
+ .forEach(method -> resolveMethod(resolver, classes, new TestMember<>(testClass, method)));
+ }
+
+ private void resolveField(ElementResolver resolver, Supplier classes, TestMember field) {
+ resolver.resolveField(field.member)
+ .ifUnresolved(childResolver -> resolveChildren(this, childResolver, field, classes));
+ }
+
+ private void resolveMethod(ElementResolver resolver, Supplier classes, TestMember method) {
+ resolver.resolveMethod(method.member)
+ .ifUnresolved(childResolver -> addChild(new ArchUnitMethodDescriptor(getUniqueId(), method, classes)));
+ }
+
+ private static void resolveChildren(
+ TestDescriptor parent, ElementResolver resolver, TestMember field, Supplier classes) {
+
+ if (ArchTests.class.isAssignableFrom(field.member.getType())) {
+ resolveArchRules(parent, resolver, field, classes);
+ } else {
+ parent.addChild(new ArchUnitRuleDescriptor(resolver.getUniqueId(), getValue(field), classes, field));
+ }
+ }
+
+ private static T getValue(TestMember field) {
+ return getValueOrThrowException(field.member, field.owner, ArchTestInitializationException::new);
+ }
+
+ private static void resolveArchRules(
+ TestDescriptor parent, ElementResolver resolver, TestMember field, Supplier classes) {
+
+ DeclaredArchTests archTests = getDeclaredArchTests(field);
+
+ resolver.resolveClass(archTests.getDefinitionLocation())
+ .ifRequestedAndResolved(CreatesChildren::createChildren)
+ .ifRequestedButUnresolved((clazz, childResolver) -> {
+ ArchUnitArchTestsDescriptor rulesDescriptor = new ArchUnitArchTestsDescriptor(childResolver, archTests, classes, field);
+ parent.addChild(rulesDescriptor);
+ rulesDescriptor.createChildren(childResolver);
+ });
+ }
+
+ private static DeclaredArchTests getDeclaredArchTests(TestMember field) {
+ return new DeclaredArchTests(getValue(field));
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+
+ @Override
+ public void after(ArchUnitEngineExecutionContext context) {
+ classCache.clear(testClass);
+ }
+
+ private static class ArchUnitRuleDescriptor extends AbstractArchUnitTestDescriptor {
+ private final ArchRule rule;
+ private final Supplier classes;
+
+ ArchUnitRuleDescriptor(UniqueId uniqueId, ArchRule rule, Supplier classes, TestMember field) {
+ super(uniqueId, determineDisplayName(formatWithPath(uniqueId, field.getName())), FieldSource.from(field.member), field.member);
+ this.rule = rule;
+ this.classes = classes;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TEST;
+ }
+
+ @Override
+ public ArchUnitEngineExecutionContext execute(ArchUnitEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
+ rule.check(classes.get());
+ return context;
+ }
+ }
+
+ private static class ArchUnitMethodDescriptor extends AbstractArchUnitTestDescriptor {
+ private final TestMember method;
+ private final Supplier classes;
+
+ ArchUnitMethodDescriptor(UniqueId uniqueId, TestMember method, Supplier classes) {
+ super(uniqueId.append("method", method.member.getName()),
+ determineDisplayName(formatWithPath(uniqueId, method.member.getName())),
+ MethodSource.from(method.member),
+ method.member);
+
+ validate(method.member);
+
+ this.method = method;
+ this.classes = classes;
+ }
+
+ private void validate(Method method) {
+ ArchTestInitializationException.check(
+ method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(JavaClasses.class),
+ "@%s Method %s.%s must have exactly one parameter of type %s",
+ ArchTest.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName(), JavaClasses.class.getName());
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TEST;
+ }
+
+ @Override
+ public ArchUnitEngineExecutionContext execute(ArchUnitEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) {
+ invokeMethod(method.member, method.owner, classes.get());
+ return context;
+ }
+ }
+
+ private static class ArchUnitArchTestsDescriptor extends AbstractArchUnitTestDescriptor implements CreatesChildren {
+ private final DeclaredArchTests archTests;
+ private final Supplier classes;
+
+ ArchUnitArchTestsDescriptor(ElementResolver resolver, DeclaredArchTests archTests, Supplier classes, TestMember field) {
+
+ super(resolver.getUniqueId(),
+ archTests.getDisplayName(),
+ noSource(),
+ field.member,
+ archTests.getDefinitionLocation());
+ this.archTests = archTests;
+ this.classes = classes;
+ }
+
+ /**
+ * We don't pass a ClassSource for intermediary descriptors or it will be used as test location by test executors.
+ * We want the root class declaring @AnalyzeClasses to be used for this.
+ */
+ private static TestSource noSource() {
+ return null;
+ }
+
+ @Override
+ public void createChildren(ElementResolver resolver) {
+ archTests.handleFields(field ->
+ resolver.resolve(
+ FIELD_SEGMENT_TYPE,
+ field.getName(),
+ childResolver -> resolveChildren(field, childResolver)));
+
+ archTests.handleMethods(method ->
+ resolver.resolve(
+ METHOD_SEGMENT_TYPE,
+ method.getName(),
+ childResolver -> addChild(method)));
+ }
+
+ private void resolveChildren(Field field, ElementResolver childResolver) {
+ ArchUnitTestDescriptor.resolveChildren(
+ this,
+ childResolver,
+ new TestMember<>(archTests.getDefinitionLocation(), field),
+ classes);
+ }
+
+ private void addChild(Method method) {
+ addChild(new ArchUnitMethodDescriptor(
+ getUniqueId(),
+ new TestMember<>(archTests.getDefinitionLocation(), method),
+ classes));
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+ }
+
+ private static class DeclaredArchTests {
+ private final ArchTests archTests;
+
+ DeclaredArchTests(ArchTests archTests) {
+ this.archTests = archTests;
+ }
+
+ Class> getDefinitionLocation() {
+ return archTests.getDefinitionLocation();
+ }
+
+ String getDisplayName() {
+ return archTests.getDefinitionLocation().getSimpleName();
+ }
+
+ void handleFields(Consumer super Field> doWithField) {
+ getAllFields(archTests.getDefinitionLocation(), withAnnotation(ArchTest.class)).forEach(doWithField);
+ }
+
+ void handleMethods(Consumer super Method> doWithMethod) {
+ getAllMethods(archTests.getDefinitionLocation(), withAnnotation(ArchTest.class)).forEach(doWithMethod);
+ }
+ }
+
+ private static class JUnit6ClassAnalysisRequest implements ClassAnalysisRequest {
+ private final AnalyzeClasses analyzeClasses;
+
+ JUnit6ClassAnalysisRequest(Class> testClass) {
+ analyzeClasses = findAnnotation(testClass, AnalyzeClasses.class);
+ }
+
+ @Override
+ public String[] getPackageNames() {
+ return analyzeClasses.packages();
+ }
+
+ @Override
+ public Class>[] getPackageRoots() {
+ return analyzeClasses.packagesOf();
+ }
+
+ @Override
+ public Class extends LocationProvider>[] getLocationProviders() {
+ return analyzeClasses.locations();
+ }
+
+ @Override
+ public Class extends ImportOption>[] getImportOptions() {
+ return analyzeClasses.importOptions();
+ }
+
+ @Override
+ public CacheMode getCacheMode() {
+ return analyzeClasses.cacheMode();
+ }
+
+ @Override
+ public boolean scanWholeClasspath() {
+ return analyzeClasses.wholeClasspath();
+ }
+ }
+
+ private static class TestMember {
+ final Class> owner;
+ final MEMBER member;
+
+ TestMember(Class> owner, MEMBER member) {
+ this.owner = owner;
+ this.member = member;
+ }
+
+ String getName() {
+ return member.getName();
+ }
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java
new file mode 100644
index 0000000000..67e590d8bb
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngine.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import com.tngtech.archunit.ArchConfiguration;
+import com.tngtech.archunit.Internal;
+import com.tngtech.archunit.base.MayResolveTypesViaReflection;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.resolvers.ClassResolver;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.engine_api.FieldSelector;
+import org.junit.platform.engine.EngineDiscoveryRequest;
+import org.junit.platform.engine.ExecutionRequest;
+import org.junit.platform.engine.Filter;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.ClassNameFilter;
+import org.junit.platform.engine.discovery.ClassSelector;
+import org.junit.platform.engine.discovery.ClasspathRootSelector;
+import org.junit.platform.engine.discovery.MethodSelector;
+import org.junit.platform.engine.discovery.PackageNameFilter;
+import org.junit.platform.engine.discovery.PackageSelector;
+import org.junit.platform.engine.discovery.UniqueIdSelector;
+import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine;
+
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllFields;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.getAllMethods;
+import static com.tngtech.archunit.junit.internal.ReflectionUtils.withAnnotation;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * A simple test engine to discover and execute ArchUnit tests with JUnit 5. In particular the engine
+ * uses a {@link ClassCache} to avoid the costly import process as much as possible.
+ *
+ * Mark classes to be executed by the {@link ArchUnitTestEngine} with {@link AnalyzeClasses @AnalyzeClasses} and
+ * rule fields or methods with {@link ArchTest @ArchTest}. Example:
+ *
+ *{@literal @}AnalyzeClasses(packages = "com.foo")
+ * class MyArchTest {
+ * {@literal @}ArchTest
+ * public static final ArchRule myRule = classes()...
+ * }
+ *
+ */
+@Internal
+public final class ArchUnitTestEngine extends HierarchicalTestEngine {
+ static final String UNIQUE_ID = "archunit";
+
+ private final ArchUnitSystemPropertyTestFilterJUnit6 systemPropertyTestFilter = new ArchUnitSystemPropertyTestFilterJUnit6();
+
+ @SuppressWarnings("FieldMayBeFinal")
+ private SharedCache cache = new SharedCache(); // NOTE: We want to change this in tests -> no static/final reference
+
+ @Override
+ public String getId() {
+ return UNIQUE_ID;
+ }
+
+ @Override
+ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
+ ArchUnitEngineDescriptor result = new ArchUnitEngineDescriptor(uniqueId);
+
+ resolveRequestedClasspathRoot(discoveryRequest, uniqueId, result);
+ resolveRequestedPackages(discoveryRequest, uniqueId, result);
+ resolveRequestedClasses(discoveryRequest, uniqueId, result);
+ resolveRequestedMethods(discoveryRequest, uniqueId, result);
+ resolveRequestedFields(discoveryRequest, uniqueId, result);
+ resolveRequestedUniqueIds(discoveryRequest, uniqueId, result);
+
+ systemPropertyTestFilter.filter(result);
+
+ return result;
+ }
+
+ private void resolveRequestedClasspathRoot(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) {
+ Stream classes = discoveryRequest.getSelectorsByType(ClasspathRootSelector.class).stream()
+ .flatMap(this::getContainedClasses);
+ filterCandidatesAndLoadClasses(classes, discoveryRequest)
+ .forEach(clazz -> ArchUnitTestDescriptor.resolve(
+ result, ElementResolver.create(result, uniqueId, clazz), cache.get()));
+ }
+
+ private void resolveRequestedPackages(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) {
+ String[] packages = discoveryRequest.getSelectorsByType(PackageSelector.class).stream()
+ .map(PackageSelector::getPackageName)
+ .toArray(String[]::new);
+ Stream classes = getContainedClasses(packages);
+
+ filterCandidatesAndLoadClasses(classes, discoveryRequest)
+ .forEach(clazz -> ArchUnitTestDescriptor.resolve(
+ result, ElementResolver.create(result, uniqueId, clazz), cache.get()));
+ }
+
+ private Stream> filterCandidatesAndLoadClasses(Stream classes, EngineDiscoveryRequest discoveryRequest) {
+ return classes
+ .filter(isAllowedBy(discoveryRequest))
+ .filter(this::isArchUnitTestCandidate)
+ .flatMap(this::safelyReflect);
+ }
+
+ private void resolveRequestedClasses(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId, ArchUnitEngineDescriptor result) {
+ discoveryRequest.getSelectorsByType(ClassSelector.class).stream()
+ .map(ClassSelector::getJavaClass)
+ .filter(this::isArchUnitTestCandidate)
+ .forEach(clazz -> ArchUnitTestDescriptor.resolve(
+ result, ElementResolver.create(result, uniqueId, clazz), cache.get()));
+ }
+
+ 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()));
+ }
+
+ 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()));
+ }
+
+ 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()));
+ }
+
+ private Stream getContainedClasses(String[] packages) {
+ if (packages.length == 0) {
+ return Stream.empty();
+ }
+ return discoverClasses(importer -> importer.importPackages(packages));
+ }
+
+ private Stream getContainedClasses(ClasspathRootSelector selector) {
+ return discoverClasses(importer -> importer.importUrl(toUrl(selector.getClasspathRoot())));
+ }
+
+ private Stream discoverClasses(Function importClasses) {
+ return ArchConfiguration.withThreadLocalScope(configuration -> {
+ configuration.setClassResolver(NoOpClassResolver.class);
+ return importClasses.apply(new ClassFileImporter()).stream();
+ });
+ }
+
+ private Predicate isAllowedBy(EngineDiscoveryRequest discoveryRequest) {
+ List> filters = Stream
+ .concat(discoveryRequest.getFiltersByType(ClassNameFilter.class).stream(),
+ discoveryRequest.getFiltersByType(PackageNameFilter.class).stream())
+ .map(Filter::toPredicate)
+ .collect(toList());
+
+ return javaClass -> filters.stream().allMatch(p -> p.test(javaClass.getName()));
+ }
+
+ private boolean isArchUnitTestCandidate(JavaClass javaClass) {
+ return javaClass.getAllMembers().stream().anyMatch(m -> m.isAnnotatedWith(ArchTest.class));
+ }
+
+ @MayResolveTypesViaReflection(reason = "Within the ArchUnitTestEngine we may resolve types via reflection, since they are needed anyway")
+ private Stream> safelyReflect(JavaClass javaClass) {
+ try {
+ return Stream.of(javaClass.reflect());
+ } catch (NoClassDefFoundError | RuntimeException e) {
+ return Stream.empty();
+ }
+ }
+
+ private boolean isArchUnitTestCandidate(Class> clazz) {
+ try {
+ return !getAllFields(clazz, withAnnotation(ArchTest.class)).isEmpty()
+ || !getAllMethods(clazz, withAnnotation(ArchTest.class)).isEmpty();
+ } catch (NoClassDefFoundError | Exception e) {
+ return false;
+ }
+ }
+
+ private URL toUrl(URI uri) {
+ try {
+ return uri.toURL();
+ } catch (MalformedURLException e) {
+ throw new ArchTestInitializationException(e);
+ }
+ }
+
+ @Override
+ protected ArchUnitEngineExecutionContext createExecutionContext(ExecutionRequest request) {
+ return new ArchUnitEngineExecutionContext();
+ }
+
+ static class SharedCache {
+ private static final ClassCache cache = new ClassCache();
+
+ ClassCache get() {
+ return cache;
+ }
+ }
+
+ static class NoOpClassResolver implements ClassResolver {
+ @Override
+ public void setClassUriImporter(ClassUriImporter classUriImporter) {
+ }
+
+ @Override
+ public Optional tryResolve(String typeName) {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java
new file mode 100644
index 0000000000..650b7f7600
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/CreatesChildren.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2026 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;
+
+interface CreatesChildren {
+ void createChildren(ElementResolver resolver);
+}
diff --git a/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ElementResolver.java b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ElementResolver.java
new file mode 100644
index 0000000000..9ac9a85d77
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/java/com/tngtech/archunit/junit/internal/ElementResolver.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2014-2026 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;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import com.tngtech.archunit.base.ClassLoaders;
+import com.tngtech.archunit.base.MayResolveTypesViaReflection;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.UniqueId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.CLASS_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.FIELD_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.METHOD_SEGMENT_TYPE;
+
+class ElementResolver {
+ private final ArchUnitEngineDescriptor engineDescriptor;
+ private final UniqueId processedId;
+ private final Deque segmentsToResolve;
+
+ private ElementResolver(ArchUnitEngineDescriptor engineDescriptor, UniqueId processedId, Deque segmentsToResolve) {
+ this.engineDescriptor = checkNotNull(engineDescriptor);
+ this.processedId = checkNotNull(processedId);
+ this.segmentsToResolve = checkNotNull(segmentsToResolve);
+ }
+
+ PossiblyResolvedClass resolveClass() {
+ UniqueId.Segment nextSegment = checkNotNull(segmentsToResolve.peekFirst());
+ if (!CLASS_SEGMENT_TYPE.equals(nextSegment.getType())) {
+ return new ClassNotRequested();
+ }
+
+ return tryResolveClass(classOf(nextSegment), processedId.append(nextSegment));
+ }
+
+ PossiblyResolvedClass resolveClass(Class> clazz) {
+ return tryResolveClass(clazz, processedId.append(CLASS_SEGMENT_TYPE, clazz.getName()));
+ }
+
+ PossiblyResolvedMember resolveMethod(Method method) {
+ return resolveMember(method, METHOD_SEGMENT_TYPE);
+ }
+
+ PossiblyResolvedMember resolveField(Field field) {
+ return resolveMember(field, FIELD_SEGMENT_TYPE);
+ }
+
+ private PossiblyResolvedMember resolveMember(Member member, String segmentType) {
+ UniqueId requestedId = processedId.append(segmentType, member.getName());
+ return engineDescriptor.findByUniqueId(requestedId).isPresent()
+ ? new SuccessfullyResolvedMember()
+ : new UnresolvedMember(member, segmentType);
+ }
+
+ void resolve(String segmentType, String segmentValue, Consumer doIfResolved) {
+ if (segmentsToResolve.isEmpty()) {
+ handleNewSegment(segmentType, segmentValue, doIfResolved);
+ } else {
+ handleRequestedSegment(segmentType, segmentValue, doIfResolved);
+ }
+ }
+
+ UniqueId getUniqueId() {
+ return processedId;
+ }
+
+ private PossiblyResolvedClass tryResolveClass(Class> clazz, UniqueId classId) {
+ ElementResolver childResolver = new ElementResolver(engineDescriptor, classId, tail(segmentsToResolve));
+ return engineDescriptor.findByUniqueId(classId)
+ .map(testDescriptor ->
+ new RequestedAndSuccessfullyResolvedClass(testDescriptor, childResolver))
+ .orElseGet(() -> new RequestedButUnresolvedClass(clazz, childResolver));
+ }
+
+ private Deque tail(Deque segmentsToResolve) {
+ LinkedList result = new LinkedList<>(segmentsToResolve);
+ result.pollFirst();
+ return result;
+ }
+
+ @MayResolveTypesViaReflection(reason = "Within the ArchUnitTestEngine we may resolve types via reflection, since they are needed anyway")
+ private Class> classOf(UniqueId.Segment segment) {
+ try {
+ return ClassLoaders.loadClass(segment.getValue());
+ } catch (ClassNotFoundException e) {
+ throw new ArchTestInitializationException(e, "Failed to load class from %s segment %s",
+ UniqueId.class.getSimpleName(), segment);
+ }
+ }
+
+ private void handleRequestedSegment(String segmentType, String segmentValue, Consumer doIfResolved) {
+ UniqueId.Segment nextSegment = checkNotNull(segmentsToResolve.peekFirst());
+ if (matches(segmentType, segmentValue).test(nextSegment)) {
+ doIfResolved.accept(new ElementResolver(engineDescriptor, processedId.append(nextSegment), tail(segmentsToResolve)));
+ }
+ }
+
+ private Predicate matches(String segmentType, String segmentValue) {
+ return nextSegment -> nextSegment.getType().equals(segmentType) && nextSegment.getValue().equals(segmentValue);
+ }
+
+ private void handleNewSegment(String segmentType, String segmentValue, Consumer doIfResolved) {
+ doIfResolved.accept(new ElementResolver(engineDescriptor, processedId.append(segmentType, segmentValue), new LinkedList<>()));
+ }
+
+ static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, UniqueId targetId) {
+ return new ElementResolver(engineDescriptor, rootId, getRemainingSegments(rootId, targetId));
+ }
+
+ static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass) {
+ UniqueId targetId = rootId.append(CLASS_SEGMENT_TYPE, testClass.getName());
+ return create(engineDescriptor, rootId, targetId);
+ }
+
+ static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass, Method testMethod) {
+ UniqueId targetId = rootId
+ .append(CLASS_SEGMENT_TYPE, testClass.getName())
+ .append(METHOD_SEGMENT_TYPE, testMethod.getName());
+ return create(engineDescriptor, rootId, targetId);
+ }
+
+ static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass, Field testField) {
+ UniqueId targetId = rootId
+ .append(CLASS_SEGMENT_TYPE, testClass.getName())
+ .append(FIELD_SEGMENT_TYPE, testField.getName());
+ return create(engineDescriptor, rootId, targetId);
+ }
+
+ private static Deque getRemainingSegments(UniqueId rootId, UniqueId targetId) {
+ Deque remainingSegments = new LinkedList<>(targetId.getSegments());
+ rootId.getSegments().forEach(segment -> {
+ checkState(segment.equals(remainingSegments.peekFirst()),
+ "targetId %s should start with rootId %s", targetId, rootId);
+ remainingSegments.pollFirst();
+ });
+ return remainingSegments;
+ }
+
+ abstract static class PossiblyResolvedClass {
+ void ifRequestedButUnresolved(BiConsumer, ElementResolver> doIfResolved) {
+ }
+
+ PossiblyResolvedClass ifRequestedAndResolved(BiConsumer doIfResolved) {
+ return this;
+ }
+ }
+
+ private static class RequestedAndSuccessfullyResolvedClass extends PossiblyResolvedClass {
+ private final CreatesChildren classDescriptor;
+ private final ElementResolver childResolver;
+
+ RequestedAndSuccessfullyResolvedClass(TestDescriptor classDescriptor, ElementResolver childResolver) {
+ checkArgument(classDescriptor instanceof CreatesChildren,
+ "descriptor with uniqueId %s is expected to implement %s",
+ classDescriptor.getUniqueId(), CreatesChildren.class.getSimpleName());
+
+ this.classDescriptor = (CreatesChildren) classDescriptor;
+ this.childResolver = childResolver;
+ }
+
+ @Override
+ RequestedAndSuccessfullyResolvedClass ifRequestedAndResolved(BiConsumer doIfResolved) {
+ doIfResolved.accept(classDescriptor, childResolver);
+ return this;
+ }
+ }
+
+ private class RequestedButUnresolvedClass extends PossiblyResolvedClass {
+ private final Class> clazz;
+ private final ElementResolver childResolver;
+
+ RequestedButUnresolvedClass(Class> clazz, ElementResolver childResolver) {
+ this.clazz = clazz;
+ this.childResolver = childResolver;
+ }
+
+ @Override
+ void ifRequestedButUnresolved(BiConsumer, ElementResolver> doWithChildResolver) {
+ doWithChildResolver.accept(clazz, childResolver);
+ }
+ }
+
+ private static class ClassNotRequested extends PossiblyResolvedClass {
+ }
+
+ abstract static class PossiblyResolvedMember {
+ abstract void ifUnresolved(Consumer childResolver);
+ }
+
+ private static class SuccessfullyResolvedMember extends PossiblyResolvedMember {
+ @Override
+ void ifUnresolved(Consumer childResolver) {
+ }
+ }
+
+ private class UnresolvedMember extends PossiblyResolvedMember {
+ private final Member member;
+ private final String segmentType;
+
+ UnresolvedMember(Member member, String segmentType) {
+ this.member = member;
+ this.segmentType = segmentType;
+ }
+
+ @Override
+ void ifUnresolved(Consumer doWithChildResolver) {
+ resolve(segmentType, member.getName(), doWithChildResolver);
+ }
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine b/archunit-junit/junit6/engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine
new file mode 100644
index 0000000000..65a1f817b8
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine
@@ -0,0 +1 @@
+com.tngtech.archunit.junit.internal.ArchUnitTestEngine
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java
new file mode 100644
index 0000000000..449c2f3269
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/ArchUnitTestEngineTest.java
@@ -0,0 +1,1510 @@
+package com.tngtech.archunit.junit.internal;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.archunit.ArchConfiguration;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+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.engine_api.FieldSource;
+import com.tngtech.archunit.junit.internal.ArchUnitTestEngine.SharedCache;
+import com.tngtech.archunit.junit.internal.testexamples.ClassWithPrivateTests;
+import com.tngtech.archunit.junit.internal.testexamples.ComplexMetaTags;
+import com.tngtech.archunit.junit.internal.testexamples.ComplexRuleLibrary;
+import com.tngtech.archunit.junit.internal.testexamples.ComplexTags;
+import com.tngtech.archunit.junit.internal.testexamples.FullAnalyzeClassesSpec;
+import com.tngtech.archunit.junit.internal.testexamples.LibraryWithPrivateTests;
+import com.tngtech.archunit.junit.internal.testexamples.SimpleRuleLibrary;
+import com.tngtech.archunit.junit.internal.testexamples.TestClassWithMetaAnnotationForAnalyzeClasses;
+import com.tngtech.archunit.junit.internal.testexamples.TestClassWithMetaTag;
+import com.tngtech.archunit.junit.internal.testexamples.TestClassWithMetaTags;
+import com.tngtech.archunit.junit.internal.testexamples.TestClassWithTags;
+import com.tngtech.archunit.junit.internal.testexamples.TestFieldWithMetaTag;
+import com.tngtech.archunit.junit.internal.testexamples.TestFieldWithMetaTags;
+import com.tngtech.archunit.junit.internal.testexamples.TestFieldWithTags;
+import com.tngtech.archunit.junit.internal.testexamples.TestMethodWithMetaTag;
+import com.tngtech.archunit.junit.internal.testexamples.TestMethodWithMetaTags;
+import com.tngtech.archunit.junit.internal.testexamples.TestMethodWithTags;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+import com.tngtech.archunit.junit.internal.testexamples.abstractbase.ArchTestWithAbstractBaseClassWithFieldRule;
+import com.tngtech.archunit.junit.internal.testexamples.abstractbase.ArchTestWithAbstractBaseClassWithMethodRule;
+import com.tngtech.archunit.junit.internal.testexamples.abstractbase.ArchTestWithLibraryWithAbstractBaseClass;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.IgnoredClass;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.IgnoredField;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.IgnoredLibrary;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.IgnoredMethod;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.MetaIgnoredClass;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.MetaIgnoredField;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.MetaIgnoredLibrary;
+import com.tngtech.archunit.junit.internal.testexamples.ignores.MetaIgnoredMethod;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleMethod;
+import com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules;
+import com.tngtech.archunit.junit.internal.testexamples.wrong.WrongRuleMethodNotStatic;
+import com.tngtech.archunit.junit.internal.testexamples.wrong.WrongRuleMethodWrongParameters;
+import com.tngtech.archunit.junit.internal.testutil.LogCaptor;
+import com.tngtech.archunit.junit.internal.testutil.SystemPropertiesExtension;
+import com.tngtech.archunit.junit.internal.testutil.TestLogExtension;
+import com.tngtech.archunit.testutil.TestLogRecorder.RecordedLogEvent;
+import org.junit.jupiter.api.AfterEach;
+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.platform.engine.ExecutionRequest;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestTag;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.ClasspathRootSelector;
+import org.junit.platform.engine.discovery.MethodSelector;
+import org.junit.platform.engine.discovery.PackageSelector;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+import org.junit.platform.engine.support.descriptor.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.tngtech.archunit.core.domain.TestUtils.importClasses;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.CLASS_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.FIELD_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.METHOD_SEGMENT_TYPE;
+import static com.tngtech.archunit.junit.internal.EngineExecutionTestListener.onlyElement;
+import static com.tngtech.archunit.junit.internal.testexamples.TestFieldWithMetaTag.FIELD_WITH_META_TAG_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.TestFieldWithMetaTags.FIELD_WITH_META_TAGS_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.TestFieldWithTags.FIELD_WITH_TAG_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.TestMethodWithMetaTag.METHOD_WITH_META_TAG_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.TestMethodWithMetaTags.METHOD_WITH_META_TAGS_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.TestMethodWithTags.METHOD_WITH_TAG_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField.SIMPLE_RULE_FIELD_NAME;
+import static com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleMethod.SIMPLE_RULE_METHOD_NAME;
+import static com.tngtech.archunit.testutil.ReflectionTestUtils.field;
+import static java.util.Collections.singleton;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.logging.log4j.Level.DEBUG;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER;
+import static org.junit.platform.engine.TestDescriptor.Type.TEST;
+import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns;
+import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
+import static org.junit.platform.engine.discovery.PackageNameFilter.excludePackageNames;
+import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@ExtendWith({MockitoExtension.class, SystemPropertiesExtension.class})
+@MockitoSettings(strictness = Strictness.WARN)
+class ArchUnitTestEngineTest {
+ @Mock
+ private ClassCache classCache;
+ @Mock
+ private SharedCache sharedCache;
+ @Captor
+ private ArgumentCaptor classAnalysisRequestCaptor;
+
+ @InjectMocks
+ private ArchUnitTestEngine testEngine;
+
+ private final UniqueId engineId = createEngineId();
+
+ @BeforeEach
+ void setUp() {
+ when(sharedCache.get()).thenReturn(classCache);
+ }
+
+ @Nested
+ class Discovers {
+ @Test
+ void a_root_that_is_a_test_container() {
+ TestDescriptor descriptor = testEngine.discover(new EngineDiscoveryTestRequest(), createEngineId());
+
+ assertThat(descriptor.getType().isContainer()).as("Root descriptor is container").isTrue();
+ }
+
+ @Test
+ void a_root_with_the_correct_unique_id() {
+
+ TestDescriptor descriptor = testEngine.discover(new EngineDiscoveryTestRequest(), engineId);
+
+ assertThat(descriptor.getUniqueId()).isEqualTo(engineId);
+ }
+
+ @Test
+ void a_single_test_class() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleField.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ 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.getType()).isEqualTo(CONTAINER);
+ assertThat(child.getParent().get()).isEqualTo(descriptor);
+ }
+
+ @Test
+ void a_test_class_with_meta_annotated_analyze_classes() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(TestClassWithMetaAnnotationForAnalyzeClasses.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor child = getOnlyElement(descriptor.getChildren());
+ assertThat(child).isInstanceOf(ArchUnitTestDescriptor.class);
+ assertThat(child.getUniqueId()).isEqualTo(engineId.append(CLASS_SEGMENT_TYPE, TestClassWithMetaAnnotationForAnalyzeClasses.class.getName()));
+ assertThat(child.getDisplayName()).isEqualTo(TestClassWithMetaAnnotationForAnalyzeClasses.class.getSimpleName());
+ assertThat(child.getType()).isEqualTo(CONTAINER);
+ assertThat(child.getParent()).get().isEqualTo(descriptor);
+ }
+
+ @Test
+ void source_of_a_single_test_class() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleField.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, createEngineId());
+
+ TestDescriptor child = getOnlyElement(descriptor.getChildren());
+
+ assertClassSource(child, SimpleRuleField.class);
+ }
+
+ @Test
+ void multiple_test_classes() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClass(SimpleRuleField.class)
+ .withClass(SimpleRuleMethod.class);
+
+ 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());
+ }
+
+ @Test
+ void a_class_with_simple_rule_field() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleField.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor ruleDescriptor = getOnlyTest(descriptor);
+
+ assertThat(ruleDescriptor.getUniqueId()).isEqualTo(simpleRuleFieldTestId(engineId));
+ FieldSource testSource = ((FieldSource) ruleDescriptor.getSource().get());
+ assertThat(testSource.getJavaClass()).isEqualTo(SimpleRuleField.class);
+ assertThat(testSource.getClassName()).isEqualTo(SimpleRuleField.class.getName());
+ assertThat(testSource.getFieldName()).isEqualTo(SIMPLE_RULE_FIELD_NAME);
+ }
+
+ @Test
+ void a_class_with_simple_rule_method() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleMethod.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor ruleDescriptor = getOnlyTest(descriptor);
+
+ assertThat(ruleDescriptor.getUniqueId()).isEqualTo(simpleRuleMethodTestId(engineId));
+ MethodSource testSource = (MethodSource) ruleDescriptor.getSource().get();
+ assertThat(testSource.getClassName()).isEqualTo(SimpleRuleMethod.class.getName());
+ assertThat(testSource.getMethodName()).isEqualTo(SIMPLE_RULE_METHOD_NAME);
+ assertThat(testSource.getMethodParameterTypes()).isEqualTo(JavaClasses.class.getName());
+ }
+
+ @Test
+ void a_class_with_simple_hierarchy__descriptor_types() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleLibrary.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Stream archRulesDescriptors = getArchRulesDescriptorsOfOnlyChild(descriptor);
+ boolean allAreContainer = archRulesDescriptors.allMatch(d -> d.getType().equals(CONTAINER));
+ assertThat(allAreContainer).as("all rules descriptor have type " + CONTAINER).isTrue();
+ }
+
+ @Test
+ void a_class_with_simple_hierarchy__uniqueIds() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleLibrary.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Stream archRulesDescriptors = getArchRulesDescriptorsOfOnlyChild(descriptor);
+
+ Set expectedIds = getExpectedIdsForSimpleRuleLibrary(engineId);
+ Set actualIds = archRulesDescriptors.flatMap(d -> d.getChildren().stream())
+ .map(TestDescriptor::getUniqueId).collect(toSet());
+ assertThat(actualIds).isEqualTo(expectedIds);
+ }
+
+ @Test
+ void a_class_with_simple_hierarchy__class_source() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleLibrary.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertClassSource(getOnlyElement(descriptor.getChildren()), SimpleRuleLibrary.class);
+
+ List archRulesDescriptors = getArchRulesDescriptorsOfOnlyChild(descriptor).collect(toList());
+
+ TestDescriptor testDescriptor = findRulesDescriptor(archRulesDescriptors, SimpleRules.class);
+ assertNoIntermediaryTestSource(testDescriptor);
+ testDescriptor.getChildren().forEach(d ->
+ assertThat(d.getSource().isPresent()).as("source is present").isTrue());
+
+ testDescriptor = findRulesDescriptor(archRulesDescriptors, SimpleRuleField.class);
+ assertNoIntermediaryTestSource(testDescriptor);
+ testDescriptor.getChildren().forEach(d ->
+ assertThat(d.getSource().isPresent()).as("source is present").isTrue());
+ }
+
+ @Test
+ void a_class_with_simple_hierarchy__display_name() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleLibrary.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ List archRulesDescriptors = getArchRulesDescriptorsOfOnlyChild(descriptor).collect(toList());
+
+ TestDescriptor testDescriptor = findRulesDescriptor(archRulesDescriptors, SimpleRules.class);
+ assertThat(testDescriptor.getChildren().stream().map(TestDescriptor::getDisplayName)).contains(
+ SimpleRules.class.getSimpleName() + " > " + SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME
+ );
+
+ testDescriptor = findRulesDescriptor(archRulesDescriptors, SimpleRuleField.class);
+ assertThat(testDescriptor.getChildren().stream().map(TestDescriptor::getDisplayName)).contains(
+ SimpleRuleField.class.getSimpleName() + " > " + SIMPLE_RULE_FIELD_NAME
+ );
+ }
+
+ @Test
+ void a_class_with_complex_hierarchy() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(ComplexRuleLibrary.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("all leaf unique ids of complex hierarchy")
+ .containsOnlyElementsOf(getExpectedIdsForComplexRuleLibrary(engineId));
+ }
+
+ @Test
+ void a_class_with_complex_hierarchy__display_names() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(ComplexRuleLibrary.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor complexRuleLibraryDescriptor = findRulesDescriptor(rootDescriptor.getChildren(), ComplexRuleLibrary.class);
+ TestDescriptor simpleRuleLibraryDescriptor = findRulesDescriptor(complexRuleLibraryDescriptor.getChildren(), SimpleRuleLibrary.class);
+ TestDescriptor simpleRulesDescriptor = findRulesDescriptor(simpleRuleLibraryDescriptor.getChildren(), SimpleRules.class);
+ assertThat(simpleRulesDescriptor.getChildren().stream().map(TestDescriptor::getDisplayName)).contains(
+ SimpleRuleLibrary.class.getSimpleName() +
+ " > " + SimpleRules.class.getSimpleName() +
+ " > " + SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME
+ );
+ }
+
+ @Test
+ void private_instance_members() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(ClassWithPrivateTests.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("all leaf unique ids of private members")
+ .containsOnly(privateRuleFieldId(engineId), privateRuleMethodId(engineId));
+ }
+
+ @Test
+ void private_instance_libraries() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(LibraryWithPrivateTests.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("all leaf unique ids of private members")
+ .containsOnlyElementsOf(privateRuleLibraryIds(engineId));
+ }
+
+ @Test
+ void an_unique_id() {
+ UniqueId ruleIdToDiscover = simpleRulesId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withUniqueId(ruleIdToDiscover);
+
+ TestDescriptor descriptor = getOnlyTest(testEngine.discover(discoveryRequest, engineId));
+
+ assertThat(descriptor.getUniqueId()).isEqualTo(ruleIdToDiscover);
+ assertThat(descriptor.getDisplayName()).isEqualTo(SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ assertThat(descriptor.getChildren()).isEmpty();
+ assertThat(descriptor.getDescendants()).isEmpty();
+ assertThat(descriptor.getType()).isEqualTo(TEST);
+ assertThat(descriptor.getSource().get()).isEqualTo(FieldSource.from(field(SimpleRules.class, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME)));
+ assertThat(descriptor.getParent().get().getSource().get()).isEqualTo(ClassSource.from(SimpleRules.class));
+ }
+
+ @Test
+ void multiple_unique_ids() {
+ UniqueId testId = simpleRulesId(engineId);
+ UniqueId firstRuleIdToDiscover = testId.append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ UniqueId secondRuleIdToDiscover = testId.append(METHOD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_METHOD_ONE_NAME);
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withUniqueId(firstRuleIdToDiscover)
+ .withUniqueId(secondRuleIdToDiscover);
+
+ TestDescriptor test = getOnlyElement(testEngine.discover(discoveryRequest, engineId).getChildren());
+
+ Set discoveredRuleIds = toUniqueIds(test);
+ assertThat(discoveredRuleIds).containsOnly(firstRuleIdToDiscover, secondRuleIdToDiscover);
+ }
+
+ @Test
+ void no_redundant_descriptors() {
+ UniqueId redundantId = simpleRulesId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClass(SimpleRules.class)
+ .withUniqueId(redundantId);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor test = getOnlyElement(rootDescriptor.getChildren());
+ assertThat(test.getChildren()).as("all children of test").hasSize(4);
+ List descriptorsWithSpecifiedId = test.getChildren().stream()
+ .filter(descriptor -> descriptor.getUniqueId().equals(redundantId))
+ .collect(toList());
+ assertThat(descriptorsWithSpecifiedId)
+ .as("descriptors with id " + redundantId)
+ .hasSize(1);
+ }
+
+ @Test
+ void no_redundant_library_descriptors() {
+ UniqueId simpleRulesId = simpleRulesInLibraryId(engineId);
+ UniqueId ruleIdOne = simpleRulesId.append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ UniqueId ruleIdTwo = simpleRulesId.append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_TWO_NAME);
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withUniqueId(ruleIdOne)
+ .withUniqueId(ruleIdTwo);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor simpleRules = getArchRulesDescriptorsOfOnlyChild(rootDescriptor).collect(onlyElement());
+
+ assertThat(toUniqueIds(simpleRules))
+ .as("ids of requested children of " + SimpleRules.class.getSimpleName())
+ .containsOnly(ruleIdOne, ruleIdTwo);
+ }
+
+ @Test
+ void classpath_roots() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClasspathRoot(rootOfClass(Test.class))
+ .withClasspathRoot(rootOfClass(SimpleRules.class))
+ .withClassNameFilter(excludeClassNamePatterns(".*(W|w)rong.*"));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("children discovered by " + ClasspathRootSelector.class.getSimpleName())
+ .contains(simpleRulesInLibraryId(engineId).append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME))
+ .contains(simpleRuleFieldTestId(engineId))
+ .contains(simpleRuleMethodTestId(engineId));
+ }
+
+ @Test
+ void packages() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withPackage(SimpleRuleField.class.getPackage().getName())
+ .withPackage(SimpleRules.class.getPackage().getName());
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor))
+ .as("tests discovered by " + PackageSelector.class.getSimpleName())
+ .containsOnly(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName()),
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleMethod.class.getName()),
+ simpleRulesId(engineId));
+ }
+
+ @Test
+ void subpackages() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withPackage(SimpleRuleLibrary.class.getPackage().getName())
+ .withClassNameFilter(excludeClassNamePatterns(".*(W|w)rong.*"));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor))
+ .as("tests discovered by " + PackageSelector.class.getSimpleName())
+ .contains(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName()),
+ simpleRulesId(engineId));
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("children discovered by " + PackageSelector.class.getSimpleName())
+ .contains(
+ simpleRulesInLibraryId(engineId).append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME),
+ simpleRuleFieldTestId(engineId),
+ simpleRuleMethodTestId(engineId));
+ }
+
+ @Test
+ void methods() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withMethod(SimpleRuleMethod.class, SimpleRuleMethod.SIMPLE_RULE_METHOD_NAME)
+ .withMethod(SimpleRules.class, SimpleRules.SIMPLE_RULE_METHOD_ONE_NAME);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("children discovered by " + MethodSelector.class.getSimpleName())
+ .containsOnly(
+ simpleRuleMethodTestId(engineId),
+ simpleRulesId(engineId)
+ .append(METHOD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_METHOD_ONE_NAME));
+ }
+
+ @Test
+ void fields() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withField(SimpleRuleField.class, SimpleRuleField.SIMPLE_RULE_FIELD_NAME)
+ .withField(SimpleRules.class, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("children discovered by " + FieldSelector.class.getSimpleName())
+ .containsOnly(
+ simpleRuleFieldTestId(engineId),
+ simpleRulesId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME));
+ }
+
+ @Test
+ void mixed_class_methods_and_fields() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withField(SimpleRuleField.class, SimpleRuleField.SIMPLE_RULE_FIELD_NAME)
+ .withMethod(SimpleRuleMethod.class, SimpleRuleMethod.SIMPLE_RULE_METHOD_NAME)
+ .withClass(SimpleRules.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Set expectedLeafIds = new HashSet<>();
+ expectedLeafIds.add(simpleRuleFieldTestId(engineId));
+ expectedLeafIds.add(simpleRuleMethodTestId(engineId));
+ Stream.concat(
+ SimpleRules.RULE_FIELD_NAMES.stream().map(fieldName ->
+ simpleRulesId(engineId).append(FIELD_SEGMENT_TYPE, fieldName)),
+ SimpleRules.RULE_METHOD_NAMES.stream().map(methodName ->
+ simpleRulesId(engineId).append(METHOD_SEGMENT_TYPE, methodName)))
+ .forEach(expectedLeafIds::add);
+
+ assertThat(getAllLeafUniqueIds(rootDescriptor))
+ .as("children discovered by mixed selectors")
+ .containsOnlyElementsOf(expectedLeafIds);
+ }
+
+ @Test
+ void tags_of_test_classes() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(TestClassWithTags.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor testClass = getOnlyElement(descriptor.getChildren());
+ assertThat(testClass.getTags()).containsOnly(TestTag.create("tag-one"), TestTag.create("tag-two"));
+
+ Set extends TestDescriptor> concreteRules = getAllLeafs(testClass);
+ assertThat(concreteRules).as("concrete rules").hasSize(3);
+ concreteRules.forEach(concreteRule ->
+ assertThat(concreteRule.getTags()).containsOnly(TestTag.create("tag-one"), TestTag.create("tag-two"))
+ );
+ }
+
+ @Test
+ void meta_tag_of_test_classes() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(TestClassWithMetaTag.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor testClass = getOnlyElement(descriptor.getChildren());
+ assertThat(testClass.getTags()).containsOnly(TestTag.create("meta-tag-one"), TestTag.create("meta-tag-two"));
+
+ Set extends TestDescriptor> concreteRules = getAllLeafs(testClass);
+ assertThat(concreteRules).as("concrete rules").hasSize(3);
+ concreteRules.forEach(concreteRule ->
+ assertThat(concreteRule.getTags()).containsOnly(TestTag.create("meta-tag-one"), TestTag.create("meta-tag-two"))
+ );
+ }
+
+ @Test
+ void meta_tags_of_test_classes() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(TestClassWithMetaTags.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor testClass = getOnlyElement(descriptor.getChildren());
+ assertThat(testClass.getTags()).containsOnly(TestTag.create("meta-tags-one"), TestTag.create("meta-tags-two"));
+
+ Set extends TestDescriptor> concreteRules = getAllLeafs(testClass);
+ assertThat(concreteRules).as("concrete rules").hasSize(3);
+ concreteRules.forEach(concreteRule ->
+ assertThat(concreteRule.getTags()).containsOnly(TestTag.create("meta-tags-one"), TestTag.create("meta-tags-two"))
+ );
+ }
+
+ @Test
+ void tags_of_rule_fields() {
+ TestDescriptor testField = getOnlyChildWithDescriptorContaining(FIELD_WITH_TAG_NAME, TestFieldWithTags.class);
+
+ assertThat(testField.getTags()).containsOnly(TestTag.create("field-tag-one"), TestTag.create("field-tag-two"));
+ }
+
+ @Test
+ void meta_tag_of_rule_fields() {
+ TestDescriptor testField = getOnlyChildWithDescriptorContaining(FIELD_WITH_META_TAG_NAME, TestFieldWithMetaTag.class);
+
+ assertThat(testField.getTags()).containsOnly(TestTag.create("field-meta-tag-one"), TestTag.create("field-meta-tag-two"));
+ }
+
+ @Test
+ void meta_tags_of_rule_fields() {
+ TestDescriptor testField = getOnlyChildWithDescriptorContaining(FIELD_WITH_META_TAGS_NAME, TestFieldWithMetaTags.class);
+
+ assertThat(testField.getTags()).containsOnly(TestTag.create("field-meta-tags-one"), TestTag.create("field-meta-tags-two"));
+ }
+
+ @Test
+ void tags_of_rule_methods() {
+ TestDescriptor testMethod = getOnlyChildWithDescriptorContaining(METHOD_WITH_TAG_NAME, TestMethodWithTags.class);
+
+ assertThat(testMethod.getTags()).containsOnly(TestTag.create("method-tag-one"), TestTag.create("method-tag-two"));
+ }
+
+ @Test
+ void meta_tag_of_rule_methods() {
+ TestDescriptor testMethod = getOnlyChildWithDescriptorContaining(METHOD_WITH_META_TAG_NAME, TestMethodWithMetaTag.class);
+
+ assertThat(testMethod.getTags()).containsOnly(TestTag.create("method-meta-tag-one"), TestTag.create("method-meta-tag-two"));
+ }
+
+ @Test
+ void meta_tags_of_rule_methods() {
+ TestDescriptor testMethod = getOnlyChildWithDescriptorContaining(METHOD_WITH_META_TAGS_NAME, TestMethodWithMetaTags.class);
+
+ assertThat(testMethod.getTags()).containsOnly(TestTag.create("method-meta-tags-one"), TestTag.create("method-meta-tags-two"));
+ }
+
+ @Test
+ void complex_tags() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(ComplexTags.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Map> tagsById = new HashMap<>();
+ descriptor.accept(d -> tagsById.put(d.getUniqueId(), d.getTags()));
+
+ assertThat(getTagsForIdEndingIn(ComplexTags.class.getSimpleName(), tagsById))
+ .containsOnly(TestTag.create("library-tag"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithTags.class.getSimpleName(), tagsById))
+ .containsOnly(
+ TestTag.create("library-tag"),
+ TestTag.create("rules-tag"),
+ TestTag.create("tag-one"),
+ TestTag.create("tag-two"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithTags.FIELD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-tag"),
+ TestTag.create("rules-tag"),
+ TestTag.create("tag-one"),
+ TestTag.create("tag-two"));
+
+ assertThat(getTagsForIdEndingIn(ComplexTags.FIELD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-tag"),
+ TestTag.create("field-tag"));
+
+ assertThat(getTagsForIdEndingIn(ComplexTags.METHOD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-tag"),
+ TestTag.create("method-tag"));
+ }
+
+ @Test
+ void complex_meta_tags() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(ComplexMetaTags.class);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Map> tagsById = new HashMap<>();
+ descriptor.accept(d -> tagsById.put(d.getUniqueId(), d.getTags()));
+
+ assertThat(getTagsForIdEndingIn(ComplexMetaTags.class.getSimpleName(), tagsById))
+ .containsOnly(TestTag.create("library-meta-tag"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithMetaTag.class.getSimpleName(), tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("rules-meta-tag"),
+ TestTag.create("meta-tag-one"),
+ TestTag.create("meta-tag-two"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithMetaTags.class.getSimpleName(), tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("rules-meta-tag"),
+ TestTag.create("meta-tags-one"),
+ TestTag.create("meta-tags-two"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithMetaTag.FIELD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("rules-meta-tag"),
+ TestTag.create("meta-tag-one"),
+ TestTag.create("meta-tag-two"));
+
+ assertThat(getTagsForIdEndingIn(TestClassWithMetaTags.FIELD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("rules-meta-tag"),
+ TestTag.create("meta-tags-one"),
+ TestTag.create("meta-tags-two"));
+
+ assertThat(getTagsForIdEndingIn(ComplexMetaTags.FIELD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("field-meta-tag"));
+
+ assertThat(getTagsForIdEndingIn(ComplexMetaTags.METHOD_RULE_NAME, tagsById))
+ .containsOnly(
+ TestTag.create("library-meta-tag"),
+ TestTag.create("method-meta-tag"));
+ }
+
+ @Test
+ void filtering_excluded_class_names() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClasspathRoot(uriOfClass(SimpleRuleField.class))
+ .withClasspathRoot(uriOfClass(SimpleRuleMethod.class))
+ .withClasspathRoot(uriOfClass(SimpleRules.class))
+ .withClasspathRoot(uriOfClass(SimpleRuleLibrary.class))
+ .withClassNameFilter(excludeClassNamePatterns(".*(Field|Rules).*"));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor)).containsOnly(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleMethod.class.getName()),
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName()));
+ }
+
+ @Test
+ void filtering_included_class_names() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClasspathRoot(uriOfClass(SimpleRuleField.class))
+ .withClasspathRoot(uriOfClass(SimpleRuleMethod.class))
+ .withClasspathRoot(uriOfClass(SimpleRules.class))
+ .withClasspathRoot(uriOfClass(SimpleRuleLibrary.class))
+ .withClassNameFilter(includeClassNamePatterns(".*(Field|Rules).*"));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor)).containsOnly(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName()),
+ simpleRulesId(engineId));
+ }
+
+ @Test
+ void filtering_excluded_packages() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withPackage(SimpleRuleLibrary.class.getPackage().getName())
+ .withPackageNameFilter(excludePackageNames(
+ SimpleRuleField.class.getPackage().getName(),
+ SimpleRules.class.getPackage().getName(),
+ WrongRuleMethodNotStatic.class.getPackage().getName()));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor))
+ .contains(engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName()))
+ .doesNotContain(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName()),
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleMethod.class.getName()),
+ simpleRulesId(engineId));
+ }
+
+ @Test
+ void filtering_included_packages() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withPackage(SimpleRuleLibrary.class.getPackage().getName())
+ .withPackageNameFilter(includePackageNames(
+ SimpleRuleField.class.getPackage().getName(),
+ SimpleRules.class.getPackage().getName()));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor)).containsOnly(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName()),
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleMethod.class.getName()),
+ simpleRulesId(engineId));
+ }
+
+ @Test
+ void filtering_specific_rule_by_system_property() {
+ System.setProperty("archunit.junit.testFilter", SimpleRules.SIMPLE_RULE_FIELD_TWO_NAME);
+
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withPackage(SimpleRuleLibrary.class.getPackage().getName())
+ .withPackageNameFilter(excludePackageNames(WrongRuleMethodNotStatic.class.getPackage().getName()));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Set leafIds = getAllLeafUniqueIds(rootDescriptor);
+ assertThat(leafIds).isNotEmpty();
+ leafIds.forEach(leafId -> {
+ assertThat(leafId.getLastSegment().getType()).isEqualTo(FIELD_SEGMENT_TYPE);
+ assertThat(leafId.getLastSegment().getValue()).isEqualTo(SimpleRules.SIMPLE_RULE_FIELD_TWO_NAME);
+ });
+ }
+
+ @Test
+ void filtering_specific_rules_by_system_property() {
+ System.setProperty("archunit.junit.testFilter", "simple_rule_method_two,simple_rule,some_non_existing_rule");
+
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClass(SimpleRuleLibrary.class);
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ Set leafIds = getAllLeafUniqueIds(rootDescriptor);
+ assertThat(leafIds).containsOnly(
+ simpleRulesInLibraryId(engineId)
+ .append(METHOD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_METHOD_TWO_NAME),
+ simpleRuleFieldTestId(engineId
+ .append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, SimpleRuleLibrary.RULES_TWO_FIELD)));
+ }
+
+ @Test
+ void all_without_filters() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest()
+ .withClasspathRoot(uriOfClass(SimpleRuleField.class))
+ .withClasspathRoot(uriOfClass(SimpleRules.class));
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(toUniqueIds(rootDescriptor)).containsOnly(
+ engineId.append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName()),
+ simpleRulesId(engineId));
+ }
+
+ @Test
+ void without_importing_classes_already() {
+ EngineDiscoveryTestRequest discoveryRequest = discoveryRequestUsingAllMethodsOfDiscovery();
+
+ doThrow(new AssertionError("The cache should not be queried during discovery, "
+ + "otherwise classes will be imported when tests are possibly skipped afterwards"))
+ .when(classCache).getClassesToAnalyzeFor(any(), any());
+
+ TestDescriptor rootDescriptor = testEngine.discover(discoveryRequest, engineId);
+
+ assertThat(rootDescriptor).isNotNull();
+ }
+
+ @Test
+ @ExtendWith(TestLogExtension.class)
+ void without_scanning_unnecessary_classes_for_tests(LogCaptor logCaptor) {
+ EngineDiscoveryTestRequest discoveryRequest = discoveryRequestUsingAllMethodsOfDiscovery();
+
+ logCaptor.watch(ClassFileImporter.class, DEBUG);
+ testEngine.discover(discoveryRequest, engineId);
+
+ // since we discover the classes from the classpath with ArchUnit we expect DEBUG log entries for the test classes
+ assertThat(logCaptor.getEvents(DEBUG).stream())
+ .extracting(RecordedLogEvent::getMessage)
+ .as("messages of log events with level " + DEBUG)
+ .anySatisfy(message -> assertThat(message)
+ .matches(".*Processing class '.*" + SimpleRuleField.class.getSimpleName() + "'"));
+
+ // but we do not want to transitively resolve dependencies of those classes like java.lang.Object
+ assertThat(logCaptor.getEvents(DEBUG).stream())
+ .extracting(RecordedLogEvent::getMessage)
+ .as("messages of log events with level " + DEBUG)
+ .noneSatisfy(message -> assertThat(message)
+ .matches(".*Processing class '.*" + Object.class.getSimpleName() + "'"));
+ }
+
+ private EngineDiscoveryTestRequest discoveryRequestUsingAllMethodsOfDiscovery() {
+ return new EngineDiscoveryTestRequest()
+ .withClass(SimpleRuleField.class)
+ .withClasspathRoot(uriOfClass(SimpleRuleField.class))
+ .withField(SimpleRuleField.class, SimpleRuleField.SIMPLE_RULE_FIELD_NAME)
+ .withPackage(SimpleRuleField.class.getPackage().getName())
+ .withMethod(SimpleRuleMethod.class, SimpleRuleMethod.SIMPLE_RULE_METHOD_NAME)
+ .withUniqueId(simpleRulesId(engineId).append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME));
+ }
+
+ private Set getTagsForIdEndingIn(String suffix, Map> tagsById) {
+ UniqueId matchingId = tagsById.keySet().stream()
+ .filter(id -> getLast(id.getSegments()).getValue().endsWith(suffix))
+ .collect(onlyElement());
+ return tagsById.get(matchingId);
+ }
+
+ private TestDescriptor getOnlyChildWithDescriptorContaining(String idPart, Class> testClass) {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(testClass);
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ return getArchRulesDescriptorsOfOnlyChild(descriptor)
+ .filter(d -> d.getUniqueId().toString().contains(idPart))
+ .collect(onlyElement());
+ }
+
+ private Stream getArchRulesDescriptorsOfOnlyChild(TestDescriptor descriptor) {
+ TestDescriptor testClass = getOnlyElement(descriptor.getChildren());
+ Set extends TestDescriptor> archRulesDescriptors = testClass.getChildren();
+ return archRulesDescriptors.stream().map(identity());
+ }
+
+ private void assertClassSource(TestDescriptor child, Class> aClass) {
+ ClassSource classSource = (ClassSource) child.getSource().get();
+ assertThat(classSource.getClassName()).isEqualTo(aClass.getName());
+ assertThat(classSource.getJavaClass()).isEqualTo(aClass);
+ assertThat(classSource.getPosition().isPresent()).as("position is present").isFalse();
+ }
+
+ private void assertNoIntermediaryTestSource(TestDescriptor testDescriptor) {
+ // Test executors like Gradle test support traverse up the descriptor hierarchy until they find
+ // a test descriptor with a class source that they then pick for the test report.
+ // We want to show the original class used to trigger the tests for this to avoid confusion.
+ assertThat(testDescriptor.getSource()).as("source of intermediary test descriptor").isEmpty();
+ }
+
+ private TestDescriptor findRulesDescriptor(Collection extends TestDescriptor> archRulesDescriptors, Class> clazz) {
+ return archRulesDescriptors.stream().filter(d -> d.getUniqueId().toString().contains(clazz.getSimpleName())).findFirst().get();
+ }
+ }
+
+ @Nested
+ class Executes {
+ @Test
+ void a_simple_rule_field_without_violation() {
+ simulateCachedClassesForTest(SimpleRuleField.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleField.class);
+
+ testListener.verifySuccessful(simpleRuleFieldTestId(engineId));
+ }
+
+ @Test
+ void a_simple_rule_field_with_violation() {
+ simulateCachedClassesForTest(SimpleRuleField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleField.class);
+
+ testListener.verifyViolation(simpleRuleFieldTestId(engineId), UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ }
+
+ @Test
+ void instance_field_rule_in_abstract_base_class() {
+ simulateCachedClassesForTest(ArchTestWithAbstractBaseClassWithFieldRule.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, ArchTestWithAbstractBaseClassWithFieldRule.class);
+
+ testListener.verifySuccessful(engineId
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithFieldRule.class.getName())
+ .append(FIELD_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithFieldRule.INSTANCE_FIELD_NAME));
+ }
+
+ @Test
+ void a_simple_rule_method_without_violation() {
+ simulateCachedClassesForTest(SimpleRuleMethod.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleMethod.class);
+
+ testListener.verifySuccessful(simpleRuleMethodTestId(engineId));
+ }
+
+ @Test
+ void a_simple_rule_method_with_violation() {
+ simulateCachedClassesForTest(SimpleRuleMethod.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleMethod.class);
+
+ testListener.verifyViolation(simpleRuleMethodTestId(engineId), UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ }
+
+ @Test
+ void instance_method_rule_in_abstract_base_class() {
+ simulateCachedClassesForTest(ArchTestWithAbstractBaseClassWithMethodRule.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, ArchTestWithAbstractBaseClassWithMethodRule.class);
+
+ testListener.verifySuccessful(engineId
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithMethodRule.class.getName())
+ .append(METHOD_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithMethodRule.INSTANCE_METHOD_NAME));
+ }
+
+ @Test
+ void rule_library_without_violation() {
+ simulateCachedClassesForTest(SimpleRuleLibrary.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleLibrary.class);
+
+ getExpectedIdsForSimpleRuleLibrary(engineId).forEach(testListener::verifySuccessful);
+ }
+
+ @Test
+ void rule_library_with_violation() {
+ simulateCachedClassesForTest(SimpleRuleLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, SimpleRuleLibrary.class);
+
+ getExpectedIdsForSimpleRuleLibrary(engineId).forEach(testId ->
+ testListener.verifyViolation(testId, UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName()));
+ }
+
+ @Test
+ void private_instance_libraries() {
+ simulateCachedClassesForTest(LibraryWithPrivateTests.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, LibraryWithPrivateTests.class);
+
+ privateRuleLibraryIds(engineId).forEach(testId ->
+ testListener.verifyViolation(testId, UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName()));
+ }
+
+ @Test
+ public void library_with_rules_in_abstract_base_class() {
+ simulateCachedClassesForTest(ArchTestWithLibraryWithAbstractBaseClass.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, ArchTestWithLibraryWithAbstractBaseClass.class);
+
+ testListener.verifySuccessful(engineId
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithLibraryWithAbstractBaseClass.class.getName())
+ .append(FIELD_SEGMENT_TYPE, ArchTestWithLibraryWithAbstractBaseClass.FIELD_RULE_LIBRARY_NAME)
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithFieldRule.class.getName())
+ .append(FIELD_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithFieldRule.INSTANCE_FIELD_NAME)
+ );
+ testListener.verifySuccessful(engineId
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithLibraryWithAbstractBaseClass.class.getName())
+ .append(FIELD_SEGMENT_TYPE, ArchTestWithLibraryWithAbstractBaseClass.METHOD_RULE_LIBRARY_NAME)
+ .append(CLASS_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithMethodRule.class.getName())
+ .append(METHOD_SEGMENT_TYPE, ArchTestWithAbstractBaseClassWithMethodRule.INSTANCE_METHOD_NAME)
+ );
+ }
+
+ @Test
+ void rule_by_unique_id_without_violation() {
+ UniqueId fieldRuleInLibrary = simpleRulesInLibraryId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ simulateCachedClassesForTest(SimpleRuleLibrary.class, UnwantedClass.CLASS_SATISFYING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, new EngineDiscoveryTestRequest()
+ .withUniqueId(fieldRuleInLibrary));
+
+ testListener.verifySuccessful(fieldRuleInLibrary);
+ testListener.verifyNoOtherStartExceptHierarchyOf(fieldRuleInLibrary);
+ }
+
+ @Test
+ void rule_by_unique_id_with_violation() {
+ UniqueId fieldRuleInLibrary = simpleRulesInLibraryId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ simulateCachedClassesForTest(SimpleRuleLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, new EngineDiscoveryTestRequest()
+ .withUniqueId(fieldRuleInLibrary));
+
+ testListener.verifyViolation(fieldRuleInLibrary, UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ testListener.verifyNoOtherStartExceptHierarchyOf(fieldRuleInLibrary);
+ }
+
+ @Test
+ void mixed_rules_by_unique_id_and_class_with_violation() {
+ UniqueId fieldRuleInLibrary = simpleRulesInLibraryId(engineId)
+ .append(FIELD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_FIELD_ONE_NAME);
+ UniqueId methodRuleInLibrary = simpleRulesInLibraryId(engineId)
+ .append(METHOD_SEGMENT_TYPE, SimpleRules.SIMPLE_RULE_METHOD_ONE_NAME);
+ simulateCachedClassesForTest(SimpleRuleLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+ simulateCachedClassesForTest(SimpleRuleField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, new EngineDiscoveryTestRequest()
+ .withClass(SimpleRuleField.class)
+ .withUniqueId(fieldRuleInLibrary)
+ .withUniqueId(methodRuleInLibrary));
+
+ testListener.verifyViolation(simpleRuleFieldTestId(engineId), UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ testListener.verifyViolation(fieldRuleInLibrary, UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ testListener.verifyViolation(methodRuleInLibrary, UnwantedClass.CLASS_VIOLATING_RULES.getSimpleName());
+ }
+
+ @Test
+ void passes_AnalyzeClasses_to_cache() {
+ execute(createEngineId(), FullAnalyzeClassesSpec.class);
+
+ verify(classCache).getClassesToAnalyzeFor(eq(FullAnalyzeClassesSpec.class), classAnalysisRequestCaptor.capture());
+ ClassAnalysisRequest request = classAnalysisRequestCaptor.getValue();
+ AnalyzeClasses expected = FullAnalyzeClassesSpec.class.getAnnotation(AnalyzeClasses.class);
+ assertThat(request.getPackageNames()).isEqualTo(expected.packages());
+ assertThat(request.getPackageRoots()).isEqualTo(expected.packagesOf());
+ assertThat(request.getLocationProviders()).isEqualTo(expected.locations());
+ assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
+ assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
+ }
+
+ @Test
+ void cache_is_cleared_afterwards() {
+ execute(createEngineId(), SimpleRuleLibrary.class);
+
+ verify(classCache, times(1)).clear(SimpleRuleLibrary.class);
+ verify(classCache, atLeastOnce()).getClassesToAnalyzeFor(any(Class.class), any(ClassAnalysisRequest.class));
+ verifyNoMoreInteractions(classCache);
+ }
+
+ @Test
+ void a_class_with_analyze_classes_as_meta_annotation() {
+ execute(createEngineId(), TestClassWithMetaAnnotationForAnalyzeClasses.class);
+
+ verify(classCache).getClassesToAnalyzeFor(eq(TestClassWithMetaAnnotationForAnalyzeClasses.class), classAnalysisRequestCaptor.capture());
+ ClassAnalysisRequest request = classAnalysisRequestCaptor.getValue();
+ AnalyzeClasses expected = TestClassWithMetaAnnotationForAnalyzeClasses.class.getAnnotation(TestClassWithMetaAnnotationForAnalyzeClasses.MetaAnalyzeClasses.class)
+ .annotationType().getAnnotation(AnalyzeClasses.class);
+ assertThat(request.getPackageNames()).isEqualTo(expected.packages());
+ assertThat(request.getPackageRoots()).isEqualTo(expected.packagesOf());
+ assertThat(request.getLocationProviders()).isEqualTo(expected.locations());
+ assertThat(request.scanWholeClasspath()).as("scan whole classpath").isTrue();
+ assertThat(request.getImportOptions()).isEqualTo(expected.importOptions());
+ }
+ }
+
+ @Nested
+ class Rejects {
+ @Test
+ void rule_method_with_wrong_parameters() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(WrongRuleMethodWrongParameters.class);
+
+ assertThatThrownBy(() -> testEngine.discover(discoveryRequest, engineId))
+ .isInstanceOf(ArchTestInitializationException.class)
+ .hasMessageContaining(ArchTest.class.getSimpleName())
+ .hasMessageContaining(WrongRuleMethodWrongParameters.class.getSimpleName())
+ .hasMessageContaining(WrongRuleMethodWrongParameters.WRONG_PARAMETERS_METHOD_NAME)
+ .hasMessageContaining("must have exactly one parameter of type " + JavaClasses.class.getName());
+ }
+ }
+
+ @Nested
+ class Ignores {
+ @Test
+ void fields() {
+ simulateCachedClassesForTest(IgnoredField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredField.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredField.IGNORED_RULE_FIELD));
+
+ testListener.verifyViolation(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredField.UNIGNORED_RULE_FIELD));
+ }
+
+ @Test
+ void methods() {
+ simulateCachedClassesForTest(IgnoredMethod.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredMethod.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredMethod.class.getName())
+ .append(METHOD_SEGMENT_TYPE, IgnoredMethod.IGNORED_RULE_METHOD));
+
+ testListener.verifyViolation(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredMethod.class.getName())
+ .append(METHOD_SEGMENT_TYPE, IgnoredMethod.UNIGNORED_RULE_METHOD));
+ }
+
+ @Test
+ void classes() {
+ simulateCachedClassesForTest(IgnoredClass.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredClass.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredClass.class.getName()));
+
+ testListener.verifyNoOtherStartExceptHierarchyOf(engineId);
+ }
+
+ @Test
+ void libraries() {
+ simulateCachedClassesForTest(IgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredLibrary.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredLibrary.IGNORED_LIB_FIELD)
+ .append(CLASS_SEGMENT_TYPE, SimpleRules.class.getName()));
+ }
+
+ @Test
+ void library_referenced_classes() {
+ simulateCachedClassesForTest(IgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredLibrary.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredLibrary.UNIGNORED_LIB_ONE_FIELD)
+ .append(CLASS_SEGMENT_TYPE, IgnoredClass.class.getName()));
+ }
+
+ @Test
+ void library_sub_rules() {
+ simulateCachedClassesForTest(IgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredLibrary.class);
+
+ UniqueId classWithIgnoredMethod = engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredLibrary.UNIGNORED_LIB_TWO_FIELD)
+ .append(CLASS_SEGMENT_TYPE, IgnoredMethod.class.getName());
+
+ testListener.verifySkipped(classWithIgnoredMethod
+ .append(METHOD_SEGMENT_TYPE, IgnoredMethod.IGNORED_RULE_METHOD));
+
+ testListener.verifyViolation(classWithIgnoredMethod
+ .append(METHOD_SEGMENT_TYPE, IgnoredMethod.UNIGNORED_RULE_METHOD));
+ }
+
+ @Test
+ void with_reason() {
+ simulateCachedClassesForTest(IgnoredField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, IgnoredField.class);
+
+ UniqueId ignoredId = engineId
+ .append(CLASS_SEGMENT_TYPE, IgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, IgnoredField.IGNORED_RULE_FIELD);
+ testListener.verifySkipped(ignoredId, "some example description");
+ }
+ }
+
+ @Nested
+ class MetaIgnores {
+ @Test
+ void fields() {
+ simulateCachedClassesForTest(MetaIgnoredField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredField.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredField.IGNORED_RULE_FIELD));
+
+ testListener.verifyViolation(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredField.UNIGNORED_RULE_FIELD));
+ }
+
+ @Test
+ void methods() {
+ simulateCachedClassesForTest(MetaIgnoredMethod.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredMethod.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredMethod.class.getName())
+ .append(METHOD_SEGMENT_TYPE, MetaIgnoredMethod.IGNORED_RULE_METHOD));
+
+ testListener.verifyViolation(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredMethod.class.getName())
+ .append(METHOD_SEGMENT_TYPE, MetaIgnoredMethod.UNIGNORED_RULE_METHOD));
+ }
+
+ @Test
+ void classes() {
+ simulateCachedClassesForTest(MetaIgnoredClass.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredClass.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredClass.class.getName()));
+
+ testListener.verifyNoOtherStartExceptHierarchyOf(engineId);
+ }
+
+ @Test
+ void libraries() {
+ simulateCachedClassesForTest(MetaIgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredLibrary.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredLibrary.IGNORED_LIB_FIELD)
+ .append(CLASS_SEGMENT_TYPE, SimpleRules.class.getName()));
+ }
+
+ @Test
+ void library_referenced_classes() {
+ simulateCachedClassesForTest(MetaIgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredLibrary.class);
+
+ testListener.verifySkipped(engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredLibrary.UNIGNORED_LIB_ONE_FIELD)
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredClass.class.getName()));
+ }
+
+ @Test
+ void library_sub_rules() {
+ simulateCachedClassesForTest(MetaIgnoredLibrary.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredLibrary.class);
+
+ UniqueId classWithIgnoredMethod = engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredLibrary.UNIGNORED_LIB_TWO_FIELD)
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredMethod.class.getName());
+
+ testListener.verifySkipped(classWithIgnoredMethod
+ .append(METHOD_SEGMENT_TYPE, MetaIgnoredMethod.IGNORED_RULE_METHOD));
+
+ testListener.verifyViolation(classWithIgnoredMethod
+ .append(METHOD_SEGMENT_TYPE, MetaIgnoredMethod.UNIGNORED_RULE_METHOD));
+ }
+
+ @Test
+ void with_reason() {
+ simulateCachedClassesForTest(MetaIgnoredField.class, UnwantedClass.CLASS_VIOLATING_RULES);
+
+ EngineExecutionTestListener testListener = execute(engineId, MetaIgnoredField.class);
+
+ UniqueId ignoredId = engineId
+ .append(CLASS_SEGMENT_TYPE, MetaIgnoredField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, MetaIgnoredField.IGNORED_RULE_FIELD);
+ testListener.verifySkipped(ignoredId, "some example description");
+ }
+ }
+
+ @Nested
+ class GeneratesDisplayName {
+ @Test
+ void for_field_by_replacing_underscores_with_blanks_if_property_is_set_to_true() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleField.class);
+
+ ArchConfiguration.get().setProperty(DisplayNameResolver.JUNIT_DISPLAYNAME_REPLACE_UNDERSCORES_BY_SPACES_PROPERTY_NAME, "true");
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor ruleDescriptor = getOnlyTest(descriptor);
+
+ assertThat(ruleDescriptor.getDisplayName()).isEqualTo("simple rule");
+ }
+
+ @Test
+ void for_method_by_replacing_underscores_with_blanks_if_property_is_set_to_true() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleMethod.class);
+
+ ArchConfiguration.get().setProperty(DisplayNameResolver.JUNIT_DISPLAYNAME_REPLACE_UNDERSCORES_BY_SPACES_PROPERTY_NAME, "true");
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor ruleDescriptor = getOnlyTest(descriptor);
+
+ assertThat(ruleDescriptor.getDisplayName()).isEqualTo("simple rule");
+ }
+
+ @Test
+ void by_returning_original_name_if_property_is_set_to_false() {
+ EngineDiscoveryTestRequest discoveryRequest = new EngineDiscoveryTestRequest().withClass(SimpleRuleField.class);
+
+ ArchConfiguration.get().setProperty(DisplayNameResolver.JUNIT_DISPLAYNAME_REPLACE_UNDERSCORES_BY_SPACES_PROPERTY_NAME, "false");
+
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, engineId);
+
+ TestDescriptor ruleDescriptor = getOnlyTest(descriptor);
+
+ assertThat(ruleDescriptor.getDisplayName()).isEqualTo("simple_rule");
+ }
+
+ @AfterEach
+ void resetConfiguration() {
+ ArchConfiguration.get().reset();
+ }
+ }
+
+ private UniqueId createEngineId() {
+ return UniqueId.forEngine(ArchUnitTestEngine.UNIQUE_ID);
+ }
+
+ private UniqueId simpleRuleFieldTestId(UniqueId uniqueId) {
+ return uniqueId
+ .append(CLASS_SEGMENT_TYPE, SimpleRuleField.class.getName())
+ .append(FIELD_SEGMENT_TYPE, SIMPLE_RULE_FIELD_NAME);
+ }
+
+ private UniqueId simpleRuleMethodTestId(UniqueId uniqueId) {
+ return uniqueId
+ .append(CLASS_SEGMENT_TYPE, SimpleRuleMethod.class.getName())
+ .append(METHOD_SEGMENT_TYPE, SIMPLE_RULE_METHOD_NAME);
+ }
+
+ private UniqueId simpleRulesId(UniqueId rootId) {
+ return rootId.append(CLASS_SEGMENT_TYPE, SimpleRules.class.getName());
+ }
+
+ private UniqueId simpleRulesInLibraryId(UniqueId uniqueId) {
+ return simpleRulesId(uniqueId
+ .append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName())
+ .append(FIELD_SEGMENT_TYPE, SimpleRuleLibrary.RULES_ONE_FIELD));
+ }
+
+ private Set getExpectedIdsForSimpleRuleLibrary(UniqueId uniqueId) {
+ UniqueId simpleRuleLibrary = uniqueId.append(CLASS_SEGMENT_TYPE, SimpleRuleLibrary.class.getName());
+ Set simpleRulesIds = getExpectedIdsForSimpleRules(
+ simpleRuleLibrary.append(FIELD_SEGMENT_TYPE, SimpleRuleLibrary.RULES_ONE_FIELD));
+ Set simpleRuleFieldIds = singleton(simpleRuleFieldTestId(
+ simpleRuleLibrary.append(FIELD_SEGMENT_TYPE, SimpleRuleLibrary.RULES_TWO_FIELD)));
+
+ return Stream.of(simpleRulesIds, simpleRuleFieldIds)
+ .flatMap(Set::stream).collect(toSet());
+ }
+
+ private Set getExpectedIdsForSimpleRules(UniqueId append) {
+ UniqueId simpleRules = simpleRulesId(append);
+ Set simpleRulesFields = SimpleRules.RULE_FIELD_NAMES.stream().map(fieldName -> simpleRules
+ .append(FIELD_SEGMENT_TYPE, fieldName)).collect(toSet());
+ Set simpleRulesMethods = SimpleRules.RULE_METHOD_NAMES.stream().map(methodName -> simpleRules
+ .append(METHOD_SEGMENT_TYPE, methodName)).collect(toSet());
+ return Stream.of(simpleRulesFields, simpleRulesMethods).flatMap(Set::stream).collect(toSet());
+ }
+
+ private Set getExpectedIdsForComplexRuleLibrary(UniqueId uniqueId) {
+ UniqueId complexRuleLibrary = uniqueId.append(CLASS_SEGMENT_TYPE, ComplexRuleLibrary.class.getName());
+ Set simpleRuleLibraryIds = getExpectedIdsForSimpleRuleLibrary(complexRuleLibrary
+ .append(FIELD_SEGMENT_TYPE, ComplexRuleLibrary.RULES_ONE_FIELD));
+ Set simpleRulesIds = getExpectedIdsForSimpleRules(complexRuleLibrary
+ .append(FIELD_SEGMENT_TYPE, ComplexRuleLibrary.RULES_TWO_FIELD));
+
+ return Stream.of(simpleRuleLibraryIds, simpleRulesIds).flatMap(Set::stream).collect(toSet());
+ }
+
+ private Set privateRuleLibraryIds(UniqueId uniqueId) {
+ UniqueId libraryId = uniqueId
+ .append(CLASS_SEGMENT_TYPE, LibraryWithPrivateTests.class.getName())
+ .append(FIELD_SEGMENT_TYPE, LibraryWithPrivateTests.PRIVATE_RULES_FIELD_NAME)
+ .append(CLASS_SEGMENT_TYPE, LibraryWithPrivateTests.SubRules.class.getName())
+ .append(FIELD_SEGMENT_TYPE, LibraryWithPrivateTests.SubRules.PRIVATE_RULES_FIELD_NAME);
+ return ImmutableSet.of(privateRuleFieldId(libraryId), privateRuleMethodId(libraryId));
+ }
+
+ private UniqueId privateRuleFieldId(UniqueId uniqueId) {
+ return uniqueId
+ .append(CLASS_SEGMENT_TYPE, ClassWithPrivateTests.class.getName())
+ .append(FIELD_SEGMENT_TYPE, ClassWithPrivateTests.PRIVATE_RULE_FIELD_NAME);
+ }
+
+ private UniqueId privateRuleMethodId(UniqueId uniqueId) {
+ return uniqueId
+ .append(CLASS_SEGMENT_TYPE, ClassWithPrivateTests.class.getName())
+ .append(METHOD_SEGMENT_TYPE, ClassWithPrivateTests.PRIVATE_RULE_METHOD_NAME);
+ }
+
+ private Set getAllLeafUniqueIds(TestDescriptor rootDescriptor) {
+ return getAllLeafs(rootDescriptor).stream().map(TestDescriptor::getUniqueId).collect(toSet());
+ }
+
+ private TestDescriptor getOnlyTest(TestDescriptor descriptor) {
+ TestDescriptor testClass = getOnlyElement(descriptor.getChildren());
+ TestDescriptor ruleDescriptor = getOnlyElement(testClass.getChildren());
+ assertThat(ruleDescriptor.getType()).isEqualTo(TEST);
+ return ruleDescriptor;
+ }
+
+ private Set extends TestDescriptor> getAllLeafs(TestDescriptor descriptor) {
+ Set result = new HashSet<>();
+ descriptor.accept(possibleLeaf -> {
+ if (possibleLeaf.getChildren().isEmpty()) {
+ result.add(possibleLeaf);
+ }
+ });
+ return result;
+ }
+
+ private Set toUniqueIds(TestDescriptor rootDescriptor) {
+ return rootDescriptor.getChildren().stream().map(TestDescriptor::getUniqueId).collect(toSet());
+ }
+
+ private void simulateCachedClassesForTest(Class> testClass, Class> classToReturn) {
+ when(classCache.getClassesToAnalyzeFor(eq(testClass), classAnalysisRequestOf(testClass)))
+ .thenReturn(importClasses(classToReturn));
+ }
+
+ private ClassAnalysisRequest classAnalysisRequestOf(Class> testClass) {
+ return argThat(r -> Arrays.equals(r.getPackageNames(), testClass.getAnnotation(AnalyzeClasses.class).packages()));
+ }
+
+ private EngineExecutionTestListener execute(UniqueId uniqueId, Class> testClass) {
+ return execute(uniqueId, new EngineDiscoveryTestRequest().withClass(testClass));
+ }
+
+ private EngineExecutionTestListener execute(UniqueId uniqueId, EngineDiscoveryTestRequest discoveryRequest) {
+ TestDescriptor descriptor = testEngine.discover(discoveryRequest, uniqueId);
+
+ EngineExecutionTestListener listener = new EngineExecutionTestListener();
+ testEngine.execute(new ExecutionRequest(descriptor, listener, discoveryRequest.getConfigurationParameters()));
+ return listener;
+ }
+
+ private static URI rootOfClass(Class> clazz) {
+ String resourceName = classFileResource(clazz);
+ URL classResource = clazz.getResource(resourceName);
+ String rootPath = classResource.toExternalForm()
+ .replace(resourceName, "")
+ .replaceAll("^jar:", "")
+ .replaceAll("!$", "");
+ return URI.create(rootPath);
+ }
+
+ private static URI uriOfClass(Class> clazz) {
+ String resourceName = classFileResource(clazz);
+ return URI.create(clazz.getResource(resourceName).toExternalForm());
+ }
+
+ private static String classFileResource(Class> clazz) {
+ return String.format("/%s.class", clazz.getName().replace('.', '/'));
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java
new file mode 100644
index 0000000000..f2a4d49cd4
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineDiscoveryTestRequest.java
@@ -0,0 +1,175 @@
+package com.tngtech.archunit.junit.internal;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.engine_api.FieldSelector;
+import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.DiscoveryFilter;
+import org.junit.platform.engine.DiscoverySelector;
+import org.junit.platform.engine.EngineDiscoveryRequest;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.ClassNameFilter;
+import org.junit.platform.engine.discovery.ClassSelector;
+import org.junit.platform.engine.discovery.ClasspathRootSelector;
+import org.junit.platform.engine.discovery.DiscoverySelectors;
+import org.junit.platform.engine.discovery.MethodSelector;
+import org.junit.platform.engine.discovery.PackageNameFilter;
+import org.junit.platform.engine.discovery.PackageSelector;
+import org.junit.platform.engine.discovery.UniqueIdSelector;
+
+import static com.tngtech.archunit.junit.engine_api.FieldSelector.selectField;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
+
+class EngineDiscoveryTestRequest implements EngineDiscoveryRequest {
+ private final List classpathRootsToDiscover = new ArrayList<>();
+ private final List packagesToDiscover = new ArrayList<>();
+ private final List> classesToDiscover = new ArrayList<>();
+ private final List methodsToDiscover = new ArrayList<>();
+ private final List fieldsToDiscover = new ArrayList<>();
+ private final List idsToDiscover = new ArrayList<>();
+
+ private final List classNameFilters = new ArrayList<>();
+ private final List packageNameFilters = new ArrayList<>();
+
+ @Override
+ @SuppressWarnings("unchecked") // compatibility is explicitly checked
+ public List getSelectorsByType(Class selectorType) {
+ if (ClasspathRootSelector.class.equals(selectorType)) {
+ return (List) createClasspathRootSelectors(classpathRootsToDiscover);
+ }
+ if (PackageSelector.class.equals(selectorType)) {
+ return (List) createPackageSelectors(packagesToDiscover);
+ }
+ if (ClassSelector.class.equals(selectorType)) {
+ return (List) createClassSelectors(classesToDiscover);
+ }
+ if (MethodSelector.class.equals(selectorType)) {
+ return (List) createMethodSelectors(methodsToDiscover);
+ }
+ if (FieldSelector.class.equals(selectorType)) {
+ return (List) createFieldSelectors(fieldsToDiscover);
+ }
+ if (UniqueIdSelector.class.equals(selectorType)) {
+ return (List) createUniqueIdSelectors(idsToDiscover);
+ }
+ return emptyList();
+ }
+
+ private List createClasspathRootSelectors(List classpathRootsToDiscover) {
+ return DiscoverySelectors.selectClasspathRoots(classpathRootsToDiscover.stream().map(Paths::get).collect(toSet()));
+ }
+
+ private List createPackageSelectors(List packagesToDiscover) {
+ return packagesToDiscover.stream().map(DiscoverySelectors::selectPackage).collect(toList());
+ }
+
+ private List createClassSelectors(List> classesToDiscover) {
+ return classesToDiscover.stream().map(DiscoverySelectors::selectClass).collect(toList());
+ }
+
+ private List createMethodSelectors(List methodsToDiscover) {
+ return methodsToDiscover.stream().map(m -> selectMethod(m.getDeclaringClass(), m)).collect(toList());
+ }
+
+ private List createFieldSelectors(List fieldsToDiscover) {
+ return fieldsToDiscover.stream().map(f -> selectField(f.getDeclaringClass(), f)).collect(toList());
+ }
+
+ private List createUniqueIdSelectors(List idsToDiscover) {
+ return idsToDiscover.stream().map(DiscoverySelectors::selectUniqueId).collect(toList());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // compatibility is explicitly checked
+ public > List getFiltersByType(Class filterType) {
+ if (ClassNameFilter.class.equals(filterType)) {
+ return (List) classNameFilters;
+ }
+ if (PackageNameFilter.class.equals(filterType)) {
+ return (List) packageNameFilters;
+ }
+ return emptyList();
+ }
+
+ @Override
+ public ConfigurationParameters getConfigurationParameters() {
+ return new EmptyConfigurationParameters();
+ }
+
+ EngineDiscoveryTestRequest withClasspathRoot(URI uri) {
+ classpathRootsToDiscover.add(uri);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withPackage(String pkg) {
+ packagesToDiscover.add(pkg);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withClass(Class> clazz) {
+ classesToDiscover.add(clazz);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withUniqueId(UniqueId id) {
+ idsToDiscover.add(id);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withClassNameFilter(ClassNameFilter filter) {
+ classNameFilters.add(filter);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withPackageNameFilter(PackageNameFilter packageNameFilter) {
+ packageNameFilters.add(packageNameFilter);
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withMethod(Class> clazz, String methodName) {
+ try {
+ methodsToDiscover.add(clazz.getDeclaredMethod(methodName, JavaClasses.class));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ return this;
+ }
+
+ EngineDiscoveryTestRequest withField(Class> clazz, String fieldName) {
+ try {
+ fieldsToDiscover.add(clazz.getDeclaredField(fieldName));
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ return this;
+ }
+
+ private static class EmptyConfigurationParameters implements ConfigurationParameters {
+ @Override
+ public Optional get(String key) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional getBoolean(String key) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Set keySet() {
+ return emptySet();
+ }
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineExecutionTestListener.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineExecutionTestListener.java
new file mode 100644
index 0000000000..19f59b34f9
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/EngineExecutionTestListener.java
@@ -0,0 +1,160 @@
+package com.tngtech.archunit.junit.internal;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+import org.junit.platform.engine.EngineExecutionListener;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.reporting.ReportEntry;
+
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.platform.engine.TestExecutionResult.Status.FAILED;
+import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL;
+
+class EngineExecutionTestListener implements EngineExecutionListener {
+ private final List startedTests = new ArrayList<>();
+ private final List finishedTests = new ArrayList<>();
+ private final List skippedTests = new ArrayList<>();
+
+ @Override
+ public void dynamicTestRegistered(TestDescriptor testDescriptor) {
+ }
+
+ @Override
+ public void executionSkipped(TestDescriptor testDescriptor, String reason) {
+ skippedTests.add(new SkippedTest(testDescriptor, reason));
+ }
+
+ @Override
+ public void executionStarted(TestDescriptor testDescriptor) {
+ startedTests.add(testDescriptor);
+ }
+
+ @Override
+ public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult testExecutionResult) {
+ finishedTests.add(new FinishedTest(testDescriptor, testExecutionResult));
+ }
+
+ @Override
+ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) {
+ }
+
+ void verifySuccessful(UniqueId testId) {
+ verifyStarted(testId);
+ FinishedTest test = finishedTests.stream().filter(result -> result.hasId(testId)).collect(onlyElement());
+ assertThat(test.result.getStatus()).as("Test status of " + test).isEqualTo(SUCCESSFUL);
+ }
+
+ private void verifyStarted(UniqueId testId) {
+ boolean testStarted = startedTests.stream().anyMatch(descriptor -> descriptor.getUniqueId().equals(testId));
+ assertThat(testStarted).as("Test with id " + testId + " was started").isTrue();
+ }
+
+ void verifyViolation(UniqueId testId) {
+ verifyViolation(testId, "");
+ }
+
+ void verifyViolation(UniqueId testId, String messagePart) {
+ verifyStarted(testId);
+ FinishedTest test = finishedTests.stream().filter(result -> result.hasId(testId)).collect(onlyElement());
+ assertThat(test.result.getStatus())
+ .as("Test status of " + test)
+ .isEqualTo(FAILED);
+ assertThat(test.result.getThrowable().isPresent())
+ .as("Test has thrown Throwable: " + test)
+ .isTrue();
+ assertThat(test.result.getThrowable().get())
+ .as("Test Throwable of " + test)
+ .isInstanceOf(AssertionError.class);
+ assertThat(test.result.getThrowable().get().getMessage())
+ .as("AssertionError message of " + test)
+ .containsSequence(messagePart);
+ }
+
+ void verifySkipped(UniqueId testId) {
+ verifySkipped(testId, "");
+ }
+
+ void verifySkipped(UniqueId testId, String reasonPart) {
+ SkippedTest test = skippedTests.stream().filter(result -> result.hasId(testId)).collect(onlyElement());
+ assertThat(test.reason)
+ .as("reason of " + test)
+ .containsSequence(reasonPart);
+ }
+
+ void verifyNoOtherStartExceptHierarchyOf(UniqueId uniqueId) {
+ List unwanted = startedTests.stream()
+ .filter(descriptor -> !uniqueId.hasPrefix(descriptor.getUniqueId()))
+ .collect(toList());
+ assertThat(unwanted).as("Unwanted started descriptors").isEmpty();
+ }
+
+ private static class FinishedTest {
+ final TestDescriptor testDescriptor;
+ final TestExecutionResult result;
+
+ FinishedTest(TestDescriptor testDescriptor, TestExecutionResult result) {
+ this.testDescriptor = testDescriptor;
+ this.result = result;
+ }
+
+ boolean hasId(UniqueId testId) {
+ return testDescriptor.getUniqueId().equals(testId);
+ }
+
+ @Override
+ public String toString() {
+ return "FinishedTest{" +
+ "testDescriptor=" + testDescriptor +
+ ", result=" + result +
+ '}';
+ }
+ }
+
+ private static class SkippedTest {
+ final TestDescriptor testDescriptor;
+ final String reason;
+
+ SkippedTest(TestDescriptor testDescriptor, String reason) {
+ this.testDescriptor = testDescriptor;
+ this.reason = reason;
+ }
+
+ boolean hasId(UniqueId testId) {
+ return testDescriptor.getUniqueId().equals(testId);
+ }
+
+ @Override
+ public String toString() {
+ return "SkippedTest{" +
+ "testDescriptor=" + testDescriptor +
+ ", reason='" + reason + '\'' +
+ '}';
+ }
+ }
+
+ static Collector, T> onlyElement() {
+ Supplier> supplier = ArrayList::new;
+ BiConsumer, T> accumulator = Collection::add;
+ BinaryOperator> combiner = (left, right) -> {
+ left.addAll(right);
+ return left;
+ };
+ Function, T> finisher = collection -> {
+ if (collection.size() != 1) {
+ throw new IllegalStateException("Expected collection to have exactly one element, but was " + collection);
+ }
+ return collection.iterator().next();
+ };
+ return Collector.of(supplier, accumulator, combiner, finisher);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ClassWithPrivateTests.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ClassWithPrivateTests.java
new file mode 100644
index 0000000000..b81e676ac0
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ClassWithPrivateTests.java
@@ -0,0 +1,20 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class ClassWithPrivateTests {
+ @ArchTest
+ private final ArchRule privateRuleField = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ private void privateRuleMethod(JavaClasses classes) {
+ RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ public static final String PRIVATE_RULE_FIELD_NAME = "privateRuleField";
+ public static final String PRIVATE_RULE_METHOD_NAME = "privateRuleMethod";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexMetaTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexMetaTags.java
new file mode 100644
index 0000000000..040f5e5028
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexMetaTags.java
@@ -0,0 +1,69 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@ComplexMetaTags.LibraryTag
+@AnalyzeClasses
+public class ComplexMetaTags {
+ public static final String FIELD_RULE_NAME = "field_rule";
+ public static final String METHOD_RULE_NAME = "method_rule";
+
+ @RulesTag
+ @ArchTest
+ static final ArchTests classWithMetaTag = ArchTests.in(TestClassWithMetaTag.class);
+
+ @RulesTag
+ @ArchTest
+ static final ArchTests classWithMetaTags = ArchTests.in(TestClassWithMetaTags.class);
+
+ @FieldTag
+ @ArchTest
+ static final ArchRule field_rule = RuleThatFails.on(UnwantedClass.class);
+
+ @MethodTag
+ @ArchTest
+ static void method_rule(JavaClasses classes) {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("library-meta-tag")
+ @interface LibraryTag {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("rules-meta-tag")
+ @interface RulesTag {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("field-meta-tag")
+ @interface FieldTag {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("method-meta-tag")
+ @interface MethodTag {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexRuleLibrary.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexRuleLibrary.java
new file mode 100644
index 0000000000..0173f27326
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexRuleLibrary.java
@@ -0,0 +1,18 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class ComplexRuleLibrary {
+ @ArchTest
+ public static final ArchTests rules_one = ArchTests.in(SimpleRuleLibrary.class);
+
+ @ArchTest
+ public static final ArchTests rules_two = ArchTests.in(SimpleRules.class);
+
+ public static final String RULES_ONE_FIELD = "rules_one";
+ public static final String RULES_TWO_FIELD = "rules_two";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexTags.java
new file mode 100644
index 0000000000..ee058661bd
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ComplexTags.java
@@ -0,0 +1,28 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.lang.ArchRule;
+
+@ArchTag("library-tag")
+@AnalyzeClasses
+public class ComplexTags {
+ public static final String FIELD_RULE_NAME = "field_rule";
+ public static final String METHOD_RULE_NAME = "method_rule";
+
+ @ArchTag("rules-tag")
+ @ArchTest
+ static final ArchTests classWithTags = ArchTests.in(TestClassWithTags.class);
+
+ @ArchTag("field-tag")
+ @ArchTest
+ static final ArchRule field_rule = RuleThatFails.on(UnwantedClass.class);
+
+ @ArchTag("method-tag")
+ @ArchTest
+ static void method_rule(JavaClasses classes) {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/FullAnalyzeClassesSpec.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/FullAnalyzeClassesSpec.java
new file mode 100644
index 0000000000..dbaa3cec83
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/FullAnalyzeClassesSpec.java
@@ -0,0 +1,49 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeJars;
+import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests;
+import com.tngtech.archunit.core.importer.Location;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.LocationProvider;
+import com.tngtech.archunit.junit.internal.testexamples.FullAnalyzeClassesSpec.FirstLocationProvider;
+import com.tngtech.archunit.junit.internal.testexamples.FullAnalyzeClassesSpec.SecondLocationProvider;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+
+@AnalyzeClasses(
+ packages = {"first.pkg", "second.pkg"},
+ packagesOf = {Object.class, File.class},
+ locations = {FirstLocationProvider.class, SecondLocationProvider.class},
+ wholeClasspath = true,
+ importOptions = {DoNotIncludeTests.class, DoNotIncludeJars.class})
+public class FullAnalyzeClassesSpec {
+ @ArchTest
+ public static final ArchRule irrelevant = classes().should(new ArchCondition("exist") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ }
+ });
+
+ public static class FirstLocationProvider implements LocationProvider {
+ @Override
+ public Set get(Class> testClass) {
+ return Collections.emptySet();
+ }
+ }
+
+ public static class SecondLocationProvider implements LocationProvider {
+ @Override
+ public Set get(Class> testClass) {
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/LibraryWithPrivateTests.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/LibraryWithPrivateTests.java
new file mode 100644
index 0000000000..8f20282c54
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/LibraryWithPrivateTests.java
@@ -0,0 +1,20 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class LibraryWithPrivateTests {
+ @ArchTest
+ private final ArchTests privateRulesField = ArchTests.in(SubRules.class);
+
+ public static final String PRIVATE_RULES_FIELD_NAME = "privateRulesField";
+
+ public static class SubRules {
+ @ArchTest
+ private final ArchTests privateRulesField = ArchTests.in(ClassWithPrivateTests.class);
+
+ public static final String PRIVATE_RULES_FIELD_NAME = "privateRulesField";
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/RuleThatFails.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/RuleThatFails.java
new file mode 100644
index 0000000000..6a9cb62133
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/RuleThatFails.java
@@ -0,0 +1,22 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+
+public class RuleThatFails {
+ public static ArchRule on(Class> input) {
+ return classes().should(new ArchCondition("not be " + input.getName()) {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ if (item.isEquivalentTo(input)) {
+ events.add(SimpleConditionEvent.violated(item, "Got class " + item.getName()));
+ }
+ }
+ });
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/SimpleRuleLibrary.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/SimpleRuleLibrary.java
new file mode 100644
index 0000000000..e8217f9c90
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/SimpleRuleLibrary.java
@@ -0,0 +1,19 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField;
+import com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class SimpleRuleLibrary {
+ @ArchTest
+ public static final ArchTests rules_one = ArchTests.in(SimpleRules.class);
+
+ @ArchTest
+ public static final ArchTests rules_two = ArchTests.in(SimpleRuleField.class);
+
+ public static final String RULES_ONE_FIELD = "rules_one";
+ public static final String RULES_TWO_FIELD = "rules_two";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaAnnotationForAnalyzeClasses.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaAnnotationForAnalyzeClasses.java
new file mode 100644
index 0000000000..3141f35de0
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaAnnotationForAnalyzeClasses.java
@@ -0,0 +1,21 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Retention;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@TestClassWithMetaAnnotationForAnalyzeClasses.MetaAnalyzeClasses
+public class TestClassWithMetaAnnotationForAnalyzeClasses {
+
+ @ArchTest
+ public static final ArchRule rule_in_class_with_meta_analyze_class_annotation = RuleThatFails.on(UnwantedClass.class);
+
+ @Retention(RUNTIME)
+ @AnalyzeClasses(wholeClasspath = true)
+ public @interface MetaAnalyzeClasses {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTag.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTag.java
new file mode 100644
index 0000000000..56e5b37b65
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTag.java
@@ -0,0 +1,42 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@TestClassWithMetaTag.MetaTag
+@AnalyzeClasses
+public class TestClassWithMetaTag {
+ public static final String FIELD_RULE_NAME = "rule_in_class_with_meta_tag";
+
+ @ArchTest
+ public static final ArchRule rule_in_class_with_meta_tag = RuleThatFails.on(UnwantedClass.class);
+
+ @ArchTest
+ public static final ArchTests rules_in_class_with_meta_tag = ArchTests.in(SimpleRuleField.class);
+
+ @ArchTest
+ static void method_in_class_with_meta_tag(JavaClasses classes) {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("meta-tag-one")
+ @ArchTag("meta-tag-two")
+ @interface MetaTag {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTags.java
new file mode 100644
index 0000000000..346f2d81f8
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithMetaTags.java
@@ -0,0 +1,45 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTags;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@TestClassWithMetaTags.MetaTags
+@AnalyzeClasses
+public class TestClassWithMetaTags {
+ public static final String FIELD_RULE_NAME = "rule_in_class_with_meta_tags";
+
+ @ArchTest
+ public static final ArchRule rule_in_class_with_meta_tags = RuleThatFails.on(UnwantedClass.class);
+
+ @ArchTest
+ public static final ArchTests rules_in_class_with_meta_tags = ArchTests.in(SimpleRuleField.class);
+
+ @ArchTest
+ static void method_in_class_with_meta_tags(JavaClasses classes) {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTags({
+ @ArchTag("meta-tags-one"),
+ @ArchTag("meta-tags-two"),
+ })
+ @interface MetaTags {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithTags.java
new file mode 100644
index 0000000000..c58c4c0450
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestClassWithTags.java
@@ -0,0 +1,26 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subone.SimpleRuleField;
+import com.tngtech.archunit.lang.ArchRule;
+
+@ArchTag("tag-one")
+@ArchTag("tag-two")
+@AnalyzeClasses
+public class TestClassWithTags {
+ public static final String FIELD_RULE_NAME = "rule_in_class_with_tags";
+
+ @ArchTest
+ public static final ArchRule rule_in_class_with_tags = RuleThatFails.on(UnwantedClass.class);
+
+ @ArchTest
+ public static final ArchTests rules_in_class_with_tags = ArchTests.in(SimpleRuleField.class);
+
+ @ArchTest
+ static void method_in_class_with_tags(JavaClasses classes) {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTag.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTag.java
new file mode 100644
index 0000000000..9f1a4ad8c9
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTag.java
@@ -0,0 +1,32 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@AnalyzeClasses
+public class TestFieldWithMetaTag {
+ public static final String FIELD_WITH_META_TAG_NAME = "field_with_meta_tag";
+
+ @MetaTag
+ @ArchTest
+ static ArchRule field_with_meta_tag = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("field-meta-tag-one")
+ @ArchTag("field-meta-tag-two")
+ private @interface MetaTag {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTags.java
new file mode 100644
index 0000000000..a5dae5928c
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithMetaTags.java
@@ -0,0 +1,35 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTags;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@AnalyzeClasses
+public class TestFieldWithMetaTags {
+ public static final String FIELD_WITH_META_TAGS_NAME = "field_with_meta_tags";
+
+ @MetaTags
+ @ArchTest
+ static ArchRule field_with_meta_tags = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTags({
+ @ArchTag("field-meta-tags-one"),
+ @ArchTag("field-meta-tags-two"),
+ })
+ private @interface MetaTags {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithTags.java
new file mode 100644
index 0000000000..0ed7b4049b
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestFieldWithTags.java
@@ -0,0 +1,16 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+@AnalyzeClasses
+public class TestFieldWithTags {
+ public static final String FIELD_WITH_TAG_NAME = "field_with_tag";
+
+ @ArchTag("field-tag-one")
+ @ArchTag("field-tag-two")
+ @ArchTest
+ static ArchRule field_with_tag = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTag.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTag.java
new file mode 100644
index 0000000000..e59a626bac
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTag.java
@@ -0,0 +1,33 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@AnalyzeClasses
+public class TestMethodWithMetaTag {
+ public static final String METHOD_WITH_META_TAG_NAME = "method_with_meta_tag";
+
+ @MetaTag
+ @ArchTest
+ static void method_with_meta_tag(JavaClasses classes) {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTag("method-meta-tag-one")
+ @ArchTag("method-meta-tag-two")
+ private @interface MetaTag {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTags.java
new file mode 100644
index 0000000000..05900c0275
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithMetaTags.java
@@ -0,0 +1,36 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTags;
+import com.tngtech.archunit.junit.ArchTest;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@AnalyzeClasses
+public class TestMethodWithMetaTags {
+ public static final String METHOD_WITH_META_TAGS_NAME = "method_with_meta_tags";
+
+ @MetaTags
+ @ArchTest
+ static void method_with_meta_tags(JavaClasses classes) {
+ }
+
+ @Inherited
+ @Retention(RUNTIME)
+ @Target({TYPE, METHOD, FIELD})
+ @ArchTags({
+ @ArchTag("method-meta-tags-one"),
+ @ArchTag("method-meta-tags-two"),
+ })
+ private @interface MetaTags {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithTags.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithTags.java
new file mode 100644
index 0000000000..e0246a6094
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/TestMethodWithTags.java
@@ -0,0 +1,17 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTag;
+import com.tngtech.archunit.junit.ArchTest;
+
+@AnalyzeClasses
+public class TestMethodWithTags {
+ public static final String METHOD_WITH_TAG_NAME = "method_with_tag";
+
+ @ArchTag("method-tag-one")
+ @ArchTag("method-tag-two")
+ @ArchTest
+ static void method_with_tag(JavaClasses classes) {
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/UnwantedClass.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/UnwantedClass.java
new file mode 100644
index 0000000000..b952454ab1
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/UnwantedClass.java
@@ -0,0 +1,6 @@
+package com.tngtech.archunit.junit.internal.testexamples;
+
+public class UnwantedClass {
+ public static final Class> CLASS_SATISFYING_RULES = Object.class;
+ public static final Class> CLASS_VIOLATING_RULES = UnwantedClass.class;
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithFieldRule.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithFieldRule.java
new file mode 100644
index 0000000000..b9e4b0e24b
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithFieldRule.java
@@ -0,0 +1,13 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+import com.tngtech.archunit.lang.ArchRule;
+
+public abstract class AbstractBaseClassWithFieldRule {
+ public static final String INSTANCE_FIELD_NAME = "abstractBaseClassInstanceField";
+
+ @ArchTest
+ ArchRule abstractBaseClassInstanceField = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithLibraryWithAbstractBaseClass.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithLibraryWithAbstractBaseClass.java
new file mode 100644
index 0000000000..4fb08ceeb9
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithLibraryWithAbstractBaseClass.java
@@ -0,0 +1,13 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+
+public abstract class AbstractBaseClassWithLibraryWithAbstractBaseClass {
+ public static final String FIELD_RULE_LIBRARY_NAME = "fieldRules";
+ public static final String METHOD_RULE_LIBRARY_NAME = "methodRules";
+ @ArchTest
+ ArchTests fieldRules = ArchTests.in(ArchTestWithAbstractBaseClassWithFieldRule.class);
+ @ArchTest
+ ArchTests methodRules = ArchTests.in(ArchTestWithAbstractBaseClassWithMethodRule.class);
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithMethodRule.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithMethodRule.java
new file mode 100644
index 0000000000..b0d03366b4
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/AbstractBaseClassWithMethodRule.java
@@ -0,0 +1,15 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+
+public abstract class AbstractBaseClassWithMethodRule {
+ public static final String INSTANCE_METHOD_NAME = "abstractBaseClassInstanceMethod";
+
+ @ArchTest
+ void abstractBaseClassInstanceMethod(JavaClasses classes) {
+ RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES).check(classes);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithFieldRule.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithFieldRule.java
new file mode 100644
index 0000000000..d7f16af74b
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithFieldRule.java
@@ -0,0 +1,7 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class ArchTestWithAbstractBaseClassWithFieldRule extends AbstractBaseClassWithFieldRule {
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithMethodRule.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithMethodRule.java
new file mode 100644
index 0000000000..766b57288e
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithAbstractBaseClassWithMethodRule.java
@@ -0,0 +1,7 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class ArchTestWithAbstractBaseClassWithMethodRule extends AbstractBaseClassWithMethodRule {
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithLibraryWithAbstractBaseClass.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithLibraryWithAbstractBaseClass.java
new file mode 100644
index 0000000000..87beab24e1
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/abstractbase/ArchTestWithLibraryWithAbstractBaseClass.java
@@ -0,0 +1,7 @@
+package com.tngtech.archunit.junit.internal.testexamples.abstractbase;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class ArchTestWithLibraryWithAbstractBaseClass extends AbstractBaseClassWithLibraryWithAbstractBaseClass {
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/ArchIgnoreMetaAnnotation.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/ArchIgnoreMetaAnnotation.java
new file mode 100644
index 0000000000..ed066681dc
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/ArchIgnoreMetaAnnotation.java
@@ -0,0 +1,19 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.tngtech.archunit.junit.ArchIgnore;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Inherited
+@Retention(RUNTIME)
+@Target({TYPE, METHOD, FIELD})
+@ArchIgnore(reason = "some example description")
+public @interface ArchIgnoreMetaAnnotation {
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredClass.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredClass.java
new file mode 100644
index 0000000000..85e9785fee
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredClass.java
@@ -0,0 +1,23 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchIgnore;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@ArchIgnore
+@AnalyzeClasses(packages = "some.dummy.package")
+public class IgnoredClass {
+
+ @ArchTest
+ static final ArchRule rule_one = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ static void rule_two(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredField.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredField.java
new file mode 100644
index 0000000000..fe0dbf6689
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredField.java
@@ -0,0 +1,23 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchIgnore;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class IgnoredField {
+
+ @ArchTest
+ static final ArchRule unignored_rule = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ @ArchIgnore(reason = "some example description")
+ static final ArchRule ignored_rule = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ public static final String UNIGNORED_RULE_FIELD = "unignored_rule";
+ public static final String IGNORED_RULE_FIELD = "ignored_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredLibrary.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredLibrary.java
new file mode 100644
index 0000000000..fcfe2df6fe
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredLibrary.java
@@ -0,0 +1,25 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchIgnore;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class IgnoredLibrary {
+
+ @ArchTest
+ static final ArchTests unignored_lib_one = ArchTests.in(IgnoredClass.class);
+
+ @ArchTest
+ static final ArchTests unignored_lib_two = ArchTests.in(IgnoredMethod.class);
+
+ @ArchTest
+ @ArchIgnore
+ static final ArchTests ignored_lib = ArchTests.in(SimpleRules.class);
+
+ public static final String UNIGNORED_LIB_ONE_FIELD = "unignored_lib_one";
+ public static final String UNIGNORED_LIB_TWO_FIELD = "unignored_lib_two";
+ public static final String IGNORED_LIB_FIELD = "ignored_lib";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredMethod.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredMethod.java
new file mode 100644
index 0000000000..00ecf784cb
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/IgnoredMethod.java
@@ -0,0 +1,27 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchIgnore;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class IgnoredMethod {
+
+ @ArchTest
+ static void unignored_rule(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ @ArchTest
+ @ArchIgnore
+ static void ignored_rule(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ public static final String UNIGNORED_RULE_METHOD = "unignored_rule";
+ public static final String IGNORED_RULE_METHOD = "ignored_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredClass.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredClass.java
new file mode 100644
index 0000000000..29a66c5713
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredClass.java
@@ -0,0 +1,22 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@ArchIgnoreMetaAnnotation
+@AnalyzeClasses(packages = "some.dummy.package")
+public class MetaIgnoredClass {
+
+ @ArchTest
+ static final ArchRule rule_one = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ static void rule_two(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredField.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredField.java
new file mode 100644
index 0000000000..cbf8f67935
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredField.java
@@ -0,0 +1,22 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.lang.ArchRule;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class MetaIgnoredField {
+
+ @ArchTest
+ static final ArchRule unignored_rule = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ @ArchIgnoreMetaAnnotation
+ static final ArchRule ignored_rule = RuleThatFails.on(CLASS_VIOLATING_RULES);
+
+ public static final String UNIGNORED_RULE_FIELD = "unignored_rule";
+ public static final String IGNORED_RULE_FIELD = "ignored_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredLibrary.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredLibrary.java
new file mode 100644
index 0000000000..e02f7cb896
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredLibrary.java
@@ -0,0 +1,24 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchTests;
+import com.tngtech.archunit.junit.internal.testexamples.subtwo.SimpleRules;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class MetaIgnoredLibrary {
+
+ @ArchTest
+ static final ArchTests unignored_lib_one = ArchTests.in(MetaIgnoredClass.class);
+
+ @ArchTest
+ static final ArchTests unignored_lib_two = ArchTests.in(MetaIgnoredMethod.class);
+
+ @ArchTest
+ @ArchIgnoreMetaAnnotation
+ static final ArchTests ignored_lib = ArchTests.in(SimpleRules.class);
+
+ public static final String UNIGNORED_LIB_ONE_FIELD = "unignored_lib_one";
+ public static final String UNIGNORED_LIB_TWO_FIELD = "unignored_lib_two";
+ public static final String IGNORED_LIB_FIELD = "ignored_lib";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredMethod.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredMethod.java
new file mode 100644
index 0000000000..e22f433200
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/ignores/MetaIgnoredMethod.java
@@ -0,0 +1,26 @@
+package com.tngtech.archunit.junit.internal.testexamples.ignores;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+
+import static com.tngtech.archunit.junit.internal.testexamples.UnwantedClass.CLASS_VIOLATING_RULES;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class MetaIgnoredMethod {
+
+ @ArchTest
+ static void unignored_rule(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ @ArchTest
+ @ArchIgnoreMetaAnnotation
+ static void ignored_rule(JavaClasses classes) {
+ RuleThatFails.on(CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ public static final String UNIGNORED_RULE_METHOD = "unignored_rule";
+ public static final String IGNORED_RULE_METHOD = "ignored_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleField.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleField.java
new file mode 100644
index 0000000000..b5b13f7808
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleField.java
@@ -0,0 +1,15 @@
+package com.tngtech.archunit.junit.internal.testexamples.subone;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+import com.tngtech.archunit.lang.ArchRule;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class SimpleRuleField {
+ @ArchTest
+ public static final ArchRule simple_rule = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ public static final String SIMPLE_RULE_FIELD_NAME = "simple_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleMethod.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleMethod.java
new file mode 100644
index 0000000000..957b2d6e37
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subone/SimpleRuleMethod.java
@@ -0,0 +1,17 @@
+package com.tngtech.archunit.junit.internal.testexamples.subone;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class SimpleRuleMethod {
+ @ArchTest
+ static void simple_rule(JavaClasses classes) {
+ RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ public static final String SIMPLE_RULE_METHOD_NAME = "simple_rule";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java
new file mode 100644
index 0000000000..727f3d60a8
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/subtwo/SimpleRules.java
@@ -0,0 +1,37 @@
+package com.tngtech.archunit.junit.internal.testexamples.subtwo;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.internal.testexamples.RuleThatFails;
+import com.tngtech.archunit.junit.internal.testexamples.UnwantedClass;
+import com.tngtech.archunit.lang.ArchRule;
+
+@AnalyzeClasses(packages = "some.dummy.package")
+public class SimpleRules {
+ @ArchTest
+ public static final ArchRule simple_rule_field_one = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ public static final ArchRule simple_rule_field_two = RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES);
+
+ @ArchTest
+ public static void simple_rule_method_one(JavaClasses classes) {
+ RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ @ArchTest
+ public static void simple_rule_method_two(JavaClasses classes) {
+ RuleThatFails.on(UnwantedClass.CLASS_VIOLATING_RULES).check(classes);
+ }
+
+ public static final String SIMPLE_RULE_FIELD_ONE_NAME = "simple_rule_field_one";
+ public static final String SIMPLE_RULE_FIELD_TWO_NAME = "simple_rule_field_two";
+ 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 String SIMPLE_RULE_METHOD_TWO_NAME = "simple_rule_method_two";
+ public static final Set RULE_METHOD_NAMES = ImmutableSet.of(SIMPLE_RULE_METHOD_ONE_NAME, SIMPLE_RULE_METHOD_TWO_NAME);
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodNotStatic.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodNotStatic.java
new file mode 100644
index 0000000000..a4b448407c
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodNotStatic.java
@@ -0,0 +1,14 @@
+package com.tngtech.archunit.junit.internal.testexamples.wrong;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+
+@AnalyzeClasses
+public class WrongRuleMethodNotStatic {
+ @ArchTest
+ void notStatic(JavaClasses classes) {
+ }
+
+ public static final String NOT_STATIC_METHOD_NAME = "notStatic";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodWrongParameters.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodWrongParameters.java
new file mode 100644
index 0000000000..5d3950c553
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testexamples/wrong/WrongRuleMethodWrongParameters.java
@@ -0,0 +1,13 @@
+package com.tngtech.archunit.junit.internal.testexamples.wrong;
+
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+
+@AnalyzeClasses
+public class WrongRuleMethodWrongParameters {
+ @ArchTest
+ static void wrongParameters() {
+ }
+
+ public static final String WRONG_PARAMETERS_METHOD_NAME = "wrongParameters";
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/LogCaptor.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/LogCaptor.java
new file mode 100644
index 0000000000..397fb38aef
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/LogCaptor.java
@@ -0,0 +1,23 @@
+package com.tngtech.archunit.junit.internal.testutil;
+
+import java.util.List;
+
+import com.tngtech.archunit.testutil.TestLogRecorder;
+import com.tngtech.archunit.testutil.TestLogRecorder.RecordedLogEvent;
+import org.apache.logging.log4j.Level;
+
+public class LogCaptor {
+ private final TestLogRecorder logRecorder = new TestLogRecorder();
+
+ public void watch(Class> loggerClass, Level level) {
+ logRecorder.record(loggerClass, level);
+ }
+
+ public void cleanUp() {
+ logRecorder.reset();
+ }
+
+ public List getEvents(Level level) {
+ return logRecorder.getEvents(level);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/SystemPropertiesExtension.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/SystemPropertiesExtension.java
new file mode 100644
index 0000000000..5a865758a9
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/SystemPropertiesExtension.java
@@ -0,0 +1,21 @@
+package com.tngtech.archunit.junit.internal.testutil;
+
+import java.util.Properties;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class SystemPropertiesExtension implements BeforeEachCallback, AfterEachCallback {
+ private Properties properties;
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) {
+ properties = (Properties) System.getProperties().clone();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) {
+ System.setProperties(properties);
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/TestLogExtension.java b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/TestLogExtension.java
new file mode 100644
index 0000000000..a522668335
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/java/com/tngtech/archunit/junit/internal/testutil/TestLogExtension.java
@@ -0,0 +1,26 @@
+package com.tngtech.archunit.junit.internal.testutil;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+
+public class TestLogExtension implements ParameterResolver, AfterEachCallback {
+ private final LogCaptor logCaptor = new LogCaptor();
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return LogCaptor.class.equals(parameterContext.getParameter().getType());
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+ return logCaptor;
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) {
+ logCaptor.cleanUp();
+ }
+}
diff --git a/archunit-junit/junit6/engine/src/test/resources/archunit.properties b/archunit-junit/junit6/engine/src/test/resources/archunit.properties
new file mode 100644
index 0000000000..d24b5e9b45
--- /dev/null
+++ b/archunit-junit/junit6/engine/src/test/resources/archunit.properties
@@ -0,0 +1,2 @@
+import.dependencyResolutionProcess.maxIterationsForEnclosingTypes=0
+import.dependencyResolutionProcess.maxIterationsForGenericSignatureTypes=0
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e954162853..83fdfca93f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -65,7 +65,11 @@ ext {
project(':archunit-junit5-api'),
project(':archunit-junit5-engine-api'),
project(':archunit-junit5-engine'),
- project(':archunit-junit5')]
+ project(':archunit-junit5'),
+ project(':archunit-junit6-api'),
+ project(':archunit-junit6-engine-api'),
+ project(':archunit-junit6-engine'),
+ project(':archunit-junit6')]
currentScriptRootOf = { it.buildscript.sourceFile.parentFile }
scriptRelativePath = { context, fileName -> new File(currentScriptRootOf(context), fileName) }
diff --git a/buildSrc/src/main/groovy/archunit.java-release-check-conventions.gradle b/buildSrc/src/main/groovy/archunit.java-release-check-conventions.gradle
index ed8a2b516d..b85905bc22 100644
--- a/buildSrc/src/main/groovy/archunit.java-release-check-conventions.gradle
+++ b/buildSrc/src/main/groovy/archunit.java-release-check-conventions.gradle
@@ -19,9 +19,20 @@ task ensureReleaseCheckUpToDate {
releaseCheckDependencies.each { releaseCheckDependency ->
VersionCatalog versionCatalog = versionCatalogs.named("libs")
def dependencies = versionCatalog.libraryAliases.collect { versionCatalog.findLibrary(it).get().get() }
- def matchingProjectDependency = dependencies.find {
+ def matchingProjectDependencies = dependencies.findAll {
it.group == releaseCheckDependency.groupId && it.name == releaseCheckDependency.artifactId
}
+
+ assert matchingProjectDependencies.size() == 1 || releaseCheckDependency.groupId.contains("org.junit"):
+ "Multiple dependency ${matchingProjectDependencies.size()}: ${matchingProjectDependencies}"
+
+ // Filter out duplicate versions of JUnit5 or JUnit6.
+ def matchingProjectDependency = releaseCheckDependency.groupId.contains("org.junit")
+ ? releaseCheckDependency.version.contains("6.")
+ ? matchingProjectDependencies.find { ju -> ju.version.contains("6.") }
+ : matchingProjectDependencies.find { ju -> !ju.version.contains("6.") }
+ : matchingProjectDependencies.first()
+
assert matchingProjectDependency != null:
"No project dependency was found for expected release dependency ${releaseCheckDependency}"
assert matchingProjectDependency.version == releaseCheckDependency.version:
diff --git a/buildSrc/src/main/resources/release_check/archunit-junit6-api.pom b/buildSrc/src/main/resources/release_check/archunit-junit6-api.pom
new file mode 100644
index 0000000000..b5122a70c8
--- /dev/null
+++ b/buildSrc/src/main/resources/release_check/archunit-junit6-api.pom
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+ com.tngtech.archunit
+ archunit-junit6-api
+ ${archunit.version}
+ ArchUnit
+ A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit6-api'
+ https://github.com/TNG/ArchUnit
+
+ TNG Technology Consulting GmbH
+ https://www.tngtech.com/
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ codecholeric
+ Peter Gafert
+ peter.gafert@archunit.org
+
+
+ rweisleder
+ Roland Weisleder
+ roland.weisleder@googlemail.com
+
+
+ hankem
+ Manfred Hanke
+ manfred.hanke@tngtech.com
+
+
+
+ scm:git@github.com:TNG/ArchUnit.git
+ scm:git@github.com:TNG/ArchUnit.git
+ https://github.com/TNG/ArchUnit
+
+
+
+ com.tngtech.archunit
+ archunit
+ ${archunit.version}
+ compile
+
+
+
diff --git a/buildSrc/src/main/resources/release_check/archunit-junit6-engine-api.pom b/buildSrc/src/main/resources/release_check/archunit-junit6-engine-api.pom
new file mode 100644
index 0000000000..217f201811
--- /dev/null
+++ b/buildSrc/src/main/resources/release_check/archunit-junit6-engine-api.pom
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+ com.tngtech.archunit
+ archunit-junit6-engine-api
+ ${archunit.version}
+ ArchUnit
+ A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit6-engine-api'
+
+ https://github.com/TNG/ArchUnit
+
+ TNG Technology Consulting GmbH
+ https://www.tngtech.com/
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ codecholeric
+ Peter Gafert
+ peter.gafert@archunit.org
+
+
+ rweisleder
+ Roland Weisleder
+ roland.weisleder@googlemail.com
+
+
+ hankem
+ Manfred Hanke
+ manfred.hanke@tngtech.com
+
+
+
+ scm:git@github.com:TNG/ArchUnit.git
+ scm:git@github.com:TNG/ArchUnit.git
+ https://github.com/TNG/ArchUnit
+
+
+
+ org.junit.platform
+ junit-platform-engine
+ 1.14.2
+ compile
+
+
+
diff --git a/buildSrc/src/main/resources/release_check/archunit-junit6-engine.pom b/buildSrc/src/main/resources/release_check/archunit-junit6-engine.pom
new file mode 100644
index 0000000000..4e18fa3a8e
--- /dev/null
+++ b/buildSrc/src/main/resources/release_check/archunit-junit6-engine.pom
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+ com.tngtech.archunit
+ archunit-junit6-engine
+ ${archunit.version}
+ ArchUnit
+ A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit6-engine'
+
+ https://github.com/TNG/ArchUnit
+
+ TNG Technology Consulting GmbH
+ https://www.tngtech.com/
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ codecholeric
+ Peter Gafert
+ peter.gafert@archunit.org
+
+
+ rweisleder
+ Roland Weisleder
+ roland.weisleder@googlemail.com
+
+
+ hankem
+ Manfred Hanke
+ manfred.hanke@tngtech.com
+
+
+
+ scm:git@github.com:TNG/ArchUnit.git
+ scm:git@github.com:TNG/ArchUnit.git
+ https://github.com/TNG/ArchUnit
+
+
+
+ com.tngtech.archunit
+ archunit
+ ${archunit.version}
+ compile
+
+
+ com.tngtech.archunit
+ archunit-junit6-api
+ ${archunit.version}
+ compile
+
+
+ com.tngtech.archunit
+ archunit-junit6-engine-api
+ ${archunit.version}
+ compile
+
+
+
diff --git a/buildSrc/src/main/resources/release_check/archunit-junit6.pom b/buildSrc/src/main/resources/release_check/archunit-junit6.pom
new file mode 100644
index 0000000000..915db4b38d
--- /dev/null
+++ b/buildSrc/src/main/resources/release_check/archunit-junit6.pom
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+ com.tngtech.archunit
+ archunit-junit6
+ ${archunit.version}
+ ArchUnit
+ A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit6'
+ https://github.com/TNG/ArchUnit
+
+ TNG Technology Consulting GmbH
+ https://www.tngtech.com/
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ codecholeric
+ Peter Gafert
+ peter.gafert@archunit.org
+
+
+ rweisleder
+ Roland Weisleder
+ roland.weisleder@googlemail.com
+
+
+ hankem
+ Manfred Hanke
+ manfred.hanke@tngtech.com
+
+
+
+ scm:git@github.com:TNG/ArchUnit.git
+ scm:git@github.com:TNG/ArchUnit.git
+ https://github.com/TNG/ArchUnit
+
+
+
+ com.tngtech.archunit
+ archunit-junit6-api
+ ${archunit.version}
+ compile
+
+
+ com.tngtech.archunit
+ archunit-junit6-engine
+ ${archunit.version}
+ runtime
+
+
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a6460888a5..2c94d8b686 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,6 +2,7 @@
log4j = "2.25.3"
junit5 = "5.14.2"
junitPlatform = "1.14.2" # in sync with buildSrc/src/main/resources/release_check/archunit-junit5-engine-api.pom
+junit6 = "6.0.2"
assertj = "3.27.7"
[libraries]
@@ -18,6 +19,12 @@ junitPlatform = { group = "org.junit.platform", name = "junit-platform-runner",
junitPlatformCommons = { group = "org.junit.platform", name = "junit-platform-commons", version.ref = "junitPlatform" }
junitPlatformEngine = { group = "org.junit.platform", name = "junit-platform-engine", version.ref = "junitPlatform" }
junitPlatformLauncher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junitPlatform" }
+junit6Jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit6" }
+junit6VintageEngine = { group = "org.junit.vintage", name = "junit-vintage-engine", version.ref = "junit6" }
+junit6Platform = { group = "org.junit.platform", name = "junit-platform-runner", version.ref = "junit6" }
+junit6PlatformCommons = { group = "org.junit.platform", name = "junit-platform-commons", version.ref = "junit6" }
+junit6PlatformEngine = { group = "org.junit.platform", name = "junit-platform-engine", version.ref = "junit6" }
+junit6PlatformLauncher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junit6" }
hamcrest = { group = "org.hamcrest", name = "hamcrest-core", version = "1.3" }
junitDataprovider = { group = "com.tngtech.java", name = "junit-dataprovider", version = "1.11.0" }
mockito = { group = "org.mockito", name = "mockito-core", version = "4.11.0" } # mockito 5 requires Java 11
diff --git a/settings.gradle b/settings.gradle
index 1bb8ade8d8..c4be48665a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,6 +6,7 @@ rootProject.name = 'archunit-root'
include 'archunit', 'archunit-integration-test', 'archunit-java-modules-test', 'archunit-3rd-party-test',
'archunit-junit', 'archunit-junit4', 'archunit-junit5-api', 'archunit-junit5-engine-api', 'archunit-junit5-engine', 'archunit-junit5',
+ 'archunit-junit6-api', 'archunit-junit6-engine-api', 'archunit-junit6-engine', 'archunit-junit6',
'archunit-example:example-plain', 'archunit-example:example-junit4', 'archunit-example:example-junit5', 'archunit-maven-test', 'docs'
project(':archunit-junit4').projectDir = file('archunit-junit/junit4')
@@ -13,6 +14,10 @@ project(':archunit-junit5-api').projectDir = file('archunit-junit/junit5/api')
project(':archunit-junit5-engine-api').projectDir = file('archunit-junit/junit5/engine-api')
project(':archunit-junit5-engine').projectDir = file('archunit-junit/junit5/engine')
project(':archunit-junit5').projectDir = file('archunit-junit/junit5/aggregator')
+project(':archunit-junit6-api').projectDir = file('archunit-junit/junit6/api')
+project(':archunit-junit6-engine-api').projectDir = file('archunit-junit/junit6/engine-api')
+project(':archunit-junit6-engine').projectDir = file('archunit-junit/junit6/engine')
+project(':archunit-junit6').projectDir = file('archunit-junit/junit6/aggregator')
dependencyResolutionManagement {
versionCatalogs {