View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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.IOException;
25  import java.io.PrintWriter;
26  import java.io.Serial;
27  import java.util.List;
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.AutomaticBean;
34  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
35  import com.puppycrawl.tools.checkstyle.api.Violation;
36  import com.puppycrawl.tools.checkstyle.internal.utils.CloseAndFlushTestByteArrayOutputStream;
37  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
38  
39  /**
40   * Enter a description of class XMLLoggerTest.java.
41   */
42  // -@cs[AbbreviationAsWordInName] Test should be named as its main class.
43  public class XMLLoggerTest extends AbstractXmlTestSupport {
44  
45      /**
46       * Output stream to hold the test results. The IntelliJ IDEA issues the AutoCloseableResource
47       * warning here, so it needs to be suppressed. The {@code ByteArrayOutputStream} does not hold
48       * any resources that need to be released.
49       */
50      private final CloseAndFlushTestByteArrayOutputStream outStream =
51          new CloseAndFlushTestByteArrayOutputStream();
52  
53      @Override
54      protected String getPackageLocation() {
55          return "com/puppycrawl/tools/checkstyle/xmllogger";
56      }
57  
58      @Test
59      public void testEncode()
60              throws IOException {
61          final XMLLogger test = new XMLLogger(outStream, OutputStreamOptions.NONE);
62          assertWithMessage("should be able to create XMLLogger without issue")
63              .that(test)
64              .isNotNull();
65          final String[][] encodings = {
66              {"<", "&lt;"},
67              {">", "&gt;"},
68              {"'", "&apos;"},
69              {"\"", "&quot;"},
70              {"&", "&amp;"},
71              {"&lt;", "&amp;lt;"},
72              {"abc;", "abc;"},
73              {"&#0;", "&amp;#0;"},
74              {"&#0", "&amp;#0"},
75              {"&#X0;", "&amp;#X0;"},
76              {"\u0001", "#x1;"},
77              {"\u0080", "#x80;"},
78          };
79          for (String[] encoding : encodings) {
80              final String encoded = XMLLogger.encode(encoding[0]);
81              assertWithMessage("\"" + encoding[0] + "\"")
82                  .that(encoded)
83                  .isEqualTo(encoding[1]);
84          }
85          outStream.close();
86      }
87  
88      @Test
89      public void testIsReference()
90              throws IOException {
91          final XMLLogger test = new XMLLogger(outStream, OutputStreamOptions.NONE);
92          assertWithMessage("should be able to create XMLLogger without issue")
93              .that(test)
94              .isNotNull();
95          final String[] references = {
96              "&#0;",
97              "&#x0;",
98              "&lt;",
99              "&gt;",
100             "&apos;",
101             "&quot;",
102             "&amp;",
103         };
104         for (String reference : references) {
105             assertWithMessage("reference: " + reference)
106                     .that(XMLLogger.isReference(reference))
107                     .isTrue();
108         }
109         final String[] noReferences = {
110             "&",
111             "&;",
112             "&#;",
113             "&#a;",
114             "&#X0;",
115             "&#x;",
116             "&#xg;",
117             "ramp;",
118             "ref",
119         };
120         for (String noReference : noReferences) {
121             assertWithMessage("no reference: " + noReference)
122                     .that(XMLLogger.isReference(noReference))
123                     .isFalse();
124         }
125 
126         outStream.close();
127     }
128 
129     @Test
130     public void testCloseStream()
131             throws Exception {
132         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
133         logger.auditStarted(null);
134         logger.auditFinished(null);
135 
136         assertWithMessage("Invalid close count")
137             .that(outStream.getCloseCount())
138             .isEqualTo(1);
139 
140         verifyXml(getPath("ExpectedXMLLoggerEmpty.xml"), outStream);
141     }
142 
143     @Test
144     public void testNoCloseStream()
145             throws Exception {
146         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.NONE);
147         logger.auditStarted(null);
148         logger.auditFinished(null);
149 
150         assertWithMessage("Invalid close count")
151             .that(outStream.getCloseCount())
152             .isEqualTo(0);
153 
154         outStream.close();
155         verifyXml(getPath("ExpectedXMLLoggerEmpty.xml"), outStream);
156     }
157 
158     @Test
159     public void testFileStarted()
160             throws Exception {
161         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
162         logger.auditStarted(null);
163         final AuditEvent ev = new AuditEvent(this, "Test.java");
164         logger.fileStarted(ev);
165         logger.fileFinished(ev);
166         logger.auditFinished(null);
167         verifyXml(getPath("ExpectedXMLLogger.xml"), outStream);
168     }
169 
170     @Test
171     public void testFileFinished()
172             throws Exception {
173         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
174         logger.auditStarted(null);
175         final AuditEvent ev = new AuditEvent(this, "Test.java");
176         logger.fileFinished(ev);
177         logger.auditFinished(null);
178         verifyXml(getPath("ExpectedXMLLogger.xml"), outStream);
179     }
180 
181     @Test
182     public void testAddError() throws Exception {
183         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerAddError.java",
184                 "ExpectedXMLLoggerAddError.xml");
185     }
186 
187     @Test
188     public void testAddErrorWithNullFileName() throws Exception {
189         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
190         logger.auditStarted(null);
191         final Violation violation =
192                 new Violation(1, 1,
193                         "messages.properties", "key", null, SeverityLevel.ERROR, null,
194                         getClass(), null);
195         final AuditEvent ev = new AuditEvent(this, null, violation);
196         logger.addError(ev);
197         logger.auditFinished(null);
198         verifyXml(getPath("ExpectedXMLLoggerErrorNullFileName.xml"), outStream,
199                 violation.getViolation());
200     }
201 
202     @Test
203     public void testAddErrorModuleId() throws Exception {
204         final String inputFile = "InputXMLLoggerErrorModuleId.java";
205         final String expectedXmlReport = "ExpectedXMLLoggerErrorModuleId.xml";
206         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
207     }
208 
209     @Test
210     public void testAddErrorWithEncodedMessage() throws Exception {
211         final String inputFileWithConfig = "InputXMLLoggerEncodedMessage.java";
212         final String expectedXmlReport = "ExpectedXMLLoggerEncodedMessage.xml";
213         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, expectedXmlReport);
214     }
215 
216     @Test
217     public void testAddErrorOnZeroColumns() throws Exception {
218         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerErrorOnZeroColumn.java",
219                 "ExpectedXMLLoggerErrorZeroColumn.xml");
220     }
221 
222     @Test
223     public void testAddIgnored() throws Exception {
224         final String inputFile = "InputXMLLoggerIgnored.java";
225         final String expectedXmlReport = "ExpectedXMLLoggerIgnored.xml";
226         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
227     }
228 
229     @Test
230     public void testAddException()
231             throws Exception {
232         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
233         logger.auditStarted(null);
234         final Violation violation =
235             new Violation(1, 1,
236                 "messages.properties", null, null, null, getClass(), null);
237         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
238         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
239         logger.auditFinished(null);
240         verifyXml(getPath("ExpectedXMLLoggerException.xml"), outStream);
241         assertWithMessage("Invalid close count")
242             .that(outStream.getCloseCount())
243             .isEqualTo(1);
244     }
245 
246     @Test
247     public void testAddExceptionWithNullFileName()
248             throws Exception {
249         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
250         logger.auditStarted(null);
251         final Violation violation =
252                 new Violation(1, 1,
253                         "messages.properties", null, null, null, getClass(), null);
254         final AuditEvent ev = new AuditEvent(this, null, violation);
255         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
256         logger.auditFinished(null);
257         verifyXml(getPath("ExpectedXMLLoggerExceptionNullFileName.xml"), outStream);
258         assertWithMessage("Invalid close count")
259             .that(outStream.getCloseCount())
260             .isEqualTo(1);
261     }
262 
263     @Test
264     public void testAddExceptionAfterFileStarted()
265             throws Exception {
266         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
267         logger.auditStarted(null);
268 
269         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
270         logger.fileStarted(fileStartedEvent);
271 
272         final Violation violation =
273                 new Violation(1, 1,
274                         "messages.properties", null, null, null, getClass(), null);
275         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
276         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
277 
278         logger.fileFinished(ev);
279         logger.auditFinished(null);
280         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
281         assertWithMessage("Invalid close count")
282             .that(outStream.getCloseCount())
283             .isEqualTo(1);
284     }
285 
286     @Test
287     public void testAddExceptionBeforeFileFinished()
288             throws Exception {
289         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
290         logger.auditStarted(null);
291         final Violation violation =
292                 new Violation(1, 1,
293                         "messages.properties", null, null, null, getClass(), null);
294         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
295         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
296         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
297         logger.fileFinished(fileFinishedEvent);
298         logger.auditFinished(null);
299         verifyXml(getPath("ExpectedXMLLoggerException3.xml"), outStream);
300         assertWithMessage("Invalid close count")
301             .that(outStream.getCloseCount())
302             .isEqualTo(1);
303     }
304 
305     @Test
306     public void testAddExceptionBetweenFileStartedAndFinished()
307             throws Exception {
308         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
309         logger.auditStarted(null);
310         final Violation violation =
311                 new Violation(1, 1,
312                         "messages.properties", null, null, null, getClass(), null);
313         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
314         logger.fileStarted(fileStartedEvent);
315         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
316         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
317         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
318         logger.fileFinished(fileFinishedEvent);
319         logger.auditFinished(null);
320         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
321         assertWithMessage("Invalid close count")
322             .that(outStream.getCloseCount())
323             .isEqualTo(1);
324     }
325 
326     @Test
327     public void testAuditFinishedWithoutFileFinished() throws Exception {
328         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
329         logger.auditStarted(null);
330         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
331         logger.fileStarted(fileStartedEvent);
332 
333         final Violation violation =
334                 new Violation(1, 1,
335                         "messages.properties", "key", null, SeverityLevel.ERROR, null,
336                         getClass(), null);
337         final AuditEvent errorEvent = new AuditEvent(this, "Test.java", violation);
338         logger.addError(errorEvent);
339 
340         logger.fileFinished(errorEvent);
341         logger.auditFinished(null);
342         verifyXml(getPath("ExpectedXMLLoggerError.xml"), outStream, violation.getViolation());
343     }
344 
345     @Test
346     public void testFileOpenTag()
347             throws Exception {
348         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
349         logger.auditStarted(null);
350         final AuditEvent ev = new AuditEvent(this, "Test&.java");
351         logger.fileFinished(ev);
352         logger.auditFinished(null);
353         verifyXml(getPath("ExpectedXMLLoggerSpecialName.xml"),
354                 outStream, "<file name=" + "Test&amp;.java" + ">");
355 
356     }
357 
358     @Test
359     public void testVerifyLogger()
360             throws Exception {
361         final String inputFileWithConfig = "InputXMLLogger.java";
362         final String xmlReport = "ExpectedXMLLoggerWithChecker.xml";
363         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, xmlReport);
364     }
365 
366     @Test
367     public void testVerifyLoggerWithMultipleInput()
368             throws Exception {
369         final String inputFileWithConfig = "InputXMLLogger.java";
370         final String expectedXmlReport = "ExpectedXMLLoggerDuplicatedFile.xml";
371         verifyWithInlineConfigParserAndXmlLogger(
372                 inputFileWithConfig,
373                 expectedXmlReport,
374                 List.of(inputFileWithConfig, inputFileWithConfig));
375     }
376 
377     @Test
378     public void testNullOutputStreamOptions() {
379         try {
380             final XMLLogger logger = new XMLLogger(outStream,
381                     (OutputStreamOptions) null);
382             // assert required to calm down eclipse's 'The allocated object is never used' violation
383             assertWithMessage("Null instance")
384                 .that(logger)
385                 .isNotNull();
386             assertWithMessage("Exception was expected").fail();
387         }
388         catch (IllegalArgumentException exception) {
389             assertWithMessage("Invalid error message")
390                 .that(exception.getMessage())
391                 .isEqualTo("Parameter outputStreamOptions can not be null");
392         }
393     }
394 
395     @Test
396     public void testFinishLocalSetup() {
397         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
398         logger.finishLocalSetup();
399         logger.auditStarted(null);
400         logger.auditFinished(null);
401         assertWithMessage("instance should not be null")
402             .that(logger)
403             .isNotNull();
404     }
405 
406     /**
407      * We keep this test for 100% coverage. Until #12873.
408      */
409     @Test
410     public void testCtorWithTwoParametersCloseStreamOptions() {
411         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.CLOSE);
412         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream");
413 
414         assertWithMessage("closeStream should be true")
415                 .that(closeStream)
416                 .isTrue();
417     }
418 
419     /**
420      * We keep this test for 100% coverage. Until #12873.
421      */
422     @Test
423     public void testCtorWithTwoParametersNoneStreamOptions() {
424         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.NONE);
425         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream");
426 
427         assertWithMessage("closeStream should be false")
428                 .that(closeStream)
429                 .isFalse();
430     }
431 
432     private static final class TestException extends RuntimeException {
433         @Serial
434         private static final long serialVersionUID = 1L;
435 
436         private TestException(String msg, Throwable cause) {
437             super(msg, cause);
438         }
439 
440         @Override
441         public void printStackTrace(PrintWriter printWriter) {
442             printWriter.print("stackTrace\r\nexample");
443         }
444 
445     }
446 
447 }