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     /**
144      * Cannot use verifyWithInlineConfigParserAndXmlLogger because it requires
145      * a custom stream to count close() calls.
146      *
147      * @throws Exception throws exception
148      */
149     @Test
150     public void testNoCloseStream()
151             throws Exception {
152         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.NONE);
153         logger.auditStarted(null);
154         logger.auditFinished(null);
155 
156         assertWithMessage("Invalid close count")
157             .that(outStream.getCloseCount())
158             .isEqualTo(0);
159 
160         outStream.close();
161         verifyXml(getPath("ExpectedXMLLoggerEmpty.xml"), outStream);
162     }
163 
164     @Test
165         public void testFileStarted() throws Exception {
166         verifyWithInlineConfigParserAndXmlLogger(
167             "InputXMLLoggerFileStarted.java",
168             "ExpectedXMLLoggerFileStarted.xml");
169     }
170 
171     @Test
172     public void testFileFinished()
173             throws Exception {
174         verifyWithInlineConfigParserAndXmlLogger(
175             "InputXMLLoggerFileFinished.java",
176             "ExpectedXMLLoggerFileFinished.xml");
177     }
178 
179     @Test
180     public void testAddError() throws Exception {
181         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerAddError.java",
182                 "ExpectedXMLLoggerAddError.xml");
183     }
184 
185     /**
186      * This test cannot use the standard input/expected XML approach
187      * because it requires an AuditEvent with a null fileName.
188      */
189     @Test
190     public void testAddErrorWithNullFileName() throws Exception {
191         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
192         logger.auditStarted(null);
193         final Violation violation =
194                 new Violation(1, 1,
195                         "messages.properties", "key", null, SeverityLevel.ERROR, null,
196                         getClass(), null);
197         final AuditEvent ev = new AuditEvent(this, null, violation);
198         logger.addError(ev);
199         logger.auditFinished(null);
200         verifyXml(getPath("ExpectedXMLLoggerErrorNullFileName.xml"), outStream,
201                 violation.getViolation());
202     }
203 
204     @Test
205     public void testAddErrorModuleId() throws Exception {
206         final String inputFile = "InputXMLLoggerErrorModuleId.java";
207         final String expectedXmlReport = "ExpectedXMLLoggerErrorModuleId.xml";
208         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
209     }
210 
211     @Test
212     public void testAddErrorWithEncodedMessage() throws Exception {
213         final String inputFileWithConfig = "InputXMLLoggerEncodedMessage.java";
214         final String expectedXmlReport = "ExpectedXMLLoggerEncodedMessage.xml";
215         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, expectedXmlReport);
216     }
217 
218     @Test
219     public void testAddErrorOnZeroColumns() throws Exception {
220         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerErrorOnZeroColumn.java",
221                 "ExpectedXMLLoggerErrorZeroColumn.xml");
222     }
223 
224     @Test
225     public void testAddIgnored() throws Exception {
226         final String inputFile = "InputXMLLoggerIgnored.java";
227         final String expectedXmlReport = "ExpectedXMLLoggerIgnored.xml";
228         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
229     }
230 
231     @Test
232     public void testAddException()
233             throws Exception {
234         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
235         logger.auditStarted(null);
236         final Violation violation =
237             new Violation(1, 1,
238                 "messages.properties", null, null, null, getClass(), null);
239         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
240         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
241         logger.auditFinished(null);
242         verifyXml(getPath("ExpectedXMLLoggerException.xml"), outStream);
243         assertWithMessage("Invalid close count")
244             .that(outStream.getCloseCount())
245             .isEqualTo(1);
246     }
247 
248     @Test
249     public void testAddExceptionWithNullFileName()
250             throws Exception {
251         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
252         logger.auditStarted(null);
253         final Violation violation =
254                 new Violation(1, 1,
255                         "messages.properties", null, null, null, getClass(), null);
256         final AuditEvent ev = new AuditEvent(this, null, violation);
257         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
258         logger.auditFinished(null);
259         verifyXml(getPath("ExpectedXMLLoggerExceptionNullFileName.xml"), outStream);
260         assertWithMessage("Invalid close count")
261             .that(outStream.getCloseCount())
262             .isEqualTo(1);
263     }
264 
265     @Test
266     public void testAddExceptionAfterFileStarted()
267             throws Exception {
268         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
269         logger.auditStarted(null);
270 
271         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
272         logger.fileStarted(fileStartedEvent);
273 
274         final Violation violation =
275                 new Violation(1, 1,
276                         "messages.properties", null, null, null, getClass(), null);
277         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
278         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
279 
280         logger.fileFinished(ev);
281         logger.auditFinished(null);
282         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
283         assertWithMessage("Invalid close count")
284             .that(outStream.getCloseCount())
285             .isEqualTo(1);
286     }
287 
288     @Test
289     public void testAddExceptionBeforeFileFinished()
290             throws Exception {
291         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
292         logger.auditStarted(null);
293         final Violation violation =
294                 new Violation(1, 1,
295                         "messages.properties", null, null, null, getClass(), null);
296         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
297         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
298         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
299         logger.fileFinished(fileFinishedEvent);
300         logger.auditFinished(null);
301         verifyXml(getPath("ExpectedXMLLoggerException3.xml"), outStream);
302         assertWithMessage("Invalid close count")
303             .that(outStream.getCloseCount())
304             .isEqualTo(1);
305     }
306 
307     @Test
308     public void testAddExceptionBetweenFileStartedAndFinished()
309             throws Exception {
310         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
311         logger.auditStarted(null);
312         final Violation violation =
313                 new Violation(1, 1,
314                         "messages.properties", null, null, null, getClass(), null);
315         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
316         logger.fileStarted(fileStartedEvent);
317         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
318         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
319         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
320         logger.fileFinished(fileFinishedEvent);
321         logger.auditFinished(null);
322         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
323         assertWithMessage("Invalid close count")
324             .that(outStream.getCloseCount())
325             .isEqualTo(1);
326     }
327 
328     @Test
329     public void testAuditFinishedWithoutFileFinished() throws Exception {
330         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
331         logger.auditStarted(null);
332         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
333         logger.fileStarted(fileStartedEvent);
334 
335         final Violation violation =
336                 new Violation(1, 1,
337                         "messages.properties", "key", null, SeverityLevel.ERROR, null,
338                         getClass(), null);
339         final AuditEvent errorEvent = new AuditEvent(this, "Test.java", violation);
340         logger.addError(errorEvent);
341 
342         logger.fileFinished(errorEvent);
343         logger.auditFinished(null);
344         verifyXml(getPath("ExpectedXMLLoggerError.xml"), outStream, violation.getViolation());
345     }
346 
347     @Test
348     public void testFileOpenTag()
349             throws Exception {
350         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
351         logger.auditStarted(null);
352         final AuditEvent ev = new AuditEvent(this, "Test&.java");
353         logger.fileFinished(ev);
354         logger.auditFinished(null);
355         verifyXml(getPath("ExpectedXMLLoggerSpecialName.xml"),
356                 outStream, "<file name=" + "Test&amp;.java" + ">");
357 
358     }
359 
360     @Test
361     public void testVerifyLogger()
362             throws Exception {
363         final String inputFileWithConfig = "InputXMLLogger.java";
364         final String xmlReport = "ExpectedXMLLoggerWithChecker.xml";
365         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, xmlReport);
366     }
367 
368     @Test
369     public void testVerifyLoggerWithMultipleInput()
370             throws Exception {
371         final String inputFileWithConfig = "InputXMLLogger.java";
372         final String expectedXmlReport = "ExpectedXMLLoggerDuplicatedFile.xml";
373         verifyWithInlineConfigParserAndXmlLogger(
374                 inputFileWithConfig,
375                 expectedXmlReport,
376                 List.of(inputFileWithConfig, inputFileWithConfig));
377     }
378 
379     @Test
380     public void testNullOutputStreamOptions() {
381         try {
382             final XMLLogger logger = new XMLLogger(outStream,
383                     (OutputStreamOptions) null);
384             // assert required to calm down eclipse's 'The allocated object is never used' violation
385             assertWithMessage("Null instance")
386                 .that(logger)
387                 .isNotNull();
388             assertWithMessage("Exception was expected").fail();
389         }
390         catch (IllegalArgumentException exception) {
391             assertWithMessage("Invalid error message")
392                 .that(exception.getMessage())
393                 .isEqualTo("Parameter outputStreamOptions can not be null");
394         }
395     }
396 
397     @Test
398     public void testFinishLocalSetup() {
399         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
400         logger.finishLocalSetup();
401         logger.auditStarted(null);
402         logger.auditFinished(null);
403         assertWithMessage("instance should not be null")
404             .that(logger)
405             .isNotNull();
406     }
407 
408     /**
409      * We keep this test for 100% coverage. Until #12873.
410      */
411     @Test
412     public void testCtorWithTwoParametersCloseStreamOptions() {
413         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.CLOSE);
414         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
415 
416         assertWithMessage("closeStream should be true")
417                 .that(closeStream)
418                 .isTrue();
419     }
420 
421     /**
422      * We keep this test for 100% coverage. Until #12873.
423      */
424     @Test
425     public void testCtorWithTwoParametersNoneStreamOptions() {
426         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.NONE);
427         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
428 
429         assertWithMessage("closeStream should be false")
430                 .that(closeStream)
431                 .isFalse();
432     }
433 
434     private static final class TestException extends RuntimeException {
435         @Serial
436         private static final long serialVersionUID = 1L;
437 
438         private TestException(String msg, Throwable cause) {
439             super(msg, cause);
440         }
441 
442         @Override
443         public void printStackTrace(PrintWriter printWriter) {
444             printWriter.print("stackTrace\r\nexample");
445         }
446 
447     }
448 
449 }