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