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