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  import static org.junit.jupiter.api.Assumptions.assumeFalse;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.nio.charset.StandardCharsets;
29  import java.util.Arrays;
30  import java.util.Locale;
31  import java.util.ResourceBundle;
32  
33  import org.junit.jupiter.api.AfterEach;
34  import org.junit.jupiter.api.Test;
35  
36  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
37  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
38  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
39  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
40  
41  public class DefaultLoggerTest extends AbstractModuleTestSupport {
42  
43      private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
44  
45      @Override
46      public String getPackageLocation() {
47          return "com/puppycrawl/tools/checkstyle/defaultlogger";
48      }
49  
50      @AfterEach
51      public void tearDown() {
52          ResourceBundle.clearCache();
53      }
54  
55      @Test
56      public void testException() throws Exception {
57          final String inputFile = "InputDefaultLoggerTestException.java";
58          final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
59          final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestException.txt";
60  
61          final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
62          final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
63          final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
64                  errorStream, OutputStreamOptions.CLOSE);
65  
66          verifyWithInlineConfigParserAndDefaultLogger(
67                  getNonCompilablePath(inputFile),
68                  getPath(expectedInfoFile),
69                  getPath(expectedErrorFile),
70                  dl, infoStream, errorStream);
71      }
72  
73      @Test
74      public void testSingleError() throws Exception {
75          final String inputFile = "InputDefaultLoggerTestSingleError.java";
76          final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
77          final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestSingleError.txt";
78  
79          final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
80          final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
81          final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
82                  errorStream, OutputStreamOptions.CLOSE);
83  
84          verifyWithInlineConfigParserAndDefaultLogger(
85                  getPath(inputFile),
86                  getPath(expectedInfoFile),
87                  getPath(expectedErrorFile),
88                  dl, infoStream, errorStream);
89      }
90  
91      @Test
92      public void testMultipleErrors() throws Exception {
93          final String inputFile = "InputDefaultLoggerTestMultipleErrors.java";
94          final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
95          final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestMultipleErrors.txt";
96  
97          final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
98          final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
99          final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
100                 errorStream, OutputStreamOptions.CLOSE);
101 
102         verifyWithInlineConfigParserAndDefaultLogger(
103                 getPath(inputFile),
104                 getPath(expectedInfoFile),
105                 getPath(expectedErrorFile),
106                 dl, infoStream, errorStream);
107     }
108 
109     @Test
110     public void testCtorWithTwoParametersCloseStreamOptions() throws Exception {
111         final String inputFile = "InputDefaultLoggerTestSingleError.java";
112         final String expectedOutputFile = "ExpectedDefaultLoggerOutputSingleError.txt";
113 
114         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
115         final DefaultLogger dl = new DefaultLogger(outputStream, OutputStreamOptions.CLOSE);
116 
117         verifyWithInlineConfigParserAndDefaultLogger(
118                 getPath(inputFile),
119                 getPath(expectedOutputFile),
120                 dl, outputStream);
121     }
122 
123     @Test
124     public void testCtorWithTwoParametersNoneStreamOptions() throws Exception {
125         final String inputFile = "InputDefaultLoggerTestSingleError.java";
126         final String expectedOutputFile = "ExpectedDefaultLoggerOutputSingleError.txt";
127 
128         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
129         final DefaultLogger dl = new DefaultLogger(outputStream, OutputStreamOptions.NONE);
130 
131         verifyWithInlineConfigParserAndDefaultLogger(
132                 getPath(inputFile),
133                 getPath(expectedOutputFile),
134                 dl, outputStream);
135     }
136 
137     /**
138      * We keep this test for 100% coverage. Until #12873.
139      * Test not updated because relies on deprecated AutomaticBean and verifies only correct field
140      * mapping.
141      */
142     @Test
143     public void testOldCtorWithTwoParametersCloseStreamOptions() {
144         final OutputStream infoStream = new ByteArrayOutputStream();
145         final DefaultLogger dl = new DefaultLogger(infoStream,
146                 AutomaticBean.OutputStreamOptions.CLOSE);
147         final boolean closeInfo = TestUtil.getInternalState(dl, "closeInfo", Boolean.class);
148 
149         assertWithMessage("closeInfo should be true")
150                 .that(closeInfo)
151                 .isTrue();
152     }
153 
154     /**
155      * We keep this test for 100% coverage. Until #12873.
156      * Test not updated because relies on deprecated AutomaticBean and verifies only correct field
157      * mapping.
158      */
159     @Test
160     public void testOldCtorWithTwoParametersNoneStreamOptions() {
161         final OutputStream infoStream = new ByteArrayOutputStream();
162         final DefaultLogger dl = new DefaultLogger(infoStream,
163                 AutomaticBean.OutputStreamOptions.NONE);
164         final boolean closeInfo = TestUtil.getInternalState(dl, "closeInfo", Boolean.class);
165 
166         assertWithMessage("closeInfo should be false")
167                 .that(closeInfo)
168                 .isFalse();
169     }
170 
171     /**
172      * This test cannot use verifyWithInlineConfigParserAndLogger as it does not
173      * involve an input file, configuration or audit process. It only verifies that
174      * constructing a DefaultLogger with null stream options throws an
175      * IllegalArgumentException with the correct message.
176      */
177     @Test
178     public void testNullInfoStreamOptions() {
179         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
180         final IllegalArgumentException ex =
181                 TestUtil.getExpectedThrowable(IllegalArgumentException.class,
182                         () -> new DefaultLogger(outputStream, (OutputStreamOptions) null),
183                         "IllegalArgumentException expected");
184         assertWithMessage("Invalid error message")
185                 .that(ex)
186                 .hasMessageThat()
187                         .isEqualTo("Parameter infoStreamOptions can not be null");
188     }
189 
190     /**
191      * This test cannot use verifyWithInlineConfigParserAndLogger as it does not
192      * involve an input file, configuration or audit process. It only verifies that
193      * constructing a DefaultLogger with null stream options throws an
194      * IllegalArgumentException with the correct message.
195      */
196     @Test
197     public void testNullErrorStreamOptions() {
198         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
199         final IllegalArgumentException ex =
200                 TestUtil.getExpectedThrowable(IllegalArgumentException.class, () -> {
201                     final DefaultLogger defaultLogger = new DefaultLogger(outputStream,
202                             OutputStreamOptions.CLOSE, outputStream, null);
203 
204                     // Workaround for Eclipse error "The allocated object is never used"
205                     assertWithMessage("defaultLogger should be non-null")
206                             .that(defaultLogger)
207                             .isNotNull();
208                 },
209                 "IllegalArgumentException expected");
210         assertWithMessage("Invalid error message")
211                 .that(ex)
212                 .hasMessageThat()
213                         .isEqualTo("Parameter errorStreamOptions can not be null");
214     }
215 
216     @Test
217     public void testAddErrorModuleId() throws Exception {
218         final String inputFile = "InputDefaultLoggerTestAddErrorModuleId.java";
219         final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
220         final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestAddErrorModuleId.txt";
221 
222         final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
223         final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
224         final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
225                 errorStream, OutputStreamOptions.CLOSE);
226 
227         verifyWithInlineConfigParserAndDefaultLogger(
228                 getPath(inputFile),
229                 getPath(expectedInfoFile),
230                 getPath(expectedErrorFile),
231                 dl, infoStream, errorStream);
232     }
233 
234     @Test
235     public void testAddErrorIgnoreSeverityLevel() throws Exception {
236         final String inputFile = "InputDefaultLoggerTestIgnoreSeverityLevel.java";
237         final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
238         final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestIgnoreSeverityLevel.txt";
239 
240         final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
241         final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
242         final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
243                 errorStream, OutputStreamOptions.CLOSE);
244 
245         verifyWithInlineConfigParserAndDefaultLogger(
246                 getPath(inputFile),
247                 getPath(expectedInfoFile),
248                 getPath(expectedErrorFile),
249                 dl, infoStream, errorStream);
250     }
251 
252     @Test
253     public void testFinishLocalSetup() throws Exception {
254         final String inputFile = "InputDefaultLoggerTestSingleError.java";
255         final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
256         final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestSingleError.txt";
257 
258         final ByteArrayOutputStream infoStream = new ByteArrayOutputStream();
259         final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
260         final DefaultLogger dl = new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
261                 errorStream, OutputStreamOptions.CLOSE);
262 
263         dl.finishLocalSetup();
264 
265         verifyWithInlineConfigParserAndDefaultLogger(
266                 getPath(inputFile),
267                 getPath(expectedInfoFile),
268                 getPath(expectedErrorFile),
269                 dl, infoStream, errorStream);
270     }
271 
272     /**
273      * Verifies that the language specified with the system property {@code user.language} exists.
274      */
275     @Test
276     public void testLanguageIsValid() {
277         final String language = DEFAULT_LOCALE.getLanguage();
278         assumeFalse(language.isEmpty(), "Locale not set");
279         assertWithMessage("Invalid language")
280                 .that(Arrays.asList(Locale.getISOLanguages()))
281                 .contains(language);
282     }
283 
284     /**
285      * Verifies that the country specified with the system property {@code user.country} exists.
286      */
287     @Test
288     public void testCountryIsValid() {
289         final String country = DEFAULT_LOCALE.getCountry();
290         assumeFalse(country.isEmpty(), "Locale not set");
291         assertWithMessage("Invalid country")
292                 .that(Arrays.asList(Locale.getISOCountries()))
293                 .contains(country);
294     }
295 
296     @Test
297     public void testNewCtor() throws Exception {
298         final String inputFile = "InputDefaultLoggerTestException.java";
299         final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
300         final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestException.txt";
301 
302         try (MockByteArrayOutputStream infoStream = new MockByteArrayOutputStream();
303              MockByteArrayOutputStream errorStream = new MockByteArrayOutputStream()) {
304             final DefaultLogger dl = new DefaultLogger(
305                     infoStream, OutputStreamOptions.CLOSE,
306                     errorStream, OutputStreamOptions.CLOSE);
307 
308             verifyWithInlineConfigParserAndDefaultLogger(
309                     getNonCompilablePath(inputFile),
310                     getPath(expectedInfoFile),
311                     getPath(expectedErrorFile),
312                     dl, infoStream, errorStream);
313 
314             assertWithMessage("Info stream should be closed")
315                     .that(infoStream.closedCount)
316                     .isGreaterThan(0);
317             assertWithMessage("Error stream should be closed")
318                     .that(errorStream.closedCount)
319                     .isGreaterThan(0);
320         }
321     }
322 
323     /**
324      * Direct invocation of {@code addException} is necessary because
325      * {@code Checker} implementation does not call {@code addException}
326      * during normal file processing flow, leaving mutations on lines 179-183
327      * ({@code PrintWriter::println}, {@code getLocalizedMessage},
328      * {@code AuditEvent::getFileName}, {@code Throwable::printStackTrace})
329      * with no coverage if tested only through {@code verifyWithInlineConfigParserAndDefaultLogger}.
330      */
331     @Test
332     public void testAddException() throws Exception {
333         try (MockByteArrayOutputStream errorStream = new MockByteArrayOutputStream()) {
334             final DefaultLogger dl = new DefaultLogger(
335                     new ByteArrayOutputStream(), OutputStreamOptions.CLOSE,
336                     errorStream, OutputStreamOptions.CLOSE);
337 
338             dl.addException(new AuditEvent(5000, "myfile"),
339                     new IllegalStateException("oops"));
340 
341             final String errorOutput = errorStream.toString(StandardCharsets.UTF_8);
342 
343             assertWithMessage("Exception output should contain filename")
344                     .that(errorOutput)
345                     .contains("myfile");
346             assertWithMessage("Exception output should contain exception type")
347                     .that(errorOutput)
348                     .contains("java.lang.IllegalStateException");
349             assertWithMessage("Exception output should contain exception message")
350                     .that(errorOutput)
351                     .contains("oops");
352         }
353     }
354 
355     @Test
356     public void testStreamsNotClosedByLogger() throws Exception {
357         final String inputFile = "InputDefaultLoggerTestSingleError.java";
358         final String expectedInfoFile = "ExpectedDefaultLoggerInfoDefaultOutput.txt";
359         final String expectedErrorFile = "ExpectedDefaultLoggerErrorsTestSingleError.txt";
360 
361         try (ByteArrayOutputStream infoStream = new ModifiedByteArrayOutputStream();
362             ByteArrayOutputStream errorStream = new ModifiedByteArrayOutputStream()) {
363             final DefaultLogger dl = new DefaultLogger(
364                     infoStream, OutputStreamOptions.NONE,
365                     errorStream, OutputStreamOptions.NONE);
366 
367             verifyWithInlineConfigParserAndDefaultLogger(
368                     getPath(inputFile),
369                     getPath(expectedInfoFile),
370                     getPath(expectedErrorFile),
371                     dl, infoStream, errorStream);
372         }
373     }
374 
375     private static final class ModifiedByteArrayOutputStream extends ByteArrayOutputStream {
376         @Override
377         public void close() throws IOException {
378             reset();
379             super.close();
380         }
381     }
382 
383     private static final class MockByteArrayOutputStream extends ByteArrayOutputStream {
384 
385         private int closedCount;
386 
387         @Override
388         public void close() throws IOException {
389             super.close();
390             ++closedCount;
391         }
392 
393     }
394 
395 }