View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.Checker;
8   import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
9   import com.puppycrawl.tools.checkstyle.PropertiesExpander;
10  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
11  import com.puppycrawl.tools.checkstyle.api.AuditListener;
12  import java.io.File;
13  import java.nio.charset.StandardCharsets;
14  import java.util.ArrayList;
15  import java.util.LinkedList;
16  import java.util.List;
17  import java.util.Objects;
18  import java.util.Properties;
19  import java.util.stream.Stream;
20  import org.apache.commons.io.IOUtils;
21  import org.cactoos.text.Joined;
22  import org.hamcrest.MatcherAssert;
23  import org.hamcrest.Matchers;
24  import org.junit.jupiter.params.ParameterizedTest;
25  import org.junit.jupiter.params.provider.MethodSource;
26  import org.xml.sax.InputSource;
27  
28  /**
29   * Integration test case for all checkstyle checks.
30   * @since 0.3
31   */
32  final class ChecksTest {
33  
34      /**
35       * Test checkstyle for true negative.
36       * @param dir Directory where test scripts are located.
37       * @throws Exception If something goes wrong
38       */
39      @ParameterizedTest
40      @MethodSource("checks")
41      @SuppressWarnings("PMD.JUnitAssertionsShouldIncludeMessage")
42      void testCheckstyleTruePositive(final String dir) throws Exception {
43          final Collector collector = new ChecksTest.Collector();
44          final AuditListener listener = new FakeAuditListener(collector);
45          this.check(dir, "/Invalid.java", listener);
46          final String[] violations = IOUtils.toString(
47              Objects.requireNonNull(
48                  this.getClass().getResourceAsStream(
49                      String.format("%s/violations.txt", dir)
50                  )
51              ),
52              StandardCharsets.UTF_8
53          ).split("\n");
54          for (final String line : violations) {
55              final String[] sectors = line.split(":");
56              final Integer pos = Integer.valueOf(sectors[0]);
57              final String needle = sectors[1].trim();
58              MatcherAssert.assertThat(
59                  collector.has(pos, needle),
60                  Matchers.describedAs(
61                      String.format(
62                          "Line no.%d ('%s') not reported by %s: '%s'",
63                          pos,
64                          needle,
65                          dir,
66                          collector.summary()
67                      ),
68                      Matchers.is(true)
69                  )
70              );
71          }
72          MatcherAssert.assertThat(
73              collector.eventCount(),
74              Matchers.describedAs(
75                  String.format(
76                      "Got more violations that expected for directory %s (%s)",
77                      dir, collector.summary()
78                  ),
79                  Matchers.equalTo(violations.length)
80              )
81          );
82      }
83  
84      /**
85       * Test checkstyle for true negative.
86       * @param dir Directory where test scripts are located.
87       * @throws Exception If something goes wrong
88       */
89      @ParameterizedTest
90      @MethodSource("checks")
91      void testCheckstyleTrueNegative(final String dir) throws Exception {
92          final Collector collector = new ChecksTest.Collector();
93          final AuditListener listener = new FakeAuditListener(collector);
94          this.check(dir, "/Valid.java", listener);
95          MatcherAssert.assertThat(
96              "Log should be empty for valid files",
97              collector.summary(),
98              Matchers.equalTo("")
99          );
100     }
101 
102     /**
103      * Check one file.
104      * @param dir Directory where test scripts are located.
105      * @param name The name of the check
106      * @param listener The listener
107      * @throws Exception If something goes wrong inside
108      */
109     private void check(
110         final String dir, final String name, final AuditListener listener
111     ) throws Exception {
112         final Checker checker = new Checker();
113         final InputSource src = new InputSource(
114             this.getClass().getResourceAsStream(
115                 String.format("%s/config.xml", dir)
116             )
117         );
118         checker.setModuleClassLoader(
119             Thread.currentThread().getContextClassLoader()
120         );
121         checker.configure(
122             ConfigurationLoader.loadConfiguration(
123                 src,
124                 new PropertiesExpander(new Properties()),
125                 ConfigurationLoader.IgnoredModulesOptions.OMIT
126             )
127         );
128         final List<File> files = new ArrayList<>(0);
129         files.add(
130             new File(
131                 this.getClass().getResource(
132                     String.format("%s%s", dir, name)
133                 ).getFile()
134             )
135         );
136         checker.addListener(listener);
137         checker.process(files);
138         checker.destroy();
139     }
140 
141     /**
142      * Returns full list of checks.
143      * @return The list
144      */
145     @SuppressWarnings("PMD.UnusedPrivateMethod")
146     private static Stream<String> checks() {
147         return Stream.of(
148             "MethodsOrderCheck",
149             "MultilineJavadocTagsCheck",
150             "StringLiteralsConcatenationCheck",
151             "EmptyLinesCheck",
152             "ImportCohesionCheck",
153             "BracketsStructureCheck",
154             "CurlyBracketsStructureCheck",
155             "JavadocLocationCheck",
156             "JavadocParameterOrderCheck",
157             "MethodBodyCommentsCheck",
158             "RequireThisCheck",
159             "ProtectedMethodInFinalClassCheck",
160             "NoJavadocForOverriddenMethodsCheck",
161             "NonStaticMethodCheck",
162             "ConstantUsageCheck",
163             "JavadocEmptyLineCheck",
164             "JavadocTagsCheck",
165             "ProhibitNonFinalClassesCheck",
166             "QualifyInnerClassCheck",
167             "CommentCheck",
168             "ProhibitUnusedPrivateConstructorCheck"
169         ).map(s -> String.format("ChecksTest/%s", s));
170     }
171 
172     /**
173      * Mocked collector of checkstyle events.
174      *
175      * @since 0.1
176      */
177     private static final class Collector {
178         /**
179          * List of events received.
180          */
181         private final List<AuditEvent> events = new LinkedList<>();
182 
183         public void add(final AuditEvent event) {
184             this.events.add(event);
185         }
186 
187         /**
188          * How many messages do we have?
189          * @return Amount of messages reported
190          */
191         public int eventCount() {
192             return this.events.size();
193         }
194 
195         /**
196          * Do we have this message for this line?
197          * @param line The number of the line
198          * @param msg The message we're looking for
199          * @return This message was reported for the give line?
200          */
201         public boolean has(final Integer line, final String msg) {
202             boolean has = false;
203             for (final AuditEvent event : this.events) {
204                 if (event.getLine() == line && event.getMessage().equals(msg)) {
205                     has = true;
206                     break;
207                 }
208             }
209             return has;
210         }
211 
212         /**
213          * Returns full summary.
214          * @return The test summary of all events
215          */
216         public String summary() {
217             final List<String> msgs = new LinkedList<>();
218             for (final AuditEvent event : this.events) {
219                 msgs.add(
220                     String.format(
221                         "%s:%s",
222                         event.getLine(),
223                         event.getMessage()
224                     )
225                 );
226             }
227             return new Joined("; ", msgs).toString();
228         }
229     }
230 
231     /**
232      * Fake Audit Listener.
233      *
234      * Just to set an event on addError() to a mocked Collector.
235      *
236      * @since 0.24.1
237      */
238     private static final class FakeAuditListener implements AuditListener {
239         /**
240          * Mocked collector.
241          */
242         private final ChecksTest.Collector collector;
243 
244         FakeAuditListener(final ChecksTest.Collector collect) {
245             this.collector = collect;
246         }
247 
248         @Override
249         public void auditStarted(final AuditEvent event) {
250             // Intentionally left blank
251         }
252 
253         @Override
254         public void auditFinished(final AuditEvent event) {
255             // Intentionally left blank
256         }
257 
258         @Override
259         public void fileStarted(final AuditEvent event) {
260             // Intentionally left blank
261         }
262 
263         @Override
264         public void fileFinished(final AuditEvent event) {
265             // Intentionally left blank
266         }
267 
268         @Override
269         public void addError(final AuditEvent event) {
270             this.collector.add(event);
271         }
272 
273         @Override
274         public void addException(
275             final AuditEvent event,
276             final Throwable throwable
277         ) {
278             // Intentionally left blank
279         }
280     }
281 }