Commit bf3653d
Make every rule able to fail, and prove it (#4)
* add a red/green fixture test for every shipped rule
Each of the 13 rules gets a fixture built to violate it and one built to satisfy
it, asserted on the violation count and message rather than on the rule having
run. The library had no such test, which is why rules that match nothing have
been reporting success.
This commit is deliberately red: nine tests fail. Eight fixtures that must
produce a violation do not.
- test classes with no production counterpart (rule is fully dead)
- a blacklisted annotation on a constructor parameter
- a blacklisted method call from a constructor
- a blacklisted method call from an instance field initializer
- System.out from a constructor
- a @nested test class whose production class is missing entirely
- a controller method covered only by a longer-named sibling's @nested class
- a non-static SortMappings field
The ninth is not a fixture: the blacklist names three AssertJ methods that do not
exist, so those entries can never match.
Fixtures live outside it.aboutbits.archunit.toolbox so the project's own
ArchitectureTest does not analyse them, and are excluded from surefire because
some are named *Test.
Third-party types the blacklists name are stubbed rather than depended on:
spring-boot-toolbox depends on archunit-toolbox, so SortMappings, @Store and
@ArchAllowDirectAccess cannot come from there. The stub contracts were verified
against the real 2.5.2 artifact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* match test class names and code unit bodies in one shared place
Two root causes, each shared by several rules.
The suffix regex was hand-built in five places, in two shapes, against two
different inputs. The counterpart rule's condition rebuilt it without the
leading ".+" and matched it against getSimpleName() with String.matches, which
anchors both ends - so only a class named exactly "Test" got past the guard and
every real test class returned before the counterpart lookup ever ran. The rule
has therefore never reported anything.
TestClassNames now owns the pattern, the stripping and the selection predicate.
The redundant guard in the condition is gone rather than corrected: the selection
already guarantees the suffix, and re-deriving it in the condition is what let
the two drift apart. Selection also matches the simple name and requires a
non-empty production name to be left over, so it can no longer accept a name the
stripping turns into nothing ("CacheTest" matched with "Cache" as the ".+").
Three rules walked getMethods(), which excludes constructors - and with them
every instance field initializer, since that is compiled into the constructor.
So a blacklisted annotation on a constructor parameter, the canonical Lombok
position, was invisible. All three now walk getCodeUnits(), which also subsumes
the separate static-initializer branches two of them carried.
Also drops three blacklist entries naming AssertJ methods that do not exist
(assertThrows, assertThrowsExactly, assertDoesNotThrow are JUnit's). An entry
that can never match reads as coverage without providing any.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* report what a rule cannot verify instead of passing
Removes the remaining ways a rule stayed quiet.
allowEmptyShould(true) is gone everywhere. A rule that selects nothing reported
success, indistinguishable from a rule that is satisfied, which is what let a
dead rule survive. EmptySelectionTest pins this for all eight rules that narrow
their input. Consequence to be aware of: CommonArchRuleCollection now fails in a
project with no controllers or no @Store classes, so implement it only where
those exist.
The @nested name rule skipped a nested class silently whenever the production
class was missing and the nested class was not inside a group - so the case with
nothing to compare against was the one case never reported. It now reports, and
in exchange honours @ArchIgnoreNoProductionCounterpart: a test class that
declares it has no production counterpart has no production methods either.
The security-test rule accepted any @nested class *starting with* the method
name, so getAll() counted as covered by GetAllArchived. It now requires the name
to match exactly or to continue with "$", which keeps grouped @nested classes
working.
The SortMappings rule swallowed every failure to read a field into a log.warn -
and the build has no SLF4J provider, so the warning was discarded outright. A
non-static field, an unreadable field, an unresolvable enum type and a value
that is not a Map are all violations now. Non-static in particular is reported
as such, since it can never be validated.
Finally, @ArchIgnoreNoProductionCounterpart and @ArchIgnoreGroupName no longer
meta-annotate ArchUnit's @ArchIgnore. The ArchUnit JUnit engine resolves
meta-annotations, so annotating a test class skipped *every* @archtest on it and
still reported BUILD SUCCESS - verified: putting it on this project's own
ArchitectureTest turned 11 rules into 11 skips. Both annotations are read by
their own type, so the meta-annotation bought nothing. ArchitectureTest now
carries it in place of the name that was hardcoded into the counterpart rule,
which also keeps the annotation exercised.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* document the current API and the opt-out annotations
The usage section still described the 1.1.0 API (extends ArchitectureTestBase,
a BLACKLISTED_CLASSES static block), which no longer exists. Replaces it with the
rule collections, records why no rule allows an empty selection, and documents
both opt-out annotations - neither of which any consumer appears to use yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* allow rules a project has no code for, and guard the import instead
Failing on an empty selection was the wrong call. Seven of the eight rules that
narrow their input have a legitimate empty case - no records, no controllers, no
@Store classes, nobody using @nested - and one of them described this repository
before this branch: a project whose only test is the ArchitectureTest has no
@test methods at all. Whether that code exists is the project's business.
allowEmptyShould(true) was never what protected against a dead rule either. It
fires on what a consumer's code happens to contain and says nothing about whether
a rule's logic works: the counterpart rule that started this had a perfectly
non-empty selection and a broken condition. The guarantee is the red test per
rule in this project, so all 13 rules now tolerate an empty selection.
That leaves one case worth failing on. A mistyped or moved package in
@AnalyzeClasses imports nothing, and every rule then passes without looking at a
single class. AnalyzedPackagesMustContainClassesArchRule turns that into one
failure naming the cause, instead of five rules passing silently and eight
reporting that they checked nothing. It is in both collections, so it applies
wherever the toolbox is used.
EmptySelectionTest is inverted to match: it now pins that every rule accepts a
project with nothing for it to check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* use markdown javadoc
Converts every documentation comment to the /// form of JEP 467: 54 javadoc
comments plus two block comments that already sat in documentation positions.
Markdown idiom throughout - `code` for {@code}, [Type#member] for {@link}, blank
/// lines for <p>, and backticked annotation names for the @ entities.
The five comments inside method bodies stay /* */, since /// is a documentation
comment form and those document statements rather than declarations.
No behaviour change. Verified with javadoc -Xdoclint:all: no warnings, the
[#member] references resolve, and the generated HTML shows code spans, paragraphs
and links rather than literal markdown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* make the opt-outs usable once, and close the blacklist hole
Addresses the review on #4, all three points verified against reverts.
The opt-outs are read with areNotMetaAnnotatedWith / isMetaAnnotatedWith instead
of the direct-only variants, so a project carries @ArchIgnoreNoProductionCounterpart
on one stereotype of its own rather than repeating it on 74 classes. ArchUnit counts
a direct annotation as meta-annotated, so annotating a single class still works.
@ArchIgnoreGroupName gets the same treatment in all three places it is read: it is
the sibling opt-out with the identical ergonomics problem, and leaving it direct-only
would be half a fix.
Architecture tests are exempted from the production-counterpart rule by package,
via .._architecture.. alongside .._support.. / .._config.., so dropping the
hardcoded "ArchitectureTest" name does not push boilerplate onto every consumer.
Deliberately not added to TestClassVisibilityArchRule: being package private is as
achievable for an architecture test as for any other test, so the two exclusion
lists encode different facts and are meant to differ.
org.junit.jupiter.api.Assertions.assertThrowsExactly is blacklisted again. It only
ever existed under the AssertJ namespace that the previous commit removed, so the
cleanup dropped the house rule along with the bogus entry. Now covered by a fixture
that actually calls it, not only by a list assertion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* use a multiline string for the empty-import message
Review feedback on #4: text block instead of concatenation. The message now reads
over two lines, which also matches how the other rules format multi-line output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* address review: lookup cost, null mappings, and an upgrade note
Four review comments on #4.
The two counterpart lookups scanned every imported class per test class. JavaClasses
is map-backed by fully qualified name, so both are now contain()/get(). New fixture
nestedclassname.goodgroup pins that a production class nested inside another is still
found, since its name contains a '$' and nothing covered that before - without it a
keying difference would have passed silently, the badgroup fixture expecting a
violation either way.
SortMappings gets the null-value fixture: a static field that reads back as null
yields nothing to compare, so the rule reports rather than calling it exhaustive.
The undeterminable-enum and non-enum-key paths remain untested.
Readme gains an upgrade note naming what breaks when a consumer moves off 1.2.0 -
revived rules surfacing real violations, and the new empty-import failure - and the
opt-out section now says that @disabled and @ArchIgnore are matched through
meta-annotations too, so a stereotype carrying either exempts its classes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 8aee4f1 commit bf3653d
132 files changed
Lines changed: 2413 additions & 307 deletions
File tree
- src
- main/java/it/aboutbits/archunit/toolbox
- config
- rule
- base
- common
- support
- util
- test/java
- it/aboutbits
- archunit
- fixture
- barren
- blacklistannotations
- badclass
- badctorparam
- badfield
- badmethod
- badparam
- good
- blacklistclasses
- bad
- good
- blacklistmethods
- badconstructor
- badfieldinit
- badjunitassertion
- badmethod
- badstatic
- good
- jspecify
- bad
- good
- nestedclassname
- badgroup
- badmethod
- badnoproduction
- goodgroup
- goodmetagroup
- goodmetaoptout
- goodoptout
- good
- nestedclassvisibility
- bad
- good
- recordaccessor
- badnested
- goodaccessor
- goodoptout
- securitytested
- badmetagroup
- badmissing
- badnonested
- badprefix
- goodnestedgroup
- good
- sortmappings
- badnonstatic
- badnullvalue
- bad
- good
- systemout
- badconstructor
- badlambda
- badmethod
- badstatic
- good
- testclasspackage
- badarchitecture
- bad
- goodmetaoptout
- goodoptout
- good
- witharchitecture
- _architecture
- testclassvisibility
- bad
- good
- testmethodvisibility
- bad
- good
- toolbox
- rule
- base
- common
- support
- util
- springboot/toolbox
- archunit
- persistence
- stereotype
- net/datafaker
- org/junit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
53 | 79 | | |
54 | 80 | | |
55 | 81 | | |
| |||
98 | 124 | | |
99 | 125 | | |
100 | 126 | | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
101 | 141 | | |
102 | 142 | | |
103 | 143 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
19 | 38 | | |
20 | 39 | | |
21 | | - | |
22 | | - | |
| 40 | + | |
23 | 41 | | |
24 | 42 | | |
25 | 43 | | |
26 | 44 | | |
27 | 45 | | |
28 | 46 | | |
29 | 47 | | |
30 | | - | |
| 48 | + | |
| 49 | + | |
31 | 50 | | |
| 51 | + | |
| 52 | + | |
32 | 53 | | |
33 | | - | |
34 | | - | |
35 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
36 | 64 | | |
37 | 65 | | |
38 | 66 | | |
39 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
40 | 99 | | |
41 | 100 | | |
42 | | - | |
43 | | - | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
44 | 106 | | |
45 | 107 | | |
46 | 108 | | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
47 | 122 | | |
48 | 123 | | |
49 | 124 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
Lines changed: 2 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| 10 | + | |
9 | 11 | | |
10 | 12 | | |
11 | 13 | | |
Lines changed: 4 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
19 | 17 | | |
20 | 18 | | |
21 | 19 | | |
| |||
Lines changed: 26 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
Lines changed: 19 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
| 67 | + | |
66 | 68 | | |
67 | 69 | | |
68 | 70 | | |
| |||
87 | 89 | | |
88 | 90 | | |
89 | 91 | | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
94 | 97 | | |
95 | 98 | | |
96 | | - | |
97 | | - | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
98 | 102 | | |
99 | 103 | | |
100 | | - | |
| 104 | + | |
101 | 105 | | |
102 | | - | |
| 106 | + | |
103 | 107 | | |
104 | 108 | | |
105 | | - | |
106 | | - | |
| 109 | + | |
| 110 | + | |
107 | 111 | | |
108 | 112 | | |
109 | 113 | | |
110 | | - | |
| 114 | + | |
111 | 115 | | |
112 | | - | |
| 116 | + | |
| 117 | + | |
113 | 118 | | |
114 | 119 | | |
115 | | - | |
116 | | - | |
| 120 | + | |
| 121 | + | |
117 | 122 | | |
118 | 123 | | |
119 | 124 | | |
| |||
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
38 | 39 | | |
39 | 40 | | |
40 | 41 | | |
0 commit comments