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  
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      public 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("\"%s\"", 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: %s", 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: %s", noReference)
122                     .that(XMLLogger.isReference(noReference))
123                     .isFalse();
124         }
125 
126         outStream.close();
127     }
128 
129     /**
130      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
131      * relies on a custom OutputStream to assert close() call behavior.
132      */
133     @Test
134     public void testCloseStream()
135             throws Exception {
136         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
137         logger.auditStarted(null);
138         logger.auditFinished(null);
139 
140         assertWithMessage("Invalid close count")
141             .that(outStream.getCloseCount())
142             .isEqualTo(1);
143 
144         verifyXml(getPath("ExpectedXMLLoggerEmpty.xml"), outStream);
145     }
146 
147     /**
148      * Cannot use verifyWithInlineConfigParserAndXmlLogger because it requires
149      * a custom stream to count close() calls.
150      *
151      * @throws Exception throws exception
152      */
153     @Test
154     public void testNoCloseStream()
155             throws Exception {
156         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.NONE);
157         logger.auditStarted(null);
158         logger.auditFinished(null);
159 
160         assertWithMessage("Invalid close count")
161             .that(outStream.getCloseCount())
162             .isEqualTo(0);
163 
164         outStream.close();
165         verifyXml(getPath("ExpectedXMLLoggerEmpty.xml"), outStream);
166     }
167 
168     @Test
169         public void testFileStarted() throws Exception {
170         verifyWithInlineConfigParserAndXmlLogger(
171             "InputXMLLoggerFileStarted.java",
172             "ExpectedXMLLoggerFileStarted.xml");
173     }
174 
175     @Test
176     public void testFileFinished()
177             throws Exception {
178         verifyWithInlineConfigParserAndXmlLogger(
179             "InputXMLLoggerFileFinished.java",
180             "ExpectedXMLLoggerFileFinished.xml");
181     }
182 
183     @Test
184     public void testAddError() throws Exception {
185         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerAddError.java",
186                 "ExpectedXMLLoggerAddError.xml");
187     }
188 
189     /**
190      * This test cannot use the standard input/expected XML approach
191      * because it requires an AuditEvent with a null fileName.
192      */
193     @Test
194     public void testAddErrorWithNullFileName() throws Exception {
195         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
196         logger.auditStarted(null);
197         final Violation violation =
198                 new Violation(1, 1,
199                         "messages.properties", "key", null, SeverityLevel.ERROR, null,
200                         getClass(), null);
201         final AuditEvent ev = new AuditEvent(this, null, violation);
202         logger.addError(ev);
203         logger.auditFinished(null);
204         verifyXml(getPath("ExpectedXMLLoggerErrorNullFileName.xml"), outStream,
205                 violation.getViolation());
206     }
207 
208     @Test
209     public void testAddErrorModuleId() throws Exception {
210         final String inputFile = "InputXMLLoggerErrorModuleId.java";
211         final String expectedXmlReport = "ExpectedXMLLoggerErrorModuleId.xml";
212         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
213     }
214 
215     @Test
216     public void testAddErrorEmptyModuleId() throws Exception {
217         final String inputFile = "InputXMLLoggerErrorEmptyModuleId.java";
218         final String expectedXmlReport = "ExpectedXMLLoggerErrorEmptyModuleId.xml";
219         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
220     }
221 
222     @Test
223     public void testAddErrorWithAndWithoutModuleId() throws Exception {
224         final String inputFile = "InputXMLLoggerErrorWithAndWithoutModuleId.java";
225         final String expectedXmlReport = "ExpectedXMLLoggerErrorWithAndWithoutModuleId.xml";
226         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
227     }
228 
229     @Test
230     public void testAddErrorWithEncodedMessage() throws Exception {
231         final String inputFileWithConfig = "InputXMLLoggerEncodedMessage.java";
232         final String expectedXmlReport = "ExpectedXMLLoggerEncodedMessage.xml";
233         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, expectedXmlReport);
234     }
235 
236     @Test
237     public void testAddErrorOnZeroColumns() throws Exception {
238         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerErrorOnZeroColumn.java",
239                 "ExpectedXMLLoggerErrorZeroColumn.xml");
240     }
241 
242     @Test
243     public void testAddIgnored() throws Exception {
244         final String inputFile = "InputXMLLoggerIgnored.java";
245         final String expectedXmlReport = "ExpectedXMLLoggerIgnored.xml";
246         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
247     }
248 
249     @Test
250     public void testAddException() throws Exception {
251         verifyWithInlineConfigParserAndXmlLogger("InputXMLLoggerException.java",
252                 "ExpectedXMLLoggerExceptionInput.xml");
253     }
254 
255     /**
256      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
257      * requires an AuditEvent with a null file name and a custom OutputStream
258      * to verify close() behavior.
259      */
260     @Test
261     public void testAddExceptionWithNullFileName()
262             throws Exception {
263         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
264         logger.auditStarted(null);
265         final Violation violation =
266                 new Violation(1, 1,
267                         "messages.properties", null, null, null, getClass(), null);
268         final AuditEvent ev = new AuditEvent(this, null, violation);
269         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
270         logger.auditFinished(null);
271         verifyXml(getPath("ExpectedXMLLoggerExceptionNullFileName.xml"), outStream);
272         assertWithMessage("Invalid close count")
273             .that(outStream.getCloseCount())
274             .isEqualTo(1);
275     }
276 
277     /**
278      * Cannot use verifyWithInlineConfigParserAndXmlLogger, because
279      * this test requires an Exception is logged. This cannot be
280      * done right now as the method uses Checker, which only calls
281      * the addError() method. Would be possible if Checker or the
282      * verifyWithInlineConfigParserAndXmlLogger method are modified
283      * to handle addException() calls.
284      */
285     @Test
286     public void testAddExceptionAfterFileStarted()
287             throws Exception {
288         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
289         logger.auditStarted(null);
290 
291         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
292         logger.fileStarted(fileStartedEvent);
293 
294         final Violation violation =
295                 new Violation(1, 1,
296                         "messages.properties", null, null, null, getClass(), null);
297         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
298         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
299 
300         logger.fileFinished(ev);
301         logger.auditFinished(null);
302         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
303         assertWithMessage("Invalid close count")
304             .that(outStream.getCloseCount())
305             .isEqualTo(1);
306     }
307 
308     /**
309      * Cannot use verifyWithInlineConfigParserAndXmlLogger, because
310      * this test requires an Exception is logged. This cannot be
311      * done right now as the method uses Checker, which only calls
312      * the addError() method. Would be possible if Checker or the
313      * verifyWithInlineConfigParserAndXmlLogger method are modified
314      * to handle addException() calls.
315      */
316     @Test
317     public void testAddExceptionBeforeFileFinished()
318             throws Exception {
319         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
320         logger.auditStarted(null);
321         final Violation violation =
322                 new Violation(1, 1,
323                         "messages.properties", null, null, null, getClass(), null);
324         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
325         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
326         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
327         logger.fileFinished(fileFinishedEvent);
328         logger.auditFinished(null);
329         verifyXml(getPath("ExpectedXMLLoggerException3.xml"), outStream);
330         assertWithMessage("Invalid close count")
331             .that(outStream.getCloseCount())
332             .isEqualTo(1);
333     }
334 
335     /**
336      * Cannot use verifyWithInlineConfigParserAndXmlLogger, because
337      * this test requires an Exception is logged. This cannot be
338      * done right now as the method uses Checker, which only calls
339      * the addError() method. Would be possible if Checker or the
340      * verifyWithInlineConfigParserAndXmlLogger method are modified
341      * to handle addException() calls.
342      */
343     @Test
344     public void testAddExceptionBetweenFileStartedAndFinished()
345             throws Exception {
346         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
347         logger.auditStarted(null);
348         final Violation violation =
349                 new Violation(1, 1,
350                         "messages.properties", null, null, null, getClass(), null);
351         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
352         logger.fileStarted(fileStartedEvent);
353         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
354         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
355         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
356         logger.fileFinished(fileFinishedEvent);
357         logger.auditFinished(null);
358         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
359         assertWithMessage("Invalid close count")
360             .that(outStream.getCloseCount())
361             .isEqualTo(1);
362     }
363 
364     @Test
365     public void testAuditFinishedWithoutFileFinished() throws Exception {
366         final String inputFile = "InputXMLLoggerAuditFinishedWithoutFileFinished.java";
367         final String expectedXmlReport = "ExpectedXMLLoggerAuditFinishedWithoutFileFinished.xml";
368         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
369     }
370 
371     /**
372      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
373      * requires a custom AuditEvent with a special character in the file
374      * name ("&") to verify XML escaping, without creating a physical file
375      * with special characters in the repository.
376      */
377     @Test
378     public void testFileOpenTag()
379             throws Exception {
380         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
381         logger.auditStarted(null);
382         final AuditEvent ev = new AuditEvent(this, "Test&.java");
383         logger.fileFinished(ev);
384         logger.auditFinished(null);
385         verifyXml(getPath("ExpectedXMLLoggerSpecialName.xml"),
386                 outStream, "<file name=" + "Test&amp;.java" + ">");
387 
388     }
389 
390     @Test
391     public void testVerifyLogger()
392             throws Exception {
393         final String inputFileWithConfig = "InputXMLLogger.java";
394         final String xmlReport = "ExpectedXMLLoggerWithChecker.xml";
395         verifyWithInlineConfigParserAndXmlLogger(inputFileWithConfig, xmlReport);
396     }
397 
398     @Test
399     public void testVerifyLoggerWithMultipleInput()
400             throws Exception {
401         final String inputFileWithConfig = "InputXMLLogger.java";
402         final String expectedXmlReport = "ExpectedXMLLoggerDuplicatedFile.xml";
403         verifyWithInlineConfigParserAndXmlLogger(
404                 inputFileWithConfig,
405                 expectedXmlReport,
406                 List.of(inputFileWithConfig, inputFileWithConfig));
407     }
408 
409     /**
410      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this is a
411      * pure unit test that validates constructor error handling and exception
412      * messaging, which cannot be exercised through Checker execution.
413      */
414     @Test
415     public void testNullOutputStreamOptions() {
416         try {
417             final XMLLogger logger = new XMLLogger(outStream,
418                     (OutputStreamOptions) null);
419             // assert required to calm down eclipse's 'The allocated object is never used' violation
420             assertWithMessage("Null instance")
421                 .that(logger)
422                 .isNotNull();
423             assertWithMessage("Exception was expected").fail();
424         }
425         catch (IllegalArgumentException exception) {
426             assertWithMessage("Invalid error message")
427                 .that(exception.getMessage())
428                 .isEqualTo("Parameter outputStreamOptions can not be null");
429         }
430     }
431 
432     /**
433      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
434      * directly exercises XMLLogger lifecycle behavior (finishLocalSetup) and
435      * does not depend on Checker execution or XML output.
436      */
437     @Test
438     public void testFinishLocalSetup() {
439         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
440         logger.finishLocalSetup();
441         logger.auditStarted(null);
442         logger.auditFinished(null);
443         assertWithMessage("instance should not be null")
444             .that(logger)
445             .isNotNull();
446     }
447 
448     /**
449      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
450      * verifies constructor wiring and internal state (closeStream flag),
451      * which are not exposed through the inline verifier.
452      * We keep this test for 100% coverage. Until #12873.
453      */
454     @Test
455     public void testCtorWithTwoParametersCloseStreamOptions() {
456         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.CLOSE);
457         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
458 
459         assertWithMessage("closeStream should be true")
460                 .that(closeStream)
461                 .isTrue();
462     }
463 
464     /**
465      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
466      * verifies constructor wiring and internal state (closeStream flag),
467      * which are not exposed through the inline verifier.
468      * We keep this test for 100% coverage. Until #12873.
469      */
470     @Test
471     public void testCtorWithTwoParametersNoneStreamOptions() {
472         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.NONE);
473         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
474 
475         assertWithMessage("closeStream should be false")
476                 .that(closeStream)
477                 .isFalse();
478     }
479 
480     private static final class TestException extends RuntimeException {
481         @Serial
482         private static final long serialVersionUID = 1L;
483 
484         private TestException(String msg, Throwable cause) {
485             super(msg, cause);
486         }
487 
488         @Override
489         public void printStackTrace(PrintWriter printWriter) {
490             printWriter.print("stackTrace\r\nexample");
491         }
492 
493     }
494 
495 }