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 ExceptionInInitializerError(exc);
73          }
74      }
75  
76      private static void constructEvents() throws Exception {
77          final TreeWalkerAuditEvent event1 = createTreeWalkerAuditEvent(
78                  "InputXpathFileGeneratorAuditListener.java", FIRST_MESSAGE);
79  
80          final TreeWalkerAuditEvent event2 = createTreeWalkerAuditEvent(
81                  "InputXpathFileGeneratorAuditListener.java", SECOND_MESSAGE);
82  
83          final TreeWalkerAuditEvent event3 = createTreeWalkerAuditEvent(
84                  "InputXpathFileGeneratorAuditListener.java", THIRD_MESSAGE);
85  
86          final TreeWalkerAuditEvent event4 = createTreeWalkerAuditEvent(
87                  "InputXpathFileGeneratorAuditListener.java", FOURTH_MESSAGE);
88  
89          final XpathFileGeneratorAstFilter astFilter = new XpathFileGeneratorAstFilter();
90          astFilter.accept(event1);
91          astFilter.accept(event2);
92          astFilter.accept(event3);
93          astFilter.accept(event4);
94      }
95  
96      @Test
97      public void testFinishLocalSetup() {
98          final OutputStream out = new ByteArrayOutputStream();
99          final XpathFileGeneratorAuditListener listener =
100                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
101 
102         listener.finishLocalSetup();
103         listener.auditStarted(null);
104         listener.auditFinished(null);
105         final String actual = out.toString();
106         assertWithMessage("Output should be empty")
107             .that(actual)
108             .isEmpty();
109     }
110 
111     @Test
112     public void testFileStarted() {
113         final OutputStream out = new ByteArrayOutputStream();
114         final XpathFileGeneratorAuditListener listener =
115                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
116         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
117         listener.fileStarted(ev);
118         listener.auditFinished(null);
119         final String actual = out.toString();
120         assertWithMessage("Output should be empty")
121             .that(actual)
122             .isEmpty();
123     }
124 
125     @Test
126     public void testFileFinished() {
127         final OutputStream out = new ByteArrayOutputStream();
128         final XpathFileGeneratorAuditListener listener =
129                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
130         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
131         listener.fileFinished(ev);
132         listener.auditFinished(null);
133         final String actual = out.toString();
134         assertWithMessage("Output should be empty")
135             .that(actual)
136             .isEmpty();
137     }
138 
139     @Test
140     public void testAddException() {
141         final OutputStream out = new ByteArrayOutputStream();
142         final XpathFileGeneratorAuditListener logger =
143                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
144         logger.auditStarted(null);
145         final Violation violation =
146                 new Violation(1, 1,
147                         "messages.properties", null, null, null, getClass(), null);
148         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
149 
150         final UnsupportedOperationException exception =
151             getExpectedThrowable(UnsupportedOperationException.class,
152                 () -> logger.addException(ev, null));
153         assertWithMessage("Invalid exception message")
154             .that(exception.getMessage())
155             .isEqualTo("Operation is not supported");
156     }
157 
158     @Test
159     public void testCorrectOne() {
160         final AuditEvent event = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
161                 FIRST_MESSAGE);
162 
163         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
164                 + "<!DOCTYPE suppressions PUBLIC" + EOL
165                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
166                 + "//EN\"" + EOL
167                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
168                 + EOL
169                 + "<suppressions>" + EOL
170                 + "  <suppress-xpath" + EOL
171                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
172                 + "       checks=\"LeftCurlyCheck\""
173                 + EOL
174                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
175                 + "[@text='InputXpathFileGeneratorAuditListener']]"
176                 + "/OBJBLOCK/LCURLY\"/>" + EOL
177                 + "</suppressions>" + EOL;
178 
179         verifyOutput(expected, event);
180     }
181 
182     @Test
183     public void testCorrectTwo() {
184         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
185                 SECOND_MESSAGE);
186 
187         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
188                 THIRD_MESSAGE);
189 
190         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
191                 + "<!DOCTYPE suppressions PUBLIC" + EOL
192                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
193                 + "//EN\"" + EOL
194                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
195                 + EOL
196                 + "<suppressions>" + EOL
197                 + "  <suppress-xpath" + EOL
198                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
199                 + "       id=\"MyModule\"" + EOL
200                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
201                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
202                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]\"/>" + EOL
203                 + "  <suppress-xpath" + EOL
204                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
205                 + "       checks=\"NestedForDepthCheck\"" + EOL
206                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
207                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
208                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]/SLIST/LITERAL_FOR/SLIST"
209                 + "/LITERAL_FOR\"/>" + EOL
210                 + "</suppressions>" + EOL;
211 
212         verifyOutput(expected, event1, event2);
213     }
214 
215     @Test
216     public void testOnlyOneMatching() {
217         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
218                 10, 5, MethodParamPadCheck.class);
219 
220         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
221                 5, 5, JavadocVariableCheck.class);
222 
223         final AuditEvent event3 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
224                 FOURTH_MESSAGE);
225 
226         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
227                 + "<!DOCTYPE suppressions PUBLIC" + EOL
228                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
229                 + "//EN\"" + EOL
230                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
231                 + EOL
232                 + "<suppressions>" + EOL
233                 + "  <suppress-xpath" + EOL
234                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
235                 + "       id=\"JavadocModuleId\"" + EOL
236                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
237                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
238                 + "/OBJBLOCK/VARIABLE_DEF[./IDENT[@text='isValid']]\"/>" + EOL
239                 + "</suppressions>" + EOL;
240 
241         verifyOutput(expected, event1, event2, event3);
242     }
243 
244     @Test
245     public void testFileNameWithXmlSpecialCharacter() {
246         final AuditEvent event = createAuditEvent("Input&XpathFileGenerator.java",
247                 FIRST_MESSAGE);
248 
249         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
250                 + "<!DOCTYPE suppressions PUBLIC" + EOL
251                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2"
252                 + "//EN\"" + EOL
253                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath.dtd\">"
254                 + EOL
255                 + "<suppressions>" + EOL
256                 + "  <suppress-xpath" + EOL
257                 + "       files=\"Input&amp;XpathFileGenerator.java\"" + EOL
258                 + "       checks=\"LeftCurlyCheck\""
259                 + EOL
260                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
261                 + "[@text='InputXpathFileGeneratorAuditListener']]"
262                 + "/OBJBLOCK/LCURLY\"/>" + EOL
263                 + "</suppressions>" + EOL;
264 
265         verifyOutput(expected, event);
266     }
267 
268     @Test
269     public void testCloseStream() {
270         final XpathFileGeneratorAuditListener listener =
271                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.CLOSE);
272         listener.finishLocalSetup();
273         listener.auditStarted(null);
274         listener.auditFinished(null);
275 
276         assertWithMessage("Invalid close count")
277             .that(outStream.getCloseCount())
278             .isEqualTo(1);
279     }
280 
281     @Test
282     public void testNoCloseStream() {
283         final XpathFileGeneratorAuditListener listener =
284                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.NONE);
285         listener.finishLocalSetup();
286         listener.auditStarted(null);
287         listener.auditFinished(null);
288 
289         assertWithMessage("Invalid close count")
290             .that(outStream.getCloseCount())
291             .isEqualTo(0);
292     }
293 
294     @Test
295     public void testNullOutputStreamOptions() {
296         final OutputStream out = new ByteArrayOutputStream();
297         final IllegalArgumentException exception =
298             getExpectedThrowable(IllegalArgumentException.class,
299                 () -> new XpathFileGeneratorAuditListener(out, null));
300         assertWithMessage("Invalid error message")
301                 .that(exception.getMessage())
302                 .isEqualTo("Parameter outputStreamOptions can not be null");
303     }
304 
305     private AuditEvent createAuditEvent(String fileName, Violation violation) {
306         return new AuditEvent(this,
307                 getPath(fileName), violation);
308     }
309 
310     private AuditEvent createAuditEvent(String fileName, int lineNumber, int columnNumber,
311                                         Class<?> sourceClass) {
312         final Violation violation =
313                 new Violation(lineNumber, columnNumber, "messages.properties", null,
314                         null, null, sourceClass, null);
315 
316         return new AuditEvent(this,
317                 getPath(fileName), violation);
318     }
319 
320     private static Violation createViolation(int lineNumber,
321                                                                    int columnNumber, int tokenType,
322                                                                    String moduleId,
323                                                                    Class<?> sourceClass) {
324         return new Violation(lineNumber, columnNumber, tokenType,
325                 "messages.properties", null, null,
326                 SeverityLevel.ERROR, moduleId, sourceClass, null);
327     }
328 
329     private static TreeWalkerAuditEvent createTreeWalkerAuditEvent(String fileName,
330                                                                    Violation violation)
331             throws Exception {
332         final File file = new File(getPath(fileName));
333         final FileText fileText = new FileText(
334                 file.getAbsoluteFile(),
335                 System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
336         final FileContents fileContents = new FileContents(fileText);
337         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
338 
339         return new TreeWalkerAuditEvent(fileContents, fileName,
340                 violation, rootAst);
341     }
342 
343     private static String getPath(String filename) {
344         return "src/test/resources/com/puppycrawl/tools/checkstyle/xpathfilegeneratorauditlistener/"
345                 + filename;
346     }
347 
348     private static void verifyOutput(String expected, AuditEvent... events) {
349         final TestByteArrayOutputStream out = new TestByteArrayOutputStream();
350 
351         final XpathFileGeneratorAuditListener listener =
352                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
353 
354         for (AuditEvent event : events) {
355             listener.addError(event);
356         }
357 
358         listener.auditFinished(null);
359 
360         assertWithMessage("Output stream flush count")
361                 .that(out.flushCount)
362                 .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
363         assertWithMessage("Output stream close count")
364                 .that(out.closeCount)
365                 .isEqualTo(1);
366 
367         final String actual = out.toString(StandardCharsets.UTF_8);
368         assertWithMessage("Invalid suppressions file content")
369             .that(actual)
370             .isEqualTo(expected);
371     }
372 
373     private static final class TestByteArrayOutputStream extends ByteArrayOutputStream {
374 
375         private int closeCount;
376         private int flushCount;
377 
378         @Override
379         public void close() {
380             closeCount++;
381         }
382 
383         @Override
384         public void flush() {
385             flushCount++;
386         }
387 
388     }
389 }