Skip to content

Commit 1f59d44

Browse files
committed
Add support for grouped rules
Invoke rules defined by fields of type ArchTests which include rules defined in other classes. See also https://www.archunit.org/userguide/html/000_Index.html#_grouping_rules Fixes #70
1 parent 5f175f8 commit 1f59d44

File tree

5 files changed

+139
-11
lines changed

5 files changed

+139
-11
lines changed

pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@
110110
<version>${tng.archunit.version}</version>
111111
</dependency>
112112

113+
<!-- To be able to work with com.tngtech.archunit.junit.ArchTests -->
114+
<dependency>
115+
<groupId>com.tngtech.archunit</groupId>
116+
<artifactId>archunit-junit5-api</artifactId>
117+
<version>${tng.archunit.version}</version>
118+
</dependency>
119+
113120
<dependency>
114121
<groupId>com.google.guava</groupId>
115122
<artifactId>guava</artifactId>

src/main/java/com/societegenerale/commons/plugin/service/InvokableRules.java

+37-11
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import java.lang.reflect.Field;
44
import java.lang.reflect.Member;
55
import java.lang.reflect.Method;
6+
import java.util.ArrayDeque;
67
import java.util.ArrayList;
78
import java.util.Collection;
9+
import java.util.Deque;
10+
import java.util.HashSet;
811
import java.util.List;
912
import java.util.Optional;
1013
import java.util.Set;
@@ -13,20 +16,20 @@
1316
import com.google.common.collect.ImmutableSet;
1417
import com.google.common.collect.Sets;
1518
import com.societegenerale.commons.plugin.Log;
19+
import com.societegenerale.commons.plugin.utils.ReflectionUtils;
1620
import com.tngtech.archunit.core.domain.JavaClasses;
21+
import com.tngtech.archunit.junit.ArchTests;
1722
import com.tngtech.archunit.lang.ArchRule;
1823

1924
import static com.societegenerale.commons.plugin.utils.ReflectionUtils.getValue;
2025
import static com.societegenerale.commons.plugin.utils.ReflectionUtils.invoke;
2126
import static com.societegenerale.commons.plugin.utils.ReflectionUtils.loadClassWithContextClassLoader;
22-
import static com.societegenerale.commons.plugin.utils.ReflectionUtils.newInstance;
2327
import static java.lang.System.lineSeparator;
2428
import static java.util.Arrays.stream;
2529
import static java.util.stream.Collectors.joining;
2630
import static java.util.stream.Collectors.toSet;
2731

2832
class InvokableRules {
29-
private final Class<?> rulesLocation;
3033
private final Set<Field> archRuleFields;
3134
private final Set<Method> archRuleMethods;
3235

@@ -36,11 +39,17 @@ private InvokableRules(String rulesClassName, List<String> ruleChecks, Log log)
3639

3740
this.log=log;
3841

39-
rulesLocation = loadClassWithContextClassLoader(rulesClassName);
42+
Class<?> definedRulesClass = loadClassWithContextClassLoader(rulesClassName);
4043

41-
Set<Field> allFieldsWhichAreArchRules = getAllFieldsWhichAreArchRules(rulesLocation.getDeclaredFields());
42-
Set<Method> allMethodsWhichAreArchRules = getAllMethodsWhichAreArchRules(rulesLocation.getDeclaredMethods());
43-
validateRuleChecks(Sets.union(allMethodsWhichAreArchRules, allFieldsWhichAreArchRules), ruleChecks);
44+
Set<Class<?>> rulesClasses = getAllClassesWhichAreArchTests(definedRulesClass);
45+
rulesClasses.add(definedRulesClass);
46+
Set<Field> allFieldsWhichAreArchRules = new HashSet<>();
47+
Set<Method> allMethodsWhichAreArchRules = new HashSet<>();
48+
for (Class<?> rulesClass : rulesClasses) {
49+
allFieldsWhichAreArchRules.addAll(getAllFieldsWhichAreArchRules(rulesClass.getDeclaredFields()));
50+
allMethodsWhichAreArchRules.addAll(getAllMethodsWhichAreArchRules(rulesClass.getDeclaredMethods()));
51+
}
52+
validateRuleChecks(definedRulesClass, Sets.union(allMethodsWhichAreArchRules, allFieldsWhichAreArchRules), ruleChecks);
4453

4554
Predicate<String> isChosenCheck = ruleChecks.isEmpty() ? check -> true : ruleChecks::contains;
4655

@@ -64,7 +73,7 @@ private void logBuiltInvokableRules(String rulesClassName) {
6473

6574
}
6675

67-
private void validateRuleChecks(Set<? extends Member> allFieldsAndMethods, Collection<String> ruleChecks) {
76+
private void validateRuleChecks(Class<?> rulesLocation, Set<? extends Member> allFieldsAndMethods, Collection<String> ruleChecks) {
6877
Set<String> allFieldAndMethodNames = allFieldsAndMethods.stream().map(Member::getName).collect(toSet());
6978
Set<String> illegalChecks = Sets.difference(ImmutableSet.copyOf(ruleChecks), allFieldAndMethodNames);
7079

@@ -91,9 +100,26 @@ private Set<Field> getAllFieldsWhichAreArchRules(Field[] fields) {
91100
.collect(toSet());
92101
}
93102

94-
InvocationResult invokeOn(JavaClasses importedClasses) {
103+
private Set<Class<?>> getAllClassesWhichAreArchTests(Class<?> startClass) {
104+
Set<Class<?>> allClassesWhichAreArchTests = new HashSet<>();
105+
Deque<Class<?>> stack = new ArrayDeque<>();
106+
stack.push(startClass);
107+
while (!stack.isEmpty()) {
108+
Class<?> currentClass = stack.pop();
109+
stream(currentClass.getDeclaredFields())
110+
.filter(f -> ArchTests.class.isAssignableFrom(f.getType()))
111+
.map(f -> getValue(f, null))
112+
.map(ArchTests.class::cast)
113+
.map(ArchTests::getDefinitionLocation)
114+
.forEach(childClass -> {
115+
allClassesWhichAreArchTests.add(childClass);
116+
stack.push(childClass);
117+
});
118+
}
119+
return allClassesWhichAreArchTests;
120+
}
95121

96-
Object instance = newInstance(rulesLocation);
122+
InvocationResult invokeOn(JavaClasses importedClasses) {
97123

98124
if(log.isInfoEnabled()) {
99125
log.info("applying rules on "+importedClasses.size()+" classe(s). To see the details, enable debug logs");
@@ -105,11 +131,11 @@ InvocationResult invokeOn(JavaClasses importedClasses) {
105131

106132
InvocationResult result = new InvocationResult();
107133
for (Method method : archRuleMethods) {
108-
checkForFailure(() -> invoke(method, instance, importedClasses))
134+
checkForFailure(() -> invoke(method, null, importedClasses))
109135
.ifPresent(result::add);
110136
}
111137
for (Field field : archRuleFields) {
112-
ArchRule rule = getValue(field, instance);
138+
ArchRule rule = getValue(field, null);
113139
checkForFailure(() -> rule.check(importedClasses))
114140
.ifPresent(result::add);
115141
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.societegenerale.commons.plugin.rules.classesForTests;
2+
3+
import com.tngtech.archunit.junit.ArchTest;
4+
import com.tngtech.archunit.junit.ArchTests;
5+
6+
public class DoubleIncludedCustomRule {
7+
8+
@ArchTest
9+
static final ArchTests DOUBLE_INCLUDED = ArchTests.in(IncludedCustomRule.class);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.societegenerale.commons.plugin.rules.classesForTests;
2+
3+
import com.tngtech.archunit.junit.ArchTest;
4+
import com.tngtech.archunit.junit.ArchTests;
5+
6+
public class IncludedCustomRule {
7+
8+
@ArchTest
9+
static final ArchTests INCLUDED = ArchTests.in(DummyCustomRule.class);
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.societegenerale.commons.plugin.service;
2+
3+
import com.societegenerale.commons.plugin.Log;
4+
import com.societegenerale.commons.plugin.model.RootClassFolder;
5+
import com.societegenerale.commons.plugin.rules.classesForTests.DoubleIncludedCustomRule;
6+
import com.societegenerale.commons.plugin.rules.classesForTests.DummyCustomRule;
7+
import com.societegenerale.commons.plugin.rules.classesForTests.IncludedCustomRule;
8+
import com.societegenerale.commons.plugin.utils.ArchUtils;
9+
import com.tngtech.archunit.core.domain.JavaClasses;
10+
import org.junit.jupiter.api.BeforeAll;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.Arrays;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.mockito.Mockito.mock;
17+
18+
class InvokableRulesTest {
19+
20+
@BeforeAll
21+
@SuppressWarnings("InstantiationOfUtilityClass")
22+
static void instantiateArchUtils() {
23+
new ArchUtils(mock(Log.class));
24+
}
25+
26+
@Test
27+
void shouldInvokeAllRulesDefinedAsFields() {
28+
assertThat(invokeAndGetMessage(DummyCustomRule.class))
29+
.contains("Rule 'classes should be annotated with @Test' was violated")
30+
.contains("Rule 'classes should reside in a package 'myPackage'' was violated");
31+
}
32+
33+
@Test
34+
void shouldInvokeSpecificRuleDefinedAsField() {
35+
assertThat(invokeAndGetMessage(DummyCustomRule.class, "annotatedWithTest"))
36+
.contains("Rule 'classes should be annotated with @Test' was violated")
37+
.doesNotContain("Rule 'classes should reside in a package 'myPackage'' was violated");
38+
}
39+
40+
@Test
41+
void shouldInvokeAllRulesIncludedViaField() {
42+
assertThat(invokeAndGetMessage(IncludedCustomRule.class))
43+
.contains("Rule 'classes should be annotated with @Test' was violated")
44+
.contains("Rule 'classes should reside in a package 'myPackage'' was violated");
45+
}
46+
47+
@Test
48+
void shouldInvokeSpecificRuleIncludedViaField() {
49+
assertThat(invokeAndGetMessage(IncludedCustomRule.class, "annotatedWithTest"))
50+
.contains("Rule 'classes should be annotated with @Test' was violated")
51+
.doesNotContain("Rule 'classes should reside in a package 'myPackage'' was violated");
52+
}
53+
54+
@Test
55+
void shouldInvokeAllRulesIncludedViaFieldThatItselfIncludes() {
56+
assertThat(invokeAndGetMessage(DoubleIncludedCustomRule.class))
57+
.contains("Rule 'classes should be annotated with @Test' was violated")
58+
.contains("Rule 'classes should reside in a package 'myPackage'' was violated");
59+
}
60+
61+
@Test
62+
void shouldInvokeSpecificRuleIncludedViaFieldThatItselfIncludes() {
63+
assertThat(invokeAndGetMessage(DoubleIncludedCustomRule.class,"annotatedWithTest"))
64+
.contains("Rule 'classes should be annotated with @Test' was violated")
65+
.doesNotContain("Rule 'classes should reside in a package 'myPackage'' was violated");
66+
}
67+
68+
69+
private static String invokeAndGetMessage(Class<?> rulesClass, String... checks) {
70+
JavaClasses javaClasses = ArchUtils.importAllClassesInPackage(new RootClassFolder(""), "");
71+
InvokableRules invokableRules = InvokableRules.of(rulesClass.getName(), Arrays.asList(checks), mock(Log.class));
72+
InvokableRules.InvocationResult invocationResult = invokableRules.invokeOn(javaClasses);
73+
return invocationResult.getMessage();
74+
}
75+
}

0 commit comments

Comments
 (0)