1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.joran;
15  
16  import ch.qos.logback.classic.*;
17  import ch.qos.logback.classic.joran.serializedModel.HardenedModelInputStream;
18  import ch.qos.logback.classic.model.ConfigurationModel;
19  import ch.qos.logback.classic.model.LoggerModel;
20  import ch.qos.logback.classic.spi.ILoggingEvent;
21  import ch.qos.logback.classic.turbo.DebugUsersTurboFilter;
22  import ch.qos.logback.classic.turbo.NOPTurboFilter;
23  import ch.qos.logback.classic.turbo.TurboFilter;
24  import ch.qos.logback.classic.util.LogbackMDCAdapter;
25  import ch.qos.logback.core.ConsoleAppender;
26  import ch.qos.logback.core.CoreConstants;
27  import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
28  import ch.qos.logback.core.helpers.NOPAppender;
29  import ch.qos.logback.core.joran.action.ParamAction;
30  import ch.qos.logback.core.joran.spi.ActionException;
31  import ch.qos.logback.core.joran.spi.JoranException;
32  import ch.qos.logback.core.model.Model;
33  import ch.qos.logback.core.model.SerializeModelModel;
34  import ch.qos.logback.core.pattern.parser.Parser;
35  import ch.qos.logback.core.read.ListAppender;
36  import ch.qos.logback.core.spi.ErrorCodes;
37  import ch.qos.logback.core.spi.ScanException;
38  import ch.qos.logback.core.status.Status;
39  import ch.qos.logback.core.status.testUtil.StatusChecker;
40  import ch.qos.logback.core.testUtil.RandomUtil;
41  import ch.qos.logback.core.testUtil.StringListAppender;
42  import ch.qos.logback.core.util.CachingDateFormatter;
43  import ch.qos.logback.core.util.EnvUtil;
44  import ch.qos.logback.core.util.StatusPrinter;
45  import ch.qos.logback.core.util.StatusPrinter2;
46  import org.junit.jupiter.api.Disabled;
47  import org.junit.jupiter.api.Test;
48  import org.slf4j.MDC;
49  import org.slf4j.event.KeyValuePair;
50  import org.slf4j.spi.MDCAdapter;
51  
52  import java.io.Console;
53  import java.io.FileInputStream;
54  import java.io.IOException;
55  import java.text.SimpleDateFormat;
56  import java.util.Date;
57  
58  import static ch.qos.logback.core.CoreConstants.MODEL_CONFIG_FILE_EXTENSION;
59  import static ch.qos.logback.core.joran.sanity.AppenderWithinAppenderSanityChecker.NESTED_APPENDERS_WARNING;
60  import static ch.qos.logback.core.model.processor.ImplicitModelHandler.IGNORING_UNKNOWN_PROP;
61  import static ch.qos.logback.core.model.processor.ShutdownHookModelHandler.RENAME_WARNING;
62  import static ch.qos.logback.core.testUtil.CoreTestConstants.OUTPUT_DIR_PREFIX;
63  import static org.junit.jupiter.api.Assertions.*;
64  
65  public class JoranConfiguratorTest {
66  
67      LoggerContext loggerContext = new LoggerContext();
68      MDCAdapter mdcAdapter = new LogbackMDCAdapter();
69      Logger logger = loggerContext.getLogger(this.getClass().getName());
70      Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
71      StatusPrinter2 statusPrinter2 = new StatusPrinter2();
72      StatusChecker checker = new StatusChecker(loggerContext);
73      int diff = RandomUtil.getPositiveInt();
74  
75      void configure(String file) throws JoranException {
76          loggerContext.setMDCAdapter(mdcAdapter);
77          JoranConfigurator jc = new JoranConfigurator();
78          jc.setContext(loggerContext);
79          loggerContext.putProperty("diff", "" + diff);
80          jc.doConfigure(file);
81  
82      }
83  
84      @Test
85      public void simpleList() throws JoranException {
86          configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleList.xml");
87          Logger logger = loggerContext.getLogger(this.getClass().getName());
88          Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
89          ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
90          assertNotNull(listAppender);
91          assertEquals(0, listAppender.list.size());
92          String msg = "hello world";
93          logger.debug(msg);
94          assertEquals(1, listAppender.list.size());
95          ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
96          assertEquals(msg, le.getMessage());
97      }
98  
99  
100     @Test
101     public void asyncWithMultipleAppendersInRoot() throws JoranException {
102         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "async/logback_1614.xml");
103         Logger logger = loggerContext.getLogger(this.getClass().getName());
104         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
105         AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
106         assertNotNull(asyncAppender);
107         ConsoleAppender<ILoggingEvent> console = (ConsoleAppender<ILoggingEvent>) root.getAppender("CONSOLE");
108         assertNotNull(console);
109         assertTrue(console.isStarted());
110         //assertEquals(0, listAppender.list.size());
111         String msg = "hello world";
112         logger.warn(msg);
113     }
114 
115     @Test
116     public void simpleListWithImports() throws JoranException {
117         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleListWithImports.xml");
118         Logger logger = loggerContext.getLogger(this.getClass().getName());
119         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
120         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
121         assertNotNull(listAppender);
122         assertEquals(0, listAppender.list.size());
123         String msg = "hello world";
124         logger.debug(msg);
125         assertEquals(1, listAppender.list.size());
126         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
127         assertEquals(msg, le.getMessage());
128     }
129 
130     @Test
131     public void level() throws JoranException {
132         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleLevel.xml");
133         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
134         assertEquals(0, listAppender.list.size());
135         String msg = "hello world";
136         logger.debug(msg);
137         assertEquals(0, listAppender.list.size());
138     }
139 
140     @Test
141     public void additivity() throws JoranException {
142         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "additivity.xml");
143         Logger logger = loggerContext.getLogger("additivityTest");
144         assertFalse(logger.isAdditive());
145     }
146 
147     @Test
148     public void rootLoggerLevelSettingBySystemProperty() throws JoranException {
149         String propertyName = "logback.level";
150 
151         System.setProperty(propertyName, "INFO");
152         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "rootLevelByProperty.xml");
153         // StatusPrinter.print(loggerContext);
154         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
155         assertEquals(0, listAppender.list.size());
156         String msg = "hello world";
157         logger.debug(msg);
158         assertEquals(0, listAppender.list.size());
159         System.clearProperty(propertyName);
160     }
161 
162     @Test
163     public void loggerLevelSettingBySystemProperty() throws JoranException {
164         String propertyName = "logback.level";
165         System.setProperty(propertyName, "DEBUG");
166         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "loggerLevelByProperty.xml");
167         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
168         assertEquals(0, listAppender.list.size());
169         String msg = "hello world";
170         logger.debug(msg);
171         assertEquals(1, listAppender.list.size());
172         System.clearProperty(propertyName);
173     }
174 
175     @Test
176     public void appenderRefSettingBySystemProperty() throws JoranException {
177         final String propertyName = "logback.appenderRef";
178         System.setProperty(propertyName, "A");
179         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefByProperty.xml");
180         final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
181         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) logger.getAppender("A");
182         // appender names or refs not subject to substitution
183         assertNull(listAppender);
184 
185         final String msg = "hello world";
186         logger.info(msg);
187 
188         System.clearProperty(propertyName);
189     }
190 
191     @Test
192     public void appenderRefSettingBySystemPropertyDefault() throws JoranException {
193 
194         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefByPropertyDefault.xml");
195         final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
196         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A");
197         // appender names not subjected to substitution
198         assertNull(listAppender);
199 
200     }
201 
202 
203     @Test
204     public void refToUndefinedAppender() throws JoranException {
205 
206         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "refToUndefinedAppender.xml");
207         //StatusPrinter.print(loggerContext);
208         final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
209         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A");
210         assertNotNull(listAppender);
211         assertEquals(0, listAppender.list.size());
212         final String msg = "hello world";
213         logger.info(msg);
214 
215         assertEquals(1, listAppender.list.size());
216 
217         checker.assertContainsMatch(Status.WARN, "Appender named \\[NON_EXISTENT_APPENDER\\] could not be found. Skipping attachment to Logger\\[ROOT\\]");
218     }
219 
220     @Test
221     public void refViaDefaultSubstitution() throws JoranException {
222 
223         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "refViaDefaultSubstitution.xml");
224         final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
225         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A");
226         final NOPAppender<ILoggingEvent> nopAppender = (NOPAppender) root.getAppender("NOP");
227 
228         assertNotNull(listAppender);
229         assertNull(nopAppender);
230 
231         assertEquals(0, listAppender.list.size());
232         final String msg = "hello world";
233         logger.info(msg);
234 
235         assertEquals(1, listAppender.list.size());
236 
237         checker.assertContainsMatch("Appender named \\[\\$\\{NONEXISTENT:-NOP\\}\\] could not be found.");
238     }
239 
240     @Test
241     public void fauxListener() throws JoranException {
242         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "fauxListener.xml");
243         //StatusPrinter.print(loggerContext);
244 
245         assertEquals(0,FauxListener.COUNT);
246         checker.assertContainsMatch(Status.ERROR, "Could not create component \\[listener\\] of type \\[ch.qos.logback.classic.joran.FauxListener\\]");
247 
248     }
249     @Test
250     public void statusListener() throws JoranException {
251         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "statusListener.xml");
252         //StatusPrinter.print(loggerContext);
253         checker.assertIsErrorFree();
254         checker.assertContainsMatch(Status.WARN,
255                 "Please use \"level\" attribute within <logger> or <root> elements instead.");
256     }
257 
258     @Test
259     public void statusListenerWithImports() throws JoranException {
260         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "statusListenerWithImports.xml");
261         //StatusPrinter.print(loggerContext);
262         checker.assertIsErrorFree();
263         checker.assertContainsMatch(Status.WARN,
264                 "Please use \"level\" attribute within <logger> or <root> elements instead.");
265     }
266 
267     @Test
268     public void contextRename() throws JoranException {
269         loggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
270         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "contextRename.xml");
271         assertEquals("wombat", loggerContext.getName());
272     }
273 
274     @Test
275     public void missingConfigurationElement() throws JoranException {
276         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/noConfig.xml");
277 
278         String msg1 = "Exception in body\\(\\) method for action \\[" + ParamAction.class.getName() + "\\]";
279         checker.assertContainsMatch(Status.ERROR, msg1);
280 
281         String msg2 = "current model is null. Is <configuration> element missing?";
282         checker.assertContainsException(ActionException.class, msg2);
283     }
284 
285     @Test
286     public void ignoreUnknownProperty() throws JoranException {
287 
288         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/unknownProperty.xml");
289         String msg = IGNORING_UNKNOWN_PROP + " \\[a\\] in \\[ch.qos.logback.classic.LoggerContext\\]";
290         checker.assertContainsMatch(Status.WARN, msg);
291     }
292 
293     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46995
294     @Test
295     public void complexCollectionWihhNoKnownClass() throws JoranException {
296 
297         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/nestedComplexWithNoKnownClass.xml");
298         String msg = "Could not find an appropriate class for property \\[listener\\]";
299         checker.assertContainsMatch(Status.ERROR, msg);
300     }
301 
302     @Test
303     public void turboFilter() throws JoranException {
304         // Although this test uses turbo filters, it only checks
305         // that Joran can see the xml element and create
306         // and place the relevant object correctly.
307         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turbo.xml");
308 
309         TurboFilter filter = loggerContext.getTurboFilterList().get(0);
310         assertTrue(filter instanceof NOPTurboFilter);
311     }
312 
313     @Test
314     public void testTurboFilterWithStringList() throws JoranException {
315         // Although this test uses turbo filters, it only checks
316         // that Joran can see <user> elements, and behave correctly
317         // that is call the addUser method and pass the correct values
318         // to that method.
319         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turbo2.xml");
320 
321         // StatusPrinter.print(loggerContext.getStatusManager());
322 
323         TurboFilter filter = loggerContext.getTurboFilterList().get(0);
324         assertTrue(filter instanceof DebugUsersTurboFilter);
325         DebugUsersTurboFilter dutf = (DebugUsersTurboFilter) filter;
326         assertEquals(2, dutf.getUsers().size());
327     }
328 
329     @Test
330     public void testLevelFilter() throws JoranException {
331         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "levelFilter.xml");
332 
333         // StatusPrinter.print(loggerContext);
334 
335         logger.warn("hello");
336         logger.error("to be ignored");
337 
338         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
339 
340         assertNotNull(listAppender);
341         assertEquals(1, listAppender.list.size());
342         ILoggingEvent back = listAppender.list.get(0);
343         assertEquals(Level.WARN, back.getLevel());
344         assertEquals("hello", back.getMessage());
345     }
346 
347 
348     @Test
349     public void testTurboDynamicThreshold() throws JoranException {
350         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turboDynamicThreshold.xml");
351 
352         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
353         assertEquals(0, listAppender.list.size());
354 
355         // this one should be denied
356         MDC.put("userId", "user1");
357         logger.debug("hello user1");
358         // this one should log
359         MDC.put("userId", "user2");
360         logger.debug("hello user2");
361 
362         assertEquals(1, listAppender.list.size());
363         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
364         assertEquals("hello user2", le.getMessage());
365     }
366 
367     @Test
368     public void testTurboDynamicThreshold2() throws JoranException {
369 
370         try {
371             configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turboDynamicThreshold2.xml");
372         } finally {
373             // StatusPrinter.print(loggerContext);
374         }
375         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
376         assertEquals(0, listAppender.list.size());
377 
378         // this one should log
379         MDC.put("userId", "user1");
380         logger.debug("hello user1");
381         // this one should log
382         MDC.put("userId", "user2");
383         logger.debug("hello user2");
384         // this one should fail
385         MDC.put("userId", "user3");
386         logger.debug("hello user3");
387 
388         assertEquals(2, listAppender.list.size());
389         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
390         assertEquals("hello user1", le.getMessage());
391         le = (ILoggingEvent) listAppender.list.get(1);
392         assertEquals("hello user2", le.getMessage());
393     }
394 
395     @Test
396     public void timestamp() throws JoranException, IOException, InterruptedException {
397 
398         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "timestamp-context.xml";
399         configure(configFileAsStr);
400 
401         String r = loggerContext.getProperty("testTimestamp");
402         assertNotNull(r);
403         CachingDateFormatter sdf = new CachingDateFormatter("yyyy-MM");
404         String expected = sdf.format(System.currentTimeMillis());
405         assertEquals(expected, r, "expected \"" + expected + "\" but got " + r);
406     }
407 
408     @Test
409     public void timestampLocal() throws JoranException, IOException, InterruptedException {
410 
411         String sysProp = "ch.qos.logback.classic.joran.JoranConfiguratorTest.timestampLocal";
412         System.setProperty(sysProp, "");
413 
414         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "timestamp-local.xml";
415         configure(configFileAsStr);
416 
417         // It's hard to test the local variable has been set, as it's not
418         // visible from here. But instead we test that it's not set in the
419         // context. And check that a system property has been replaced with the
420         // contents of the local variable
421 
422         String r = loggerContext.getProperty("testTimestamp");
423         assertNull(r);
424 
425         String expected = "today is " + new SimpleDateFormat("yyyy-MM").format(new Date());
426         String sysPropValue = System.getProperty(sysProp);
427         assertEquals(expected, sysPropValue);
428     }
429 
430     @Test
431     public void encoderCharset() throws JoranException, IOException, InterruptedException {
432 
433         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "encoderCharset.xml";
434         configure(configFileAsStr);
435 
436         ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) root.getAppender("CONSOLE");
437         assertNotNull(consoleAppender);
438         LayoutWrappingEncoder<ILoggingEvent> encoder = (LayoutWrappingEncoder<ILoggingEvent>) consoleAppender
439                 .getEncoder();
440 
441         assertEquals("UTF-8", encoder.getCharset().displayName());
442 
443         checker.assertIsErrorFree();
444     }
445 
446 
447 
448     @Disabled // because slow
449     @Test
450     public void onConsoleRetro() throws JoranException, IOException, InterruptedException {
451         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "/onConsoleRetro.xml";
452         configure(configFileAsStr);
453         Thread.sleep(400);
454 
455         loggerContext.reset();
456         configure(configFileAsStr);
457     }
458 
459     @Test
460     public void unreferencedAppenderShouldNotTriggerUnknownPropertyMessages() throws JoranException {
461         String configFileAsStr = ClassicTestConstants.ISSUES_PREFIX + "/logback1572.xml";
462         configure(configFileAsStr);
463         checker.assertContainsMatch(Status.WARN,
464                 "Appender named \\[EMAIL\\] not referenced. Skipping further processing.");
465         checker.assertNoMatch(IGNORING_UNKNOWN_PROP + " \\[evaluator\\]");
466     }
467 
468     @Test
469     public void LOGBACK_111() throws JoranException {
470         String configFileAsStr = ClassicTestConstants.ISSUES_PREFIX + "lbcore193.xml";
471         configure(configFileAsStr);
472         checker.assertContainsException(ScanException.class);
473         checker.assertContainsMatch(Status.ERROR, "Expecting RIGHT_PARENTHESIS token but got null");
474         checker.assertContainsMatch(Status.ERROR, "See also " + Parser.MISSING_RIGHT_PARENTHESIS);
475     }
476 
477     @Test
478     public void properties() throws JoranException {
479         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "properties.xml";
480         assertNull(System.getProperty("sys"));
481 
482         configure(configFileAsStr);
483         assertNotNull(loggerContext.getProperty(CoreConstants.HOSTNAME_KEY));
484         assertNull(loggerContext.getProperty("transientKey1"));
485         assertNull(loggerContext.getProperty("transientKey2"));
486         assertEquals("node0", loggerContext.getProperty("nodeId"));
487         assertEquals("tem", System.getProperty("sys"));
488         assertNotNull(loggerContext.getProperty("path"));
489         checker.assertIsErrorFree();
490     }
491 
492     @Test
493     public void hostnameProperty() throws JoranException {
494         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "hostnameProperty.xml";
495         configure(configFileAsStr);
496         assertEquals("A", loggerContext.getProperty(CoreConstants.HOSTNAME_KEY));
497     }
498 
499     // see also http://jira.qos.ch/browse/LOGBACK-134
500     @Test
501     public void sysProps() throws JoranException {
502         System.setProperty("k.lbcore254", ClassicTestConstants.ISSUES_PREFIX + "lbcore254");
503         JoranConfigurator configurator = new JoranConfigurator();
504         configurator.setContext(loggerContext);
505         configurator.doConfigure(ClassicTestConstants.ISSUES_PREFIX + "lbcore254.xml");
506 
507         checker.assertIsErrorFree();
508     }
509 
510     @Test
511     public void propsWithMissingRightCurlyBrace() throws JoranException {
512         System.setProperty("abc", "not important");
513         JoranConfigurator configurator = new JoranConfigurator();
514         configurator.setContext(loggerContext);
515         configurator.doConfigure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propsMissingRightCurlyBrace.xml");
516         checker.assertContainsMatch(Status.ERROR, "Problem while parsing");
517     }
518 
519     @Test
520     public void packageDataDisabledByConfigAttribute() throws JoranException {
521         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "packagingDataDisabled.xml";
522         configure(configFileAsStr);
523         assertFalse(loggerContext.isPackagingDataEnabled());
524     }
525 
526     @Test
527     public void packageDataEnabledByConfigAttribute() throws JoranException {
528         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "packagingDataEnabled.xml";
529         try {
530             configure(configFileAsStr);
531         } finally {
532             // StatusPrinter.print(loggerContext);
533         }
534         assertTrue(loggerContext.isPackagingDataEnabled());
535     }
536 
537     @Test
538     public void valueOfConvention() throws JoranException {
539         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "valueOfConvention.xml";
540         configure(configFileAsStr);
541         checker.assertIsWarningOrErrorFree();
542     }
543 
544     @Test
545     public void shutdownHookTest() throws JoranException {
546         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1162.xml";
547         loggerContext.putProperty("output_dir", ClassicTestConstants.OUTPUT_DIR_PREFIX + "logback_issue_1162/");
548         configure(configFileAsStr);
549         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
550         assertNotNull(thread);
551     }
552 
553 
554     @Test
555     public void nestedAppendersDisallowed() throws JoranException {
556         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1674.xml";
557         configure(configFileAsStr);
558         checker.assertContainsMatch(Status.WARN, NESTED_APPENDERS_WARNING);
559         checker.assertContainsMatch(Status.WARN, "Appender at line ");
560     }
561 
562     @Test
563     public void shutdownHookWithDelayParameter() throws JoranException {
564         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1672.xml";
565         configure(configFileAsStr);
566 
567         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
568         assertNotNull(thread);
569         checker.assertNoMatch(IGNORING_UNKNOWN_PROP);
570     }
571 
572     @Test
573     public void migrateShutdownHookClassName() throws JoranException {
574         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1678_shutdown.xml";
575         configure(configFileAsStr);
576 
577         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
578         assertNotNull(thread);
579         checker.assertContainsMatch(RENAME_WARNING);
580     }
581 
582     @Test
583     public void appenderRefBeforeAppenderTest() throws JoranException {
584         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefBeforeAppender.xml";
585         configure(configFileAsStr);
586         Logger logger = loggerContext.getLogger(this.getClass().getName());
587         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
588         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
589         assertNotNull(listAppender);
590         assertEquals(0, listAppender.list.size());
591         String msg = "hello world";
592         logger.debug(msg);
593         assertEquals(1, listAppender.list.size());
594         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
595         assertEquals(msg, le.getMessage());
596         checker.assertIsErrorFree();
597     }
598 
599     @Test
600     public void unreferencedAppendersShouldBeSkipped() throws JoranException {
601         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "unreferencedAppender1.xml");
602 
603         final ListAppender<ILoggingEvent> listAppenderA = (ListAppender<ILoggingEvent>) root.getAppender("A");
604         assertNotNull(listAppenderA);
605         checker.assertContainsMatch(Status.WARN, "Appender named \\[B\\] not referenced. Skipping further processing.");
606     }
607 
608     @Test
609     public void asynAppenderListFirst() throws JoranException {
610         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "asyncAppender_list_first.xml");
611 
612         final AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
613         assertNotNull(asyncAppender);
614         assertTrue(asyncAppender.isStarted());
615     }
616 
617     @Test
618     public void asynAppenderListAfter() throws JoranException {
619         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "asyncAppender_list_after.xml");
620         StatusPrinter.print(loggerContext);
621         final AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
622         assertNotNull(asyncAppender);
623         assertTrue(asyncAppender.isStarted());
624     }
625 
626     // https://jira.qos.ch/browse/LOGBACK-1570
627     @Test
628     public void missingPropertyErrorHandling() throws JoranException {
629         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "missingProperty.xml");
630 
631         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
632         assertNotNull(listAppender);
633         assertTrue(listAppender.isStarted());
634         checker.assertContainsMatch(Status.WARN,
635                 IGNORING_UNKNOWN_PROP + " \\[inexistent\\] in \\[ch.qos.logback.core.read.ListAppender\\]");
636     }
637 
638     @Test
639     public void sequenceNumberGenerator() throws JoranException {
640         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "sequenceNumberGenerator.xml");
641         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
642         assertNotNull(listAppender);
643 
644         logger.atDebug().setMessage("hello").log();
645         logger.atDebug().setMessage("world").log();
646 
647         ILoggingEvent le0 = listAppender.list.get(0);
648         ILoggingEvent le1 = listAppender.list.get(1);
649 
650         long se0 = le0.getSequenceNumber();
651         long se1 = le1.getSequenceNumber();
652         assertEquals(1, se1 - se0);
653     }
654 
655     @Test
656     public void sequenceNumberGenerator_missingClass() throws JoranException {
657         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "sequenceNumberGenerator-missingClass.xml");
658         //StatusPrinter.print(loggerContext);
659         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
660         assertNotNull(listAppender);
661         checker.assertContainsMatch(Status.ERROR, "Missing attribute \\[class\\]. See element \\[sequenceNumberGenerator\\]");
662     }
663 
664     @Test
665     public void kvp() throws JoranException {
666         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "pattern/kvp.xml");
667 
668         String msg = "hello kvp";
669 
670         KeyValuePair kvp1 = new KeyValuePair("k" + diff, "v" + diff);
671         KeyValuePair kvp2 = new KeyValuePair("k" + (diff + 1), "v" + (diff + 1));
672         KeyValuePair kvpNullKey = new KeyValuePair(null, "v" + (diff + 2));
673         KeyValuePair kvpNullValue = new KeyValuePair("k" + (diff + 3), null);
674 
675         logger.atDebug().addKeyValue(kvp1.key, kvp1.value).log(msg);
676         logger.atDebug().addKeyValue(kvp2.key, kvp2.value).log(msg);
677         logger.atDebug().addKeyValue(kvpNullKey.key, kvpNullKey.value).log(msg);
678         logger.atDebug().addKeyValue(kvpNullValue.key, kvpNullValue.value).log(msg);
679 
680         StringListAppender<ILoggingEvent> slAppender = (StringListAppender<ILoggingEvent>) loggerContext
681                 .getLogger("root").getAppender("LIST");
682         assertNotNull(slAppender);
683         assertEquals(4, slAppender.strList.size());
684         assertTrue(slAppender.strList.get(0).contains(kvp1.key + "=\"" + kvp1.value + "\" " + msg));
685         assertTrue(slAppender.strList.get(1).contains(kvp2.key + "=\"" + kvp2.value + "\" " + msg));
686         assertTrue(slAppender.strList.get(2).contains("null=\"" + kvpNullKey.value + "\" " + msg));
687         assertTrue(slAppender.strList.get(3).contains(kvpNullValue.key + "=\"null\" " + msg));
688     }
689 
690 
691     // See LOGBACK-1746
692     @Test
693     public void inclusionWithVariables() throws JoranException {
694         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "include/topLevel0.xml");
695         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
696         //statusPrinter2.print(loggerContext);
697         assertEquals(Level.ERROR, root.getLevel());
698     }
699 
700     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46697
701     @Test
702     public void ossFuzz_46697() throws JoranException {
703         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-46697.xml");
704 
705         checker.assertContainsMatch(Status.ERROR, ErrorCodes.EMPTY_MODEL_STACK);
706     }
707 
708     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47093
709     // In previous versions of the code, we honored String literal
710     // escape sequences for the 'value' attribute named 'value'. After
711     // analysis this was deemed superfluous.
712     @Test
713     public void ossFuzz_47093() throws JoranException {
714         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47093.xml");
715         assertEquals("a\\t", loggerContext.getProperty("fuzz-47093-a"));
716         assertEquals("a\\\\", loggerContext.getProperty("fuzz-47093-b"));
717     }
718 
719     @Test
720     public void ossFuzz_41117() throws JoranException {
721         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117.xml");
722         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
723         checker.assertErrorCount(2);
724         //StatusPrinter.print(loggerContext);
725     }
726 
727     @Test
728     public void ossFuzz_41117_bis() throws JoranException {
729         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117-bis.xml");
730         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
731     }
732 
733     @Test
734     public void ossFuzz_41117_bis2() throws JoranException {
735         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117-bis2.xml");
736         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
737     }
738 
739     @Test
740     public void ossFuzz_47293() throws JoranException {
741         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47293.xml");
742         checker.assertContainsMatch(Status.ERROR, ErrorCodes.MISSING_IF_EMPTY_MODEL_STACK);
743     }
744 
745 
746     @Test
747     public void dateConverterWithLocale() throws JoranException {
748         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "dateWithLocale.xml");
749         checker.assertContainsMatch(Status.INFO, "Setting zoneId to \"Australia/Perth\"");
750         checker.assertContainsMatch(Status.INFO, "Setting locale to \"en_AU\"");
751         //StatusPrinter.print(loggerContext);
752     }
753 
754     @Test
755     public void propertyConfiguratorSmoke() throws JoranException {
756         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propertiesConfigurator/smoke.xml");
757         Logger com_foo_Logger = loggerContext.getLogger("com.toto");
758         //StatusPrinter.print(loggerContext);
759         assertEquals(Level.WARN, com_foo_Logger.getLevel());
760 
761 
762     }
763 
764     @Test
765     public void consoleCharsetTest() throws JoranException {
766         if (EnvUtil.isJDK21OrHigher()) {
767             configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "consoleCharset.xml");
768             checker.assertContainsMatch(Status.INFO, "About to instantiate property definer of type \\[ch.qos.logback.core.property.ConsoleCharsetPropertyDefiner\\]");
769 
770             Console console = System.console();
771             if(console == null) {
772                 checker.assertContainsMatch(Status.WARN, "System.console\\(\\) returned null. Cannot compute console's charset, returning");
773             } else {
774 
775                 boolean nullCharset =  checker.containsMatch("System.console() returned null charset. Returning \"NULL\" string as defined value.");
776                 boolean foundCharset = checker.containsMatch("Found value '.*' as returned by System.console().");
777 
778             }
779             //StatusPrinter.print(loggerContext);
780         }
781     }
782 
783     @Test
784     public void levelFromAPropertyTest() throws JoranException {
785 
786 
787         String loggerASysLevelKey = "LOGGER_A_SYS_LEVEL";
788         String loggerNestedSysLevelKey = "LOGGER_NESTED_SYS_LEVEL";
789         System.setProperty(loggerASysLevelKey, "WARN");
790         System.setProperty(loggerNestedSysLevelKey, "ERROR");
791         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "levelFromAProperty.xml");
792 
793 
794         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
795 
796 
797         Logger loggerA = loggerContext.getLogger("A");
798         Logger loggerA_SYS = loggerContext.getLogger("A_SYS");
799 
800         Logger loggerNESTED = loggerContext.getLogger("NESTED");
801 
802         Logger loggerNESTED_SYS = loggerContext.getLogger("NESTED_SYS");
803 
804 
805         assertEquals(Level.TRACE, root.getLevel());
806         assertEquals(Level.WARN, loggerA.getLevel());
807         assertEquals(Level.WARN, loggerA_SYS.getLevel());
808 
809         assertEquals(Level.ERROR, loggerNESTED.getLevel());
810         assertEquals(Level.ERROR, loggerNESTED_SYS.getLevel());
811 
812         checker.assertContainsMatch(Status.INFO, "value \\\"WARN\\\" substituted for \\\"\\$\\{LOGGER_A_LEVEL\\}\\\"");
813 
814         System.clearProperty(loggerASysLevelKey);
815         System.clearProperty(loggerNestedSysLevelKey);
816 
817     }
818 
819     @Test
820     public void exceptionEventFilter() throws JoranException {
821         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "boolex/exceptionEvaluator.xml");
822         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
823         root.info("deny");
824         root.info("allow", new RuntimeException("test"));
825 
826         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
827         assertNotNull(listAppender);
828         assertEquals(1, listAppender.list.size());
829 
830         ILoggingEvent le = listAppender.list.get(0);
831 
832         assertNotNull(le.getThrowableProxy());
833         assertEquals(RuntimeException.class.getName(), le.getThrowableProxy().getClassName());
834 
835     }
836 
837     @Test
838     public void modelSerialization() throws JoranException, IOException, ClassNotFoundException {
839         String outputPath = OUTPUT_DIR_PREFIX + "minimal_" + diff + MODEL_CONFIG_FILE_EXTENSION;
840 
841         loggerContext.putProperty("targetModelFile", outputPath);
842         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "model/minimal.xml");
843         StatusPrinter.print(loggerContext);
844 
845         FileInputStream fis = new FileInputStream(outputPath);
846         HardenedModelInputStream hmis = new HardenedModelInputStream(fis);
847 
848         Model model = (Model) hmis.readObject();
849 
850         assertNotNull(model);
851         assertTrue(model instanceof ConfigurationModel);
852 
853         ConfigurationModel configurationModel = (ConfigurationModel) model;
854 
855         assertEquals("false", configurationModel.getDebugStr());
856 
857         assertEquals(2, configurationModel.getSubModels().size());
858 
859         SerializeModelModel smm = (SerializeModelModel) configurationModel.getSubModels().get(0);
860         assertEquals("${targetModelFile}", smm.getFile());
861 
862 
863         LoggerModel loggerModel = (LoggerModel) configurationModel.getSubModels().get(1);
864         assertEquals("ModelSerializationTest", loggerModel.getName());
865 
866         //    <serializeModel file="${targetModelFile}"/>
867         //    <logger name="ModelSerializationTest" level="DEBUG"/>
868     }
869 
870 
871     // reproduction requires placing a binary properties file. Probably not worth the effort.
872 //    @Test
873 //    public void ossFuzz_47249() throws JoranException  {
874 //        configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47249.xml");
875 //         StatusPrinter.print(loggerContext);
876 //    }
877 
878 //	@Test
879 //	public void doTest() throws JoranException {
880 //		int LIMIT = 0;
881 //		boolean oss = true;
882 //		for (int i = 0; i < LIMIT; i++) {
883 //			innerDoT(oss);
884 //		}
885 //		long start = System.currentTimeMillis();
886 //		innerDoT(oss);
887 //		long diff = System.currentTimeMillis() - start;
888 //		double average = (1.0d * diff);
889 //		System.out.println("Average time " + average + " ms. By serialization " + oss);
890 //
891 //	}
892 
893 //	private void innerDoT(boolean oss) throws JoranException {
894 //		JoranConfigurator jc = new JoranConfigurator();
895 //		jc.setContext(loggerContext);
896 //		if (oss) {
897 //			System.out.println("jc.doT");
898 //			jc.doT();
899 //		} else {
900 //			System.out.println("jc.doConfigure");
901 //			jc.doConfigure(ClassicTestConstants.JORAN_INPUT_PREFIX + "twoAppenders.xml");
902 //		}
903 //	}
904 
905 }