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 testCloseStream() {
246         final XpathFileGeneratorAuditListener listener =
247                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.CLOSE);
248         listener.finishLocalSetup();
249         listener.auditStarted(null);
250         listener.auditFinished(null);
251 
252         assertWithMessage("Invalid close count")
253             .that(outStream.getCloseCount())
254             .isEqualTo(1);
255     }
256 
257     @Test
258     public void testNoCloseStream() {
259         final XpathFileGeneratorAuditListener listener =
260                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.NONE);
261         listener.finishLocalSetup();
262         listener.auditStarted(null);
263         listener.auditFinished(null);
264 
265         assertWithMessage("Invalid close count")
266             .that(outStream.getCloseCount())
267             .isEqualTo(0);
268     }
269 
270     @Test
271     public void testNullOutputStreamOptions() {
272         final OutputStream out = new ByteArrayOutputStream();
273         final IllegalArgumentException exception =
274             getExpectedThrowable(IllegalArgumentException.class,
275                 () -> new XpathFileGeneratorAuditListener(out, null));
276         assertWithMessage("Invalid error message")
277                 .that(exception.getMessage())
278                 .isEqualTo("Parameter outputStreamOptions can not be null");
279     }
280 
281     private AuditEvent createAuditEvent(String fileName, int lineNumber, int columnNumber,
282                                         Class<?> sourceClass) {
283         final Violation violation =
284                 new Violation(lineNumber, columnNumber, "messages.properties", null,
285                         null, null, sourceClass, null);
286 
287         return new AuditEvent(this,
288                 getPath(fileName), violation);
289     }
290 
291     private AuditEvent createAuditEvent(String fileName, Violation violation) {
292         return new AuditEvent(this,
293                 getPath(fileName), violation);
294     }
295 
296     private static Violation createViolation(int lineNumber,
297                                                                    int columnNumber, int tokenType,
298                                                                    String moduleId,
299                                                                    Class<?> sourceClass) {
300         return new Violation(lineNumber, columnNumber, tokenType,
301                 "messages.properties", null, null,
302                 SeverityLevel.ERROR, moduleId, sourceClass, null);
303     }
304 
305     private static TreeWalkerAuditEvent createTreeWalkerAuditEvent(String fileName,
306                                                                    Violation violation)
307             throws Exception {
308         final File file = new File(getPath(fileName));
309         final FileText fileText = new FileText(
310                 file.getAbsoluteFile(),
311                 System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
312         final FileContents fileContents = new FileContents(fileText);
313         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
314 
315         return new TreeWalkerAuditEvent(fileContents, fileName,
316                 violation, rootAst);
317     }
318 
319     private static String getPath(String filename) {
320         return "src/test/resources/com/puppycrawl/tools/checkstyle/xpathfilegeneratorauditlistener/"
321                 + filename;
322     }
323 
324     private static void verifyOutput(String expected, AuditEvent... events) {
325         final TestByteArrayOutputStream out = new TestByteArrayOutputStream();
326 
327         final XpathFileGeneratorAuditListener listener =
328                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
329 
330         for (AuditEvent event : events) {
331             listener.addError(event);
332         }
333 
334         listener.auditFinished(null);
335 
336         assertWithMessage("Output stream flush count")
337                 .that(out.flushCount)
338                 .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
339         assertWithMessage("Output stream close count")
340                 .that(out.closeCount)
341                 .isEqualTo(1);
342 
343         final String actual = out.toString(StandardCharsets.UTF_8);
344         assertWithMessage("Invalid suppressions file content")
345             .that(actual)
346             .isEqualTo(expected);
347     }
348 
349     private static final class TestByteArrayOutputStream extends ByteArrayOutputStream {
350 
351         private int closeCount;
352         private int flushCount;
353 
354         @Override
355         public void close() {
356             closeCount++;
357         }
358 
359         @Override
360         public void flush() {
361             flushCount++;
362         }
363 
364     }
365 }