Repository: tng/archunit
Issue: @ArchIgnore is ignored on ArchRules fields

ArchRule suites ignore the `@ArchIgnore` annotation, e.g.:

```
public class MyRules {
    @ArchTest
    public static final ArchRule someRule = ...
}
```

```
@RunWith(ArchUnitRunner.class)
// ...
public class MyArchTest {
    @ArchTest
    @ArchIgnore // This is ignored
    public static final ArchRules rules = ArchRules.in(MyRules.class);
}
```

Expected behavior when running `MyArchTest`:
All rules within `MyRules` are skipped

Actual behavior:
Rules within `MyRules` run anyway.

Changed files:
archunit-junit/src/test/java/com/tngtech/archunit/junit/ArchUnitRunnerRunsRuleSetsTest.java
archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRuleExecution.java
archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestExecution.java
archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchUnitRunner.java
archunit-integration-test/src/test/java/com/tngtech/archunit/junit/ArchUnitIntegrationTestRunner.java
archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRules.java
archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestMethodExecution.java

diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/junit/ArchUnitIntegrationTestRunner.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/junit/ArchUnitIntegrationTestRunner.java
index 29714924..9629f4f5 100644
--- a/archunit-integration-test/src/test/java/com/tngtech/archunit/junit/ArchUnitIntegrationTestRunner.java
+++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/junit/ArchUnitIntegrationTestRunner.java
@@ -1,6 +1,7 @@
 package com.tngtech.archunit.junit;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
@@ -14,6 +15,7 @@ import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.mockito.Mockito.mock;
 
 public class ArchUnitIntegrationTestRunner extends ArchUnitRunner {
     public ArchUnitIntegrationTestRunner(Class<?> testClass) throws InitializationError {
@@ -113,7 +115,7 @@ public class ArchUnitIntegrationTestRunner extends ArchUnitRunner {
         private JavaClasses classes;
 
         ClassesCaptor() {
-            super(Object.class);
+            super(Object.class, mock(AnnotatedElement.class), false);
         }
 
         @Override
diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRuleExecution.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRuleExecution.java
index 0cfe4218..51e5e28e 100644
--- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRuleExecution.java
+++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRuleExecution.java
@@ -30,8 +30,8 @@ class ArchRuleExecution extends ArchTestExecution {
     @VisibleForTesting
     final ArchRule rule;
 
-    ArchRuleExecution(Class<?> testClass, Field ruleField) {
-        super(testClass);
+    ArchRuleExecution(Class<?> testClass, Field ruleField, boolean forceIgnore) {
+        super(testClass, ruleField, forceIgnore);
 
         validatePublicStatic(ruleField);
         checkArgument(ArchRule.class.isAssignableFrom(ruleField.getType()),
diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRules.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRules.java
index 1f4ea221..f139d4f9 100644
--- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRules.java
+++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchRules.java
@@ -29,6 +29,7 @@ import com.tngtech.archunit.junit.ReflectionUtils.Predicate;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+import static com.tngtech.archunit.junit.ArchTestExecution.elementShouldBeIgnored;
 import static com.tngtech.archunit.junit.ReflectionUtils.getAllFields;
 import static com.tngtech.archunit.junit.ReflectionUtils.getAllMethods;
 
@@ -56,21 +57,21 @@ public final class ArchRules {
         return new ArchRules(definitionLocation);
     }
 
-    Set<ArchTestExecution> asTestExecutions() {
+    Set<ArchTestExecution> asTestExecutions(boolean forceIgnore) {
         ImmutableSet.Builder<ArchTestExecution> result = ImmutableSet.builder();
         for (Field field : fields) {
-            result.addAll(archRuleExecutionsOf(field));
+            result.addAll(archRuleExecutionsOf(field, forceIgnore));
         }
         for (Method method : methods) {
-            result.add(new ArchTestMethodExecution(method.getDeclaringClass(), method));
+            result.add(new ArchTestMethodExecution(method.getDeclaringClass(), method, forceIgnore));
         }
         return result.build();
     }
 
-    private Set<ArchTestExecution> archRuleExecutionsOf(Field field) {
+    private Set<ArchTestExecution> archRuleExecutionsOf(Field field, boolean forceIgnore) {
         return ArchRules.class.isAssignableFrom(field.getType()) ?
-                getArchRulesIn(field).asTestExecutions() :
-                Collections.<ArchTestExecution>singleton(new ArchRuleExecution(field.getDeclaringClass(), field));
+                getArchRulesIn(field).asTestExecutions(forceIgnore || elementShouldBeIgnored(field)) :
+                Collections.<ArchTestExecution>singleton(new ArchRuleExecution(field.getDeclaringClass(), field, forceIgnore));
     }
 
     private ArchRules getArchRulesIn(Field field) {
diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestExecution.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestExecution.java
index 163ecb1d..8db416e2 100644
--- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestExecution.java
+++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestExecution.java
@@ -16,6 +16,8 @@
 package com.tngtech.archunit.junit;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Modifier;
 
@@ -28,9 +30,11 @@ import static com.google.common.base.Preconditions.checkArgument;
 
 abstract class ArchTestExecution {
     final Class<?> testClass;
+    private final boolean ignore;
 
-    ArchTestExecution(Class<?> testClass) {
+    ArchTestExecution(Class<?> testClass, AnnotatedElement ruleDeclaration, boolean forceIgnore) {
         this.testClass = testClass;
+        this.ignore = forceIgnore || elementShouldBeIgnored(testClass, ruleDeclaration);
     }
 
     static <T extends Member> T validatePublicStatic(T member) {
@@ -53,7 +57,16 @@ abstract class ArchTestExecution {
     abstract <T extends Annotation> T getAnnotation(Class<T> type);
 
     boolean ignore() {
-        return testClass.getAnnotation(ArchIgnore.class) != null || getAnnotation(ArchIgnore.class) != null;
+        return ignore;
+    }
+
+    static boolean elementShouldBeIgnored(Field field) {
+        return elementShouldBeIgnored(field.getDeclaringClass(), field);
+    }
+
+    private static boolean elementShouldBeIgnored(Class<?> testClass, AnnotatedElement ruleDeclaration) {
+        return testClass.getAnnotation(ArchIgnore.class) != null ||
+                ruleDeclaration.getAnnotation(ArchIgnore.class) != null;
     }
 
     abstract static class Result {
diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestMethodExecution.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestMethodExecution.java
index 0750688e..2bf674d6 100644
--- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestMethodExecution.java
+++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchTestMethodExecution.java
@@ -26,8 +26,8 @@ import org.junit.runners.model.FrameworkMethod;
 class ArchTestMethodExecution extends ArchTestExecution {
     private final Method testMethod;
 
-    ArchTestMethodExecution(Class<?> testClass, Method testMethod) {
-        super(testClass);
+    ArchTestMethodExecution(Class<?> testClass, Method testMethod, boolean forceIgnore) {
+        super(testClass, testMethod, forceIgnore);
         this.testMethod = validatePublicStatic(testMethod);
     }
 
diff --git a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchUnitRunner.java b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchUnitRunner.java
index b3892a75..270e4aea 100644
--- a/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchUnitRunner.java
+++ b/archunit-junit/src/main/java/com/tngtech/archunit/junit/ArchUnitRunner.java
@@ -33,6 +33,7 @@ import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.InitializationError;
 
 import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
+import static com.tngtech.archunit.junit.ArchTestExecution.elementShouldBeIgnored;
 
 /**
  * Evaluates {@link ArchRule ArchRules} against the classes inside of the packages specified via
@@ -80,9 +81,10 @@ public class ArchUnitRunner extends ParentRunner<ArchTestExecution> {
 
     private Set<ArchTestExecution> findArchRulesIn(FrameworkField ruleField) {
         if (ruleField.getType() == ArchRules.class) {
-            return getArchRules(ruleField).asTestExecutions();
+            boolean ignore = elementShouldBeIgnored(ruleField.getField());
+            return getArchRules(ruleField).asTestExecutions(ignore);
         }
-        return Collections.<ArchTestExecution>singleton(new ArchRuleExecution(getTestClass().getJavaClass(), ruleField.getField()));
+        return Collections.<ArchTestExecution>singleton(new ArchRuleExecution(getTestClass().getJavaClass(), ruleField.getField(), false));
     }
 
     private ArchRules getArchRules(FrameworkField ruleField) {
@@ -97,7 +99,7 @@ public class ArchUnitRunner extends ParentRunner<ArchTestExecution> {
     private Collection<ArchTestExecution> findArchRuleMethods() {
         List<ArchTestExecution> result = new ArrayList<>();
         for (FrameworkMethod testMethod : getTestClass().getAnnotatedMethods(ArchTest.class)) {
-            result.add(new ArchTestMethodExecution(getTestClass().getJavaClass(), testMethod.getMethod()));
+            result.add(new ArchTestMethodExecution(getTestClass().getJavaClass(), testMethod.getMethod(), false));
         }
         return result;
     }
diff --git a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ArchUnitRunnerRunsRuleSetsTest.java b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ArchUnitRunnerRunsRuleSetsTest.java
index 1ab6eb87..e9ddf172 100644
--- a/archunit-junit/src/test/java/com/tngtech/archunit/junit/ArchUnitRunnerRunsRuleSetsTest.java
+++ b/archunit-junit/src/test/java/com/tngtech/archunit/junit/ArchUnitRunnerRunsRuleSetsTest.java
@@ -25,12 +25,17 @@ import org.mockito.junit.MockitoRule;
 import static com.google.common.base.Preconditions.checkState;
 import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext;
 import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.ArchTestWithRuleLibrary.someOtherMethodRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredRules.someIgnoredFieldRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredRules.someIgnoredMethodRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredSubRules.someIgnoredSubFieldRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredSubRules.someIgnoredSubMethodRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredSubRules.someNonIgnoredSubFieldRuleName;
+import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.IgnoredSubRules.someNonIgnoredSubMethodRuleName;
 import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.Rules.someFieldRuleName;
 import static com.tngtech.archunit.junit.ArchUnitRunnerRunsRuleSetsTest.Rules.someMethodRuleName;
 import static com.tngtech.archunit.junit.ArchUnitRunnerTestUtils.getRule;
 import static com.tngtech.archunit.junit.ArchUnitRunnerTestUtils.newRunnerFor;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.all;
-import static com.tngtech.archunit.lang.syntax.ClassesIdentityTransformer.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
 import static com.tngtech.archunit.testutil.TestUtils.invoke;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -55,9 +60,15 @@ public class ArchUnitRunnerRunsRuleSetsTest {
     @InjectMocks
     private ArchUnitRunner runnerForRuleSet = newRunnerFor(ArchTestWithRuleSet.class);
 
+    @InjectMocks
+    private ArchUnitRunner runnerForIgnoredRuleSet = newRunnerFor(ArchTestWithIgnoredRuleSet.class);
+
     @InjectMocks
     private ArchUnitRunner runnerForRuleLibrary = newRunnerFor(ArchTestWithRuleLibrary.class);
 
+    @InjectMocks
+    private ArchUnitRunner runnerForIgnoredRuleLibrary = newRunnerFor(ArchTestWithIgnoredRuleLibrary.class);
+
     private JavaClasses cachedClasses = importClassesWithContext(ArchUnitRunnerRunsRuleSetsTest.class);
 
     @Before
@@ -86,12 +97,81 @@ public class ArchUnitRunnerRunsRuleSetsTest {
 
     @Test
     public void can_run_rule_field() throws Exception {
-        run(someFieldRuleName);
+        run(someFieldRuleName, runnerForRuleSet, verifyTestRan());
     }
 
     @Test
     public void can_run_rule_method() throws Exception {
-        run(someMethodRuleName);
+        run(someMethodRuleName, runnerForRuleSet, verifyTestRan());
+    }
+
+    @Test
+    public void ignores_field_rule_of_ignored_rule_set() {
+        run(someFieldRuleName, runnerForIgnoredRuleSet, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_method_rule_of_ignored_rule_set() {
+        run(someMethodRuleName, runnerForIgnoredRuleSet, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_nested_field_rule() {
+        run(someIgnoredFieldRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_nested_method_rule() {
+        run(someIgnoredMethodRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_double_nested_field_rule() {
+        run(someIgnoredSubFieldRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_double_nested_method_rule() {
+        run(someIgnoredSubMethodRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    @Test
+    public void runs_double_nested_field_method_rule() {
+        run(someNonIgnoredSubFieldRuleName, runnerForIgnoredRuleLibrary, verifyTestRan());
+    }
+
+    @Test
+    public void runs_double_nested_method_rule() {
+        run(someNonIgnoredSubMethodRuleName, runnerForIgnoredRuleLibrary, verifyTestRan());
+    }
+
+    @Test
+    public void ignores_double_nested_field_rule_in_ignored_rule_set() {
+        run(someFieldRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    @Test
+    public void ignores_double_nested_method_rule_in_ignored_rule_set() {
+        run(someMethodRuleName, runnerForIgnoredRuleLibrary, verifyTestIgnored());
+    }
+
+    private Runnable verifyTestIgnored() {
+        return new Runnable() {
+            @Override
+            public void run() {
+                verify(runNotifier).fireTestIgnored(descriptionCaptor.capture());
+            }
+        };
+    }
+
+    private Runnable verifyTestRan() {
+        return new Runnable() {
+            @Override
+            public void run() {
+                verify(runNotifier).fireTestStarted(any(Description.class));
+                verify(runNotifier).fireTestFinished(descriptionCaptor.capture());
+            }
+        };
     }
 
     // extractingResultOf(..) only looks for public methods
@@ -113,13 +193,12 @@ public class ArchUnitRunnerRunsRuleSetsTest {
         };
     }
 
-    private void run(String ruleName) {
-        ArchTestExecution rule = getRule(ruleName, runnerForRuleSet);
+    private void run(String ruleName, ArchUnitRunner runner, Runnable testVerification) {
+        ArchTestExecution rule = getRule(ruleName, runner);
 
-        runnerForRuleSet.runChild(rule, runNotifier);
+        runner.runChild(rule, runNotifier);
 
-        verify(runNotifier).fireTestStarted(any(Description.class));
-        verify(runNotifier).fireTestFinished(descriptionCaptor.capture());
+        testVerification.run();
         assertThat(descriptionCaptor.getValue().toString()).contains(ruleName);
     }
 
@@ -141,20 +220,80 @@ public class ArchUnitRunnerRunsRuleSetsTest {
         public static final ArchRules rules = ArchRules.in(Rules.class);
     }
 
+    @AnalyzeClasses(packages = "some.pkg")
+    public static class ArchTestWithIgnoredRuleSet {
+        @ArchTest
+        @ArchIgnore
+        public static final ArchRules rules = ArchRules.in(Rules.class);
+    }
+
+    @AnalyzeClasses(packages = "some.pkg")
+    public static class ArchTestWithIgnoredRuleLibrary {
+        @ArchTest
+        public static final ArchRules rules = ArchRules.in(IgnoredRules.class);
+    }
+
     public static class Rules {
         static final String someFieldRuleName = "someFieldRule";
         static final String someMethodRuleName = "someMethodRule";
 
         @ArchTest
-        public static final ArchRule someFieldRule = all(classes())
-                .should(new ArchCondition<JavaClass>("satisfy something") {
-                    @Override
-                    public void check(JavaClass item, ConditionEvents events) {
-                    }
-                });
+        public static final ArchRule someFieldRule = classes().should(satisfySomething());
 
         @ArchTest
         public static void someMethodRule(JavaClasses classes) {
         }
     }
+
+    public static class IgnoredRules {
+        static final String someIgnoredFieldRuleName = "someIgnoredFieldRule";
+        static final String someIgnoredMethodRuleName = "someIgnoredMethodRule";
+
+        @ArchTest
+        @ArchIgnore
+        public static final ArchRule someIgnoredFieldRule = classes().should(satisfySomething());
+
+        @ArchTest
+        @ArchIgnore
+        public static void someIgnoredMethodRule(JavaClasses classes) {
+        }
+
+        @ArchTest
+        public static final ArchRules subRules = ArchRules.in(IgnoredSubRules.class);
+
+        @ArchTest
+        @ArchIgnore
+        public static final ArchRules ignoredSubRules = ArchRules.in(Rules.class);
+    }
+
+    public static class IgnoredSubRules {
+        static final String someIgnoredSubFieldRuleName = "someIgnoredSubFieldRule";
+        static final String someNonIgnoredSubFieldRuleName = "someNonIgnoredSubFieldRule";
+        static final String someIgnoredSubMethodRuleName = "someIgnoredSubMethodRule";
+        static final String someNonIgnoredSubMethodRuleName = "someNonIgnoredSubMethodRule";
+
+        @ArchTest
+        @ArchIgnore
+        public static final ArchRule someIgnoredSubFieldRule = classes().should(satisfySomething());
+
+        @ArchTest
+        public static final ArchRule someNonIgnoredSubFieldRule = classes().should(satisfySomething());
+
+        @ArchTest
+        @ArchIgnore
+        public static void someIgnoredSubMethodRule(JavaClasses classes) {
+        }
+
+        @ArchTest
+        public static void someNonIgnoredSubMethodRule(JavaClasses classes) {
+        }
+    }
+
+    private static ArchCondition<JavaClass> satisfySomething() {
+        return new ArchCondition<JavaClass>("satisfy something") {
+            @Override
+            public void check(JavaClass item, ConditionEvents events) {
+            }
+        };
+    }
 }
\\ No newline at end of file