View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.io.OutputStream;
28  import java.nio.charset.StandardCharsets;
29  
30  import org.junit.jupiter.api.Test;
31  
32  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
33  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
34  import com.puppycrawl.tools.checkstyle.api.DetailAST;
35  import com.puppycrawl.tools.checkstyle.api.FileContents;
36  import com.puppycrawl.tools.checkstyle.api.FileText;
37  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
38  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39  import com.puppycrawl.tools.checkstyle.api.Violation;
40  import com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck;
41  import com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck;
42  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck;
43  import com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck;
44  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
45  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
46  
47  public class XpathFileGeneratorAuditListenerTest {
48  
49      /** OS specific line separator. */
50      private static final String EOL = System.lineSeparator();
51  
52      private static final Violation FIRST_MESSAGE = createViolation(3, 51,
53              TokenTypes.LCURLY, null, LeftCurlyCheck.class);
54  
55      private static final Violation SECOND_MESSAGE = createViolation(15, 5,
56              TokenTypes.METHOD_DEF, "MyModule", MethodParamPadCheck.class);
57  
58      private static final Violation THIRD_MESSAGE = createViolation(17, 13,
59              TokenTypes.LITERAL_FOR, null, NestedForDepthCheck.class);
60  
61      private static final Violation FOURTH_MESSAGE = createViolation(5, 5,
62              TokenTypes.VARIABLE_DEF, "JavadocModuleId", JavadocVariableCheck.class);
63  
64      private final CloseAndFlushTestByteArrayOutputStream outStream =
65              new CloseAndFlushTestByteArrayOutputStream();
66  
67      static {
68          try {
69              constructEvents();
70          }
71          catch (Exception exc) {
72              throw new IllegalStateException("Failed to construct events for "
73                      + FOURTH_MESSAGE.getSourceName(), exc);
74          }
75      }
76  
77      private static void constructEvents() throws Exception {
78          final TreeWalkerAuditEvent event1 = createTreeWalkerAuditEvent(
79                  "InputXpathFileGeneratorAuditListener.java", FIRST_MESSAGE);
80  
81          final TreeWalkerAuditEvent event2 = createTreeWalkerAuditEvent(
82                  "InputXpathFileGeneratorAuditListener.java", SECOND_MESSAGE);
83  
84          final TreeWalkerAuditEvent event3 = createTreeWalkerAuditEvent(
85                  "InputXpathFileGeneratorAuditListener.java", THIRD_MESSAGE);
86  
87          final TreeWalkerAuditEvent event4 = createTreeWalkerAuditEvent(
88                  "InputXpathFileGeneratorAuditListener.java", FOURTH_MESSAGE);
89  
90          final XpathFileGeneratorAstFilter astFilter = new XpathFileGeneratorAstFilter();
91          astFilter.accept(event1);
92          astFilter.accept(event2);
93          astFilter.accept(event3);
94          astFilter.accept(event4);
95      }
96  
97      @Test
98      public void testFinishLocalSetup() {
99          final OutputStream out = new ByteArrayOutputStream();
100         final XpathFileGeneratorAuditListener listener =
101                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
102 
103         listener.finishLocalSetup();
104         listener.auditStarted(null);
105         listener.auditFinished(null);
106         final String actual = out.toString();
107         assertWithMessage("Output should be empty")
108             .that(actual)
109             .isEmpty();
110     }
111 
112     @Test
113     public void testFileStarted() {
114         final OutputStream out = new ByteArrayOutputStream();
115         final XpathFileGeneratorAuditListener listener =
116                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
117         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
118         listener.fileStarted(ev);
119         listener.auditFinished(null);
120         final String actual = out.toString();
121         assertWithMessage("Output should be empty")
122             .that(actual)
123             .isEmpty();
124     }
125 
126     @Test
127     public void testFileFinished() {
128         final OutputStream out = new ByteArrayOutputStream();
129         final XpathFileGeneratorAuditListener listener =
130                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
131         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
132         listener.fileFinished(ev);
133         listener.auditFinished(null);
134         final String actual = out.toString();
135         assertWithMessage("Output should be empty")
136             .that(actual)
137             .isEmpty();
138     }
139 
140     @Test
141     public void testAddException() {
142         final OutputStream out = new ByteArrayOutputStream();
143         final XpathFileGeneratorAuditListener logger =
144                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
145         logger.auditStarted(null);
146         final Violation violation =
147                 new Violation(1, 1,
148                         "messages.properties", null, null, null, getClass(), null);
149         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
150 
151         final UnsupportedOperationException exception =
152             getExpectedThrowable(UnsupportedOperationException.class,
153                 () -> logger.addException(ev, null));
154         assertWithMessage("Invalid exception message")
155             .that(exception.getMessage())
156             .isEqualTo("Operation is not supported");
157     }
158 
159     @Test
160     public void testCorrectOne() {
161         final AuditEvent event = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
162                 FIRST_MESSAGE);
163 
164         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
165                 + "<!DOCTYPE suppressions PUBLIC" + EOL
166                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
167                 + "//EN\"" + EOL
168                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
169                 + EOL
170                 + "<suppressions>" + EOL
171                 + "  <suppress-xpath" + EOL
172                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
173                 + "       checks=\"LeftCurlyCheck\""
174                 + EOL
175                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
176                 + "[@text='InputXpathFileGeneratorAuditListener']]"
177                 + "/OBJBLOCK/LCURLY\"/>" + EOL
178                 + "</suppressions>" + EOL;
179 
180         verifyOutput(expected, event);
181     }
182 
183     @Test
184     public void testCorrectTwo() {
185         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
186                 SECOND_MESSAGE);
187 
188         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
189                 THIRD_MESSAGE);
190 
191         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
192                 + "<!DOCTYPE suppressions PUBLIC" + EOL
193                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
194                 + "//EN\"" + EOL
195                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
196                 + EOL
197                 + "<suppressions>" + EOL
198                 + "  <suppress-xpath" + EOL
199                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
200                 + "       id=\"MyModule\"" + EOL
201                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
202                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
203                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]\"/>" + EOL
204                 + "  <suppress-xpath" + EOL
205                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
206                 + "       checks=\"NestedForDepthCheck\"" + EOL
207                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
208                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
209                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]/SLIST/LITERAL_FOR/SLIST"
210                 + "/LITERAL_FOR\"/>" + EOL
211                 + "</suppressions>" + EOL;
212 
213         verifyOutput(expected, event1, event2);
214     }
215 
216     @Test
217     public void testOnlyOneMatching() {
218         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
219                 10, 5, MethodParamPadCheck.class);
220 
221         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
222                 5, 5, JavadocVariableCheck.class);
223 
224         final AuditEvent event3 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
225                 FOURTH_MESSAGE);
226 
227         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
228                 + "<!DOCTYPE suppressions PUBLIC" + EOL
229                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
230                 + "//EN\"" + EOL
231                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
232                 + EOL
233                 + "<suppressions>" + EOL
234                 + "  <suppress-xpath" + EOL
235                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
236                 + "       id=\"JavadocModuleId\"" + EOL
237                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
238                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
239                 + "/OBJBLOCK/VARIABLE_DEF[./IDENT[@text='isValid']]\"/>" + EOL
240                 + "</suppressions>" + EOL;
241 
242         verifyOutput(expected, event1, event2, event3);
243     }
244 
245     @Test
246     public void testFileNameWithXmlSpecialCharacter() {
247         final AuditEvent event = createAuditEvent("Input&XpathFileGenerator.java",
248                 FIRST_MESSAGE);
249 
250         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
251                 + "<!DOCTYPE suppressions PUBLIC" + EOL
252                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
253                 + "//EN\"" + EOL
254                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
255                 + EOL
256                 + "<suppressions>" + EOL
257                 + "  <suppress-xpath" + EOL
258                 + "       files=\"Input&amp;XpathFileGenerator.java\"" + EOL
259                 + "       checks=\"LeftCurlyCheck\""
260                 + EOL
261                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
262                 + "[@text='InputXpathFileGeneratorAuditListener']]"
263                 + "/OBJBLOCK/LCURLY\"/>" + EOL
264                 + "</suppressions>" + EOL;
265 
266         verifyOutput(expected, event);
267     }
268 
269     @Test
270     public void testCloseStream() {
271         final XpathFileGeneratorAuditListener listener =
272                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.CLOSE);
273         listener.finishLocalSetup();
274         listener.auditStarted(null);
275         listener.auditFinished(null);
276 
277         assertWithMessage("Invalid close count")
278             .that(outStream.getCloseCount())
279             .isEqualTo(1);
280     }
281 
282     @Test
283     public void testNoCloseStream() {
284         final XpathFileGeneratorAuditListener listener =
285                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.NONE);
286         listener.finishLocalSetup();
287         listener.auditStarted(null);
288         listener.auditFinished(null);
289 
290         assertWithMessage("Invalid close count")
291             .that(outStream.getCloseCount())
292             .isEqualTo(0);
293     }
294 
295     @Test
296     public void testNullOutputStreamOptions() {
297         final OutputStream out = new ByteArrayOutputStream();
298         final IllegalArgumentException exception =
299             getExpectedThrowable(IllegalArgumentException.class,
300                 () -> new XpathFileGeneratorAuditListener(out, null));
301         assertWithMessage("Invalid error message")
302                 .that(exception.getMessage())
303                 .isEqualTo("Parameter outputStreamOptions can not be null");
304     }
305 
306     private AuditEvent createAuditEvent(String fileName, Violation violation) {
307         return new AuditEvent(this,
308                 getPath(fileName), violation);
309     }
310 
311     private AuditEvent createAuditEvent(String fileName, int lineNumber, int columnNumber,
312                                         Class<?> sourceClass) {
313         final Violation violation =
314                 new Violation(lineNumber, columnNumber, "messages.properties", null,
315                         null, null, sourceClass, null);
316 
317         return new AuditEvent(this,
318                 getPath(fileName), violation);
319     }
320 
321     private static Violation createViolation(int lineNumber,
322                                                                    int columnNumber, int tokenType,
323                                                                    String moduleId,
324                                                                    Class<?> sourceClass) {
325         return new Violation(lineNumber, columnNumber, tokenType,
326                 "messages.properties", null, null,
327                 SeverityLevel.ERROR, moduleId, sourceClass, null);
328     }
329 
330     private static TreeWalkerAuditEvent createTreeWalkerAuditEvent(String fileName,
331                                                                    Violation violation)
332             throws Exception {
333         final File file = new File(getPath(fileName));
334         final FileText fileText = new FileText(
335                 file.getAbsoluteFile(),
336                 System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
337         final FileContents fileContents = new FileContents(fileText);
338         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
339 
340         return new TreeWalkerAuditEvent(fileContents, fileName,
341                 violation, rootAst);
342     }
343 
344     private static String getPath(String filename) {
345         return "src/test/resources/com/puppycrawl/tools/checkstyle/xpathfilegeneratorauditlistener/"
346                 + filename;
347     }
348 
349     private static void verifyOutput(String expected, AuditEvent... events) {
350         final TestByteArrayOutputStream out = new TestByteArrayOutputStream();
351 
352         final XpathFileGeneratorAuditListener listener =
353                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
354 
355         for (AuditEvent event : events) {
356             listener.addError(event);
357         }
358 
359         listener.auditFinished(null);
360 
361         assertWithMessage("Output stream flush count")
362                 .that(out.flushCount)
363                 .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
364         assertWithMessage("Output stream close count")
365                 .that(out.closeCount)
366                 .isEqualTo(1);
367 
368         final String actual = out.toString(StandardCharsets.UTF_8);
369         assertWithMessage("Invalid suppressions file content")
370             .that(actual)
371             .isEqualTo(expected);
372     }
373 
374     private static final class TestByteArrayOutputStream extends ByteArrayOutputStream {
375 
376         private int closeCount;
377         private int flushCount;
378 
379         @Override
380         public void close() {
381             closeCount++;
382         }
383 
384         @Override
385         public void flush() {
386             flushCount++;
387         }
388 
389     }
390 
391 }