View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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  
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.OutputStream;
27  import java.nio.charset.StandardCharsets;
28  
29  import org.junit.jupiter.api.Test;
30  
31  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
32  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
33  import com.puppycrawl.tools.checkstyle.api.DetailAST;
34  import com.puppycrawl.tools.checkstyle.api.FileContents;
35  import com.puppycrawl.tools.checkstyle.api.FileText;
36  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.api.Violation;
39  import com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck;
40  import com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck;
41  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck;
42  import com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck;
43  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
44  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
45  
46  public class XpathFileGeneratorAuditListenerTest {
47  
48      /** OS specific line separator. */
49      private static final String EOL = System.getProperty("line.separator");
50  
51      private static final Violation FIRST_MESSAGE = createViolation(3, 51,
52              TokenTypes.LCURLY, null, LeftCurlyCheck.class);
53  
54      private static final Violation SECOND_MESSAGE = createViolation(15, 5,
55              TokenTypes.METHOD_DEF, "MyModule", MethodParamPadCheck.class);
56  
57      private static final Violation THIRD_MESSAGE = createViolation(17, 13,
58              TokenTypes.LITERAL_FOR, null, NestedForDepthCheck.class);
59  
60      private static final Violation FOURTH_MESSAGE = createViolation(5, 5,
61              TokenTypes.VARIABLE_DEF, "JavadocModuleId", JavadocVariableCheck.class);
62  
63      private final CloseAndFlushTestByteArrayOutputStream outStream =
64              new CloseAndFlushTestByteArrayOutputStream();
65  
66      static {
67          try {
68              constructEvents();
69          }
70          catch (Exception ex) {
71              throw new ExceptionInInitializerError(ex);
72          }
73      }
74  
75      private static void constructEvents() throws Exception {
76          final TreeWalkerAuditEvent event1 = createTreeWalkerAuditEvent(
77                  "InputXpathFileGeneratorAuditListener.java", FIRST_MESSAGE);
78  
79          final TreeWalkerAuditEvent event2 = createTreeWalkerAuditEvent(
80                  "InputXpathFileGeneratorAuditListener.java", SECOND_MESSAGE);
81  
82          final TreeWalkerAuditEvent event3 = createTreeWalkerAuditEvent(
83                  "InputXpathFileGeneratorAuditListener.java", THIRD_MESSAGE);
84  
85          final TreeWalkerAuditEvent event4 = createTreeWalkerAuditEvent(
86                  "InputXpathFileGeneratorAuditListener.java", FOURTH_MESSAGE);
87  
88          final XpathFileGeneratorAstFilter astFilter = new XpathFileGeneratorAstFilter();
89          astFilter.accept(event1);
90          astFilter.accept(event2);
91          astFilter.accept(event3);
92          astFilter.accept(event4);
93      }
94  
95      @Test
96      public void testFinishLocalSetup() {
97          final OutputStream out = new ByteArrayOutputStream();
98          final XpathFileGeneratorAuditListener listener =
99                  new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
100 
101         listener.finishLocalSetup();
102         listener.auditStarted(null);
103         listener.auditFinished(null);
104         final String actual = out.toString();
105         assertWithMessage("Output should be empty")
106             .that(actual)
107             .isEmpty();
108     }
109 
110     @Test
111     public void testFileStarted() {
112         final OutputStream out = new ByteArrayOutputStream();
113         final XpathFileGeneratorAuditListener listener =
114                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
115         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
116         listener.fileStarted(ev);
117         listener.auditFinished(null);
118         final String actual = out.toString();
119         assertWithMessage("Output should be empty")
120             .that(actual)
121             .isEmpty();
122     }
123 
124     @Test
125     public void testFileFinished() {
126         final OutputStream out = new ByteArrayOutputStream();
127         final XpathFileGeneratorAuditListener listener =
128                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
129         final AuditEvent ev = new AuditEvent(this, "Test.java", null);
130         listener.fileFinished(ev);
131         listener.auditFinished(null);
132         final String actual = out.toString();
133         assertWithMessage("Output should be empty")
134             .that(actual)
135             .isEmpty();
136     }
137 
138     @Test
139     public void testAddException() {
140         final OutputStream out = new ByteArrayOutputStream();
141         final XpathFileGeneratorAuditListener logger =
142                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
143         logger.auditStarted(null);
144         final Violation violation =
145                 new Violation(1, 1,
146                         "messages.properties", null, null, null, getClass(), null);
147         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
148 
149         try {
150             logger.addException(ev, null);
151             assertWithMessage("Exception is excepted").fail();
152         }
153         catch (UnsupportedOperationException ex) {
154             assertWithMessage("Invalid exception message")
155                 .that(ex.getMessage())
156                 .isEqualTo("Operation is not supported");
157         }
158     }
159 
160     @Test
161     public void testCorrectOne() {
162         final AuditEvent event = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
163                 FIRST_MESSAGE);
164 
165         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
166                 + "<!DOCTYPE suppressions PUBLIC" + EOL
167                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
168                 + "//EN\"" + EOL
169                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
170                 + EOL
171                 + "<suppressions>" + EOL
172                 + "<suppress-xpath" + EOL
173                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
174                 + "       checks=\"LeftCurlyCheck\""
175                 + EOL
176                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
177                 + "[@text='InputXpathFileGeneratorAuditListener']]"
178                 + "/OBJBLOCK/LCURLY\"/>" + EOL
179                 + "</suppressions>" + EOL;
180 
181         verifyOutput(expected, event);
182     }
183 
184     @Test
185     public void testCorrectTwo() {
186         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
187                 SECOND_MESSAGE);
188 
189         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
190                 THIRD_MESSAGE);
191 
192         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
193                 + "<!DOCTYPE suppressions PUBLIC" + EOL
194                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
195                 + "//EN\"" + EOL
196                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
197                 + EOL
198                 + "<suppressions>" + EOL
199                 + "<suppress-xpath" + EOL
200                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
201                 + "       id=\"MyModule\"" + EOL
202                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
203                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
204                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]\"/>" + EOL
205                 + "<suppress-xpath" + EOL
206                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
207                 + "       checks=\"NestedForDepthCheck\"" + EOL
208                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
209                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
210                 + "/OBJBLOCK/METHOD_DEF[./IDENT[@text='sort']]/SLIST/LITERAL_FOR/SLIST"
211                 + "/LITERAL_FOR\"/>" + EOL
212                 + "</suppressions>" + EOL;
213 
214         verifyOutput(expected, event1, event2);
215     }
216 
217     @Test
218     public void testOnlyOneMatching() {
219         final AuditEvent event1 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
220                 10, 5, MethodParamPadCheck.class);
221 
222         final AuditEvent event2 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
223                 5, 5, JavadocVariableCheck.class);
224 
225         final AuditEvent event3 = createAuditEvent("InputXpathFileGeneratorAuditListener.java",
226                 FOURTH_MESSAGE);
227 
228         final String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + EOL
229                 + "<!DOCTYPE suppressions PUBLIC" + EOL
230                 + "    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2"
231                 + "//EN\"" + EOL
232                 + "    \"https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd\">"
233                 + EOL
234                 + "<suppressions>" + EOL
235                 + "<suppress-xpath" + EOL
236                 + "       files=\"InputXpathFileGeneratorAuditListener.java\"" + EOL
237                 + "       id=\"JavadocModuleId\"" + EOL
238                 + "       query=\"/COMPILATION_UNIT/CLASS_DEF"
239                 + "[./IDENT[@text='InputXpathFileGeneratorAuditListener']]"
240                 + "/OBJBLOCK/VARIABLE_DEF[./IDENT[@text='isValid']]\"/>" + EOL
241                 + "</suppressions>" + EOL;
242 
243         verifyOutput(expected, event1, event2, event3);
244     }
245 
246     @Test
247     public void testCloseStream() {
248         final XpathFileGeneratorAuditListener listener =
249                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.CLOSE);
250         listener.finishLocalSetup();
251         listener.auditStarted(null);
252         listener.auditFinished(null);
253 
254         assertWithMessage("Invalid close count")
255             .that(outStream.getCloseCount())
256             .isEqualTo(1);
257     }
258 
259     @Test
260     public void testNoCloseStream() {
261         final XpathFileGeneratorAuditListener listener =
262                 new XpathFileGeneratorAuditListener(outStream, OutputStreamOptions.NONE);
263         listener.finishLocalSetup();
264         listener.auditStarted(null);
265         listener.auditFinished(null);
266 
267         assertWithMessage("Invalid close count")
268             .that(outStream.getCloseCount())
269             .isEqualTo(0);
270     }
271 
272     private AuditEvent createAuditEvent(String fileName, int lineNumber, int columnNumber,
273                                         Class<?> sourceClass) {
274         final Violation violation =
275                 new Violation(lineNumber, columnNumber, "messages.properties", null,
276                         null, null, sourceClass, null);
277 
278         return new AuditEvent(this,
279                 getPath(fileName), violation);
280     }
281 
282     private AuditEvent createAuditEvent(String fileName, Violation violation) {
283         return new AuditEvent(this,
284                 getPath(fileName), violation);
285     }
286 
287     private static Violation createViolation(int lineNumber,
288                                                                    int columnNumber, int tokenType,
289                                                                    String moduleId,
290                                                                    Class<?> sourceClass) {
291         return new Violation(lineNumber, columnNumber, tokenType,
292                 "messages.properties", null, null,
293                 SeverityLevel.ERROR, moduleId, sourceClass, null);
294     }
295 
296     private static TreeWalkerAuditEvent createTreeWalkerAuditEvent(String fileName,
297                                                                    Violation violation)
298             throws Exception {
299         final File file = new File(getPath(fileName));
300         final FileText fileText = new FileText(
301                 file.getAbsoluteFile(),
302                 System.getProperty("file.encoding", StandardCharsets.UTF_8.name()));
303         final FileContents fileContents = new FileContents(fileText);
304         final DetailAST rootAst = JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS);
305 
306         return new TreeWalkerAuditEvent(fileContents, fileName,
307                 violation, rootAst);
308     }
309 
310     private static String getPath(String filename) {
311         return "src/test/resources/com/puppycrawl/tools/checkstyle/xpathfilegeneratorauditlistener/"
312                 + filename;
313     }
314 
315     private static void verifyOutput(String expected, AuditEvent... events) {
316         final TestByteArrayOutputStream out = new TestByteArrayOutputStream();
317 
318         final XpathFileGeneratorAuditListener listener =
319                 new XpathFileGeneratorAuditListener(out, OutputStreamOptions.CLOSE);
320 
321         for (AuditEvent event : events) {
322             listener.addError(event);
323         }
324 
325         listener.auditFinished(null);
326 
327         assertWithMessage("Output stream flush count")
328                 .that(out.flushCount)
329                 .isEqualTo(TestUtil.adjustFlushCountForOutputStreamClose(1));
330         assertWithMessage("Output stream close count")
331                 .that(out.closeCount)
332                 .isEqualTo(1);
333 
334         final String actual = out.toString();
335         assertWithMessage("Invalid suppressions file content")
336             .that(actual)
337             .isEqualTo(expected);
338     }
339 
340     private static final class TestByteArrayOutputStream extends ByteArrayOutputStream {
341 
342         private int closeCount;
343         private int flushCount;
344 
345         @Override
346         public void close() {
347             closeCount++;
348         }
349 
350         @Override
351         public void flush() {
352             flushCount++;
353         }
354 
355     }
356 }