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