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     @Test
278     public void testAddExceptionAfterFileStarted()
279             throws Exception {
280         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
281         logger.auditStarted(null);
282 
283         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
284         logger.fileStarted(fileStartedEvent);
285 
286         final Violation violation =
287                 new Violation(1, 1,
288                         "messages.properties", null, null, null, getClass(), null);
289         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
290         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
291 
292         logger.fileFinished(ev);
293         logger.auditFinished(null);
294         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
295         assertWithMessage("Invalid close count")
296             .that(outStream.getCloseCount())
297             .isEqualTo(1);
298     }
299 
300     @Test
301     public void testAddExceptionBeforeFileFinished()
302             throws Exception {
303         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
304         logger.auditStarted(null);
305         final Violation violation =
306                 new Violation(1, 1,
307                         "messages.properties", null, null, null, getClass(), null);
308         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
309         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
310         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
311         logger.fileFinished(fileFinishedEvent);
312         logger.auditFinished(null);
313         verifyXml(getPath("ExpectedXMLLoggerException3.xml"), outStream);
314         assertWithMessage("Invalid close count")
315             .that(outStream.getCloseCount())
316             .isEqualTo(1);
317     }
318 
319     @Test
320     public void testAddExceptionBetweenFileStartedAndFinished()
321             throws Exception {
322         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
323         logger.auditStarted(null);
324         final Violation violation =
325                 new Violation(1, 1,
326                         "messages.properties", null, null, null, getClass(), null);
327         final AuditEvent fileStartedEvent = new AuditEvent(this, "Test.java");
328         logger.fileStarted(fileStartedEvent);
329         final AuditEvent ev = new AuditEvent(this, "Test.java", violation);
330         logger.addException(ev, new TestException("msg", new RuntimeException("msg")));
331         final AuditEvent fileFinishedEvent = new AuditEvent(this, "Test.java");
332         logger.fileFinished(fileFinishedEvent);
333         logger.auditFinished(null);
334         verifyXml(getPath("ExpectedXMLLoggerException2.xml"), outStream);
335         assertWithMessage("Invalid close count")
336             .that(outStream.getCloseCount())
337             .isEqualTo(1);
338     }
339 
340     @Test
341     public void testAuditFinishedWithoutFileFinished() throws Exception {
342         final String inputFile = "InputXMLLoggerAuditFinishedWithoutFileFinished.java";
343         final String expectedXmlReport = "ExpectedXMLLoggerAuditFinishedWithoutFileFinished.xml";
344         verifyWithInlineConfigParserAndXmlLogger(inputFile, expectedXmlReport);
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     /**
380      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this is a
381      * pure unit test that validates constructor error handling and exception
382      * messaging, which cannot be exercised through Checker execution.
383      */
384     @Test
385     public void testNullOutputStreamOptions() {
386         try {
387             final XMLLogger logger = new XMLLogger(outStream,
388                     (OutputStreamOptions) null);
389             // assert required to calm down eclipse's 'The allocated object is never used' violation
390             assertWithMessage("Null instance")
391                 .that(logger)
392                 .isNotNull();
393             assertWithMessage("Exception was expected").fail();
394         }
395         catch (IllegalArgumentException exception) {
396             assertWithMessage("Invalid error message")
397                 .that(exception.getMessage())
398                 .isEqualTo("Parameter outputStreamOptions can not be null");
399         }
400     }
401 
402     /**
403      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
404      * directly exercises XMLLogger lifecycle behavior (finishLocalSetup) and
405      * does not depend on Checker execution or XML output.
406      */
407     @Test
408     public void testFinishLocalSetup() {
409         final XMLLogger logger = new XMLLogger(outStream, OutputStreamOptions.CLOSE);
410         logger.finishLocalSetup();
411         logger.auditStarted(null);
412         logger.auditFinished(null);
413         assertWithMessage("instance should not be null")
414             .that(logger)
415             .isNotNull();
416     }
417 
418     /**
419      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
420      * verifies constructor wiring and internal state (closeStream flag),
421      * which are not exposed through the inline verifier.
422      * We keep this test for 100% coverage. Until #12873.
423      */
424     @Test
425     public void testCtorWithTwoParametersCloseStreamOptions() {
426         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.CLOSE);
427         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
428 
429         assertWithMessage("closeStream should be true")
430                 .that(closeStream)
431                 .isTrue();
432     }
433 
434     /**
435      * Cannot use verifyWithInlineConfigParserAndXmlLogger because this test
436      * verifies constructor wiring and internal state (closeStream flag),
437      * which are not exposed through the inline verifier.
438      * We keep this test for 100% coverage. Until #12873.
439      */
440     @Test
441     public void testCtorWithTwoParametersNoneStreamOptions() {
442         final XMLLogger logger = new XMLLogger(outStream, AutomaticBean.OutputStreamOptions.NONE);
443         final boolean closeStream = TestUtil.getInternalState(logger, "closeStream", Boolean.class);
444 
445         assertWithMessage("closeStream should be false")
446                 .that(closeStream)
447                 .isFalse();
448     }
449 
450     private static final class TestException extends RuntimeException {
451         @Serial
452         private static final long serialVersionUID = 1L;
453 
454         private TestException(String msg, Throwable cause) {
455             super(msg, cause);
456         }
457 
458         @Override
459         public void printStackTrace(PrintWriter printWriter) {
460             printWriter.print("stackTrace\r\nexample");
461         }
462 
463     }
464 
465 }