void writeToFile(File file, String contents) throws IOException {
   FileWriter fw = new FileWriter(file);
   fw.write(contents);
   fw.close();
   // on linux changes to last modified are not propagated if the
   // time stamp is near the previous time stamp hence the random delta
   file.setLastModified(System.currentTimeMillis() + RandomUtil.getPositiveInt());
 }
public class MDCConverterTest {

  LoggerContext lc;
  MDCConverter converter;
  int diff = RandomUtil.getPositiveInt();

  @Before
  public void setUp() throws Exception {
    lc = new LoggerContext();
    converter = new MDCConverter();
    converter.start();
    MDC.clear();
  }

  @After
  public void tearDown() throws Exception {
    lc = null;
    converter.stop();
    converter = null;
    MDC.clear();
  }

  @Test
  public void testConvertWithOneEntry() {
    String k = "MDCConverterTest_k" + diff;
    String v = "MDCConverterTest_v" + diff;

    MDC.put(k, v);
    ILoggingEvent le = createLoggingEvent();
    String result = converter.convert(le);
    assertEquals(k + "=" + v, result);
  }

  @Test
  public void testConvertWithMultipleEntries() {
    MDC.put("testKey", "testValue");
    MDC.put("testKey2", "testValue2");
    ILoggingEvent le = createLoggingEvent();
    String result = converter.convert(le);
    boolean isConform = result.matches("testKey2?=testValue2?, testKey2?=testValue2?");
    assertTrue(result + " is not conform", isConform);
  }

  private ILoggingEvent createLoggingEvent() {
    return new LoggingEvent(
        this.getClass().getName(),
        lc.getLogger(Logger.ROOT_LOGGER_NAME),
        Level.DEBUG,
        "test message",
        null,
        null);
  }
}
  @Test
  public void tException() throws InterruptedException {
    int port = RandomUtil.getRandomServerPort();

    MockSyslogServer mockServer = new MockSyslogServer(21, port);
    mockServer.start();
    // give MockSyslogServer head start
    Thread.sleep(100);

    LoggerContext lc = new LoggerContext();
    lc.setName("test");
    SyslogAppender sa = new SyslogAppender();
    sa.setContext(lc);
    sa.setSyslogHost("localhost");
    sa.setFacility("MAIL");
    sa.setPort(port);
    sa.setSuffixPattern("[%thread] %logger %msg");
    sa.start();
    assertTrue(sa.isStarted());

    String loggerName = this.getClass().getName();
    Logger logger = lc.getLogger(loggerName);
    logger.addAppender(sa);
    String logMsg = "hello";
    String exMsg = "just testing";
    Exception ex = new Exception(exMsg);
    logger.debug(logMsg, ex);
    // StatusPrinter.print(lc.getStatusManager());

    // wait max 2 seconds for mock server to finish. However, it should
    // much sooner than that.
    mockServer.join(8000);
    assertTrue(mockServer.isFinished());

    // message + 20 lines of stacktrace
    assertEquals(21, mockServer.getMessageList().size());
    // int i = 0;
    // for (String line: mockServer.msgList) {
    // System.out.println(i++ + ": " + line);
    // }

    String msg = mockServer.getMessageList().get(0);
    String expected = "<" + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    String expectedPrefix = "<\\d{2}>\\w{3} \\d{2} \\d{2}(:\\d{2}){2} [\\w.-]* ";
    String threadName = Thread.currentThread().getName();
    String regex = expectedPrefix + "\\[" + threadName + "\\] " + loggerName + " " + logMsg;
    checkRegexMatch(msg, regex);
  }
public class LevelChangePropagatorTest {
  int rand = RandomUtil.getPositiveInt();
  LoggerContext loggerContext = new LoggerContext();
  LevelChangePropagator levelChangePropagator = new LevelChangePropagator();

  @Before
  public void setUp() {
    levelChangePropagator.setContext(loggerContext);
    loggerContext.addListener(levelChangePropagator);
  }

  void checkLevelChange(String loggerName, Level level) {
    Logger logger = loggerContext.getLogger(loggerName);
    logger.setLevel(level);
    java.util.logging.Logger julLogger = JULHelper.asJULLogger(logger);
    java.util.logging.Level julLevel = JULHelper.asJULLevel(level);

    assertEquals(julLevel, julLogger.getLevel());
  }

  @Test
  public void smoke() {
    checkLevelChange("a", Level.INFO);
    checkLevelChange("a.b", Level.DEBUG);
  }

  @Test
  public void root() {
    checkLevelChange(Logger.ROOT_LOGGER_NAME, Level.TRACE);
  }

  // see http://jira.qos.ch/browse/LBCLASSIC-256
  @Test
  public void gc() {
    Logger logger = loggerContext.getLogger("gc" + rand);
    logger.setLevel(Level.INFO);
    // invoke GC so that the relevant julLogger can be garbage collected.
    System.gc();
    java.util.logging.Logger julLogger = JULHelper.asJULLogger(logger);
    java.util.logging.Level julLevel = JULHelper.asJULLevel(Level.INFO);

    assertEquals(julLevel, julLogger.getLevel());
  }
}
  @Test
  public void basic() throws InterruptedException {
    int port = RandomUtil.getRandomServerPort();

    MockSyslogServer mockServer = new MockSyslogServer(1, port);
    mockServer.start();
    // give MockSyslogServer head start
    Thread.sleep(100);

    LoggerContext lc = new LoggerContext();
    lc.setName("test");
    SyslogAppender sa = new SyslogAppender();
    sa.setContext(lc);
    sa.setSyslogHost("localhost");
    sa.setFacility("MAIL");
    sa.setPort(port);
    sa.setSuffixPattern("[%thread] %logger %msg");
    sa.start();
    assertTrue(sa.isStarted());

    String loggerName = this.getClass().getName();
    Logger logger = lc.getLogger(loggerName);
    logger.addAppender(sa);
    String logMsg = "hello";
    logger.debug(logMsg);

    // wait max 2 seconds for mock server to finish. However, it should
    // much sooner than that.
    mockServer.join(8000);
    assertTrue(mockServer.isFinished());
    assertEquals(1, mockServer.getMessageList().size());
    String msg = mockServer.getMessageList().get(0);

    String threadName = Thread.currentThread().getName();

    String expected = "<" + (SyslogConstants.LOG_MAIL + SyslogConstants.DEBUG_SEVERITY) + ">";
    assertTrue(msg.startsWith(expected));

    String first = "<\\d{2}>\\w{3} \\d{2} \\d{2}(:\\d{2}){2} [\\w.-]* ";
    checkRegexMatch(msg, first + "\\[" + threadName + "\\] " + loggerName + " " + logMsg);
  }
  @Test
  public void withMissingTargetDir() throws Exception {
    String testId = "missingTargetDir";

    initRFA(rfa1, testId2FileName(testId));
    int secondDiff = RandomUtil.getPositiveInt();
    String randomTargetDir = CoreTestConstants.OUTPUT_DIR_PREFIX + secondDiff + '/';

    System.out.println("randomOutputDir" + randomOutputDir);
    System.out.println("randomTargetDir" + randomTargetDir);

    initTRBP(
        rfa1,
        tbrp1,
        randomTargetDir + testId + "-%d{" + DATE_PATTERN_WITH_SECONDS + "}",
        currentTime);

    addExpectedFileName_ByDate(randomTargetDir, testId, getDateOfCurrentPeriodsStart(), false);

    incCurrentTime(1100);
    tbrp1.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);

    for (int i = 0; i < 3; i++) {
      rfa1.doAppend("Hello---" + i);
      addExpectedFileNamedIfItsTime_ByDate(randomTargetDir, testId, false);
      incCurrentTime(500);
      tbrp1.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);
    }
    massageExpectedFilesToCorresponToCurrentTarget("missingTargetDir.log");
    int i = 0;
    for (String fn : expectedFilenameList) {
      System.out.println("expectedFile=" + fn);
      assertTrue(
          Compare.compare(
              fn, CoreTestConstants.TEST_SRC_PREFIX + "witness/rolling/tbr-test5." + i++));
    }
  }
public class SMTPAppender_GreenTest {

  static final String HEADER = "HEADER\n";
  static final String FOOTER = "FOOTER\n";
  static final String DEFAULT_PATTERN = "%-4relative %mdc [%thread] %-5level %class - %msg%n";

  static final boolean SYNCHRONOUS = false;
  static final boolean ASYNCHRONOUS = true;

  int port = RandomUtil.getRandomServerPort();
  // GreenMail cannot be static. As a shared server induces race conditions
  GreenMail greenMailServer;

  SMTPAppender smtpAppender;
  LoggerContext loggerContext = new LoggerContext();
  Logger logger = loggerContext.getLogger(this.getClass());

  @Before
  public void setUp() throws Exception {

    OnConsoleStatusListener.addNewInstanceToContext(loggerContext);
    MDC.clear();
    ServerSetup serverSetup = new ServerSetup(port, "localhost", ServerSetup.PROTOCOL_SMTP);
    greenMailServer = new GreenMail(serverSetup);
    greenMailServer.start();
    // give the server a head start
    if (EnvUtilForTests.isRunningOnSlowJenkins()) {
      Thread.sleep(2000);
    } else {
      Thread.sleep(50);
    }
  }

  @After
  public void tearDown() throws Exception {
    greenMailServer.stop();
  }

  void buildSMTPAppender(String subject, boolean synchronicity) throws Exception {
    smtpAppender = new SMTPAppender();
    smtpAppender.setContext(loggerContext);
    smtpAppender.setName("smtp");
    smtpAppender.setFrom("*****@*****.**");
    smtpAppender.setSMTPHost("localhost");
    smtpAppender.setSMTPPort(port);
    smtpAppender.setSubject(subject);
    smtpAppender.addTo("*****@*****.**");
    smtpAppender.setAsynchronousSending(synchronicity);
  }

  private Layout<ILoggingEvent> buildPatternLayout(String pattern) {
    PatternLayout layout = new PatternLayout();
    layout.setContext(loggerContext);
    layout.setFileHeader(HEADER);
    layout.setOutputPatternAsHeader(false);
    layout.setPattern(pattern);
    layout.setFileFooter(FOOTER);
    layout.start();
    return layout;
  }

  private Layout<ILoggingEvent> buildHTMLLayout() {
    HTMLLayout layout = new HTMLLayout();
    layout.setContext(loggerContext);
    layout.setPattern("%level%class%msg");
    layout.start();
    return layout;
  }

  private void waitForServerToReceiveEmails(int emailCount) throws InterruptedException {
    greenMailServer.waitForIncomingEmail(5000, emailCount);
  }

  private MimeMultipart verifyAndExtractMimeMultipart(String subject)
      throws MessagingException, IOException, InterruptedException {
    int oldCount = 0;
    int expectedEmailCount = 1;
    // wait for the server to receive the messages
    waitForServerToReceiveEmails(expectedEmailCount);
    MimeMessage[] mma = greenMailServer.getReceivedMessages();
    assertNotNull(mma);
    assertEquals(expectedEmailCount, mma.length);
    MimeMessage mm = mma[oldCount];
    // http://jira.qos.ch/browse/LBCLASSIC-67
    assertEquals(subject, mm.getSubject());
    return (MimeMultipart) mm.getContent();
  }

  void waitUntilEmailIsSent() throws InterruptedException {
    loggerContext.getExecutorService().shutdown();
    loggerContext.getExecutorService().awaitTermination(1000, TimeUnit.MILLISECONDS);
  }

  @Test
  public void synchronousSmoke() throws Exception {
    String subject = "synchronousSmoke";
    buildSMTPAppender(subject, SYNCHRONOUS);

    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("hello");
    logger.error("en error", new Exception("an exception"));

    MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertTrue(body.startsWith(HEADER.trim()));
    assertTrue(body.endsWith(FOOTER.trim()));
  }

  @Test
  public void asynchronousSmoke() throws Exception {
    String subject = "asynchronousSmoke";
    buildSMTPAppender(subject, ASYNCHRONOUS);
    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("hello");
    logger.error("en error", new Exception("an exception"));

    waitUntilEmailIsSent();
    MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertTrue(body.startsWith(HEADER.trim()));
    assertTrue(body.endsWith(FOOTER.trim()));
  }

  // See also http://jira.qos.ch/browse/LOGBACK-734
  @Test
  public void callerDataShouldBeCorrectlySetWithAsynchronousSending() throws Exception {
    String subject = "LOGBACK-734";
    buildSMTPAppender("LOGBACK-734", ASYNCHRONOUS);
    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.setIncludeCallerData(true);
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("LOGBACK-734");
    logger.error("callerData", new Exception("ShouldBeCorrectlySetWithAsynchronousSending"));

    waitUntilEmailIsSent();
    MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertTrue(
        "actual [" + body + "]",
        body.contains("DEBUG " + this.getClass().getName() + " - LOGBACK-734"));
  }

  // lost MDC
  @Test
  public void LBCLASSIC_104() throws Exception {
    String subject = "LBCLASSIC_104";
    buildSMTPAppender(subject, SYNCHRONOUS);
    smtpAppender.setAsynchronousSending(false);
    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    MDC.put("key", "val");
    logger.debug("LBCLASSIC_104");
    MDC.clear();
    logger.error("en error", new Exception("test"));

    MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertTrue(body.startsWith(HEADER.trim()));
    System.out.println(body);
    assertTrue(body.contains("key=val"));
    assertTrue(body.endsWith(FOOTER.trim()));
  }

  @Test
  public void html() throws Exception {
    String subject = "html";
    buildSMTPAppender(subject, SYNCHRONOUS);
    smtpAppender.setAsynchronousSending(false);
    smtpAppender.setLayout(buildHTMLLayout());
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("html");
    logger.error("en error", new Exception("an exception"));

    MimeMultipart mp = verifyAndExtractMimeMultipart(subject);

    // verifyAndExtractMimeMultipart strict adherence to xhtml1-strict.dtd
    SAXReader reader = new SAXReader();
    reader.setValidation(true);
    reader.setEntityResolver(new XHTMLEntityResolver());
    byte[] messageBytes = getAsByteArray(mp.getBodyPart(0).getInputStream());
    ByteArrayInputStream bais = new ByteArrayInputStream(messageBytes);
    try {
      reader.read(bais);
    } catch (DocumentException de) {
      System.out.println("incoming message:");
      System.out.println(new String(messageBytes));
      throw de;
    }
  }

  private byte[] getAsByteArray(InputStream inputStream) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    byte[] buffer = new byte[1024];
    int n = -1;
    while ((n = inputStream.read(buffer)) != -1) {
      baos.write(buffer, 0, n);
    }
    return baos.toByteArray();
  }

  private void configure(String file) throws JoranException {
    JoranConfigurator jc = new JoranConfigurator();
    jc.setContext(loggerContext);
    loggerContext.putProperty("port", "" + port);
    jc.doConfigure(file);
  }

  @Test
  public void testCustomEvaluator() throws Exception {
    configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customEvaluator.xml");

    logger.debug("test");
    String msg2 = "CustomEvaluator";
    logger.debug(msg2);
    logger.debug("invisible");
    waitUntilEmailIsSent();
    MimeMultipart mp =
        verifyAndExtractMimeMultipart(
            "testCustomEvaluator " + this.getClass().getName() + " - " + msg2);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertEquals("testCustomEvaluator", body);
  }

  @Test
  public void testCustomBufferSize() throws Exception {
    configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customBufferSize.xml");

    logger.debug("invisible1");
    logger.debug("invisible2");
    String msg = "hello";
    logger.error(msg);
    waitUntilEmailIsSent();
    MimeMultipart mp =
        verifyAndExtractMimeMultipart(
            "testCustomBufferSize " + this.getClass().getName() + " - " + msg);
    String body = GreenMailUtil.getBody(mp.getBodyPart(0));
    assertEquals(msg, body);
  }

  // this test fails intermittently on Jenkins.
  @Test
  public void testMultipleTo() throws Exception {
    buildSMTPAppender("testMultipleTo", SYNCHRONOUS);
    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    // buildSMTPAppender() already added one destination address
    smtpAppender.addTo("Test <*****@*****.**>, [email protected]");
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("testMultipleTo hello");
    logger.error("testMultipleTo en error", new Exception("an exception"));
    Thread.yield();
    int expectedEmailCount = 3;
    waitForServerToReceiveEmails(expectedEmailCount);
    MimeMessage[] mma = greenMailServer.getReceivedMessages();
    assertNotNull(mma);
    assertEquals(expectedEmailCount, mma.length);
  }

  // http://jira.qos.ch/browse/LBCLASSIC-221
  @Test
  public void bufferShouldBeResetBetweenMessages() throws Exception {
    buildSMTPAppender("bufferShouldBeResetBetweenMessages", SYNCHRONOUS);
    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    String msg0 = "hello zero";
    logger.debug(msg0);
    logger.error("error zero");

    String msg1 = "hello one";
    logger.debug(msg1);
    logger.error("error one");

    Thread.yield();
    int oldCount = 0;
    int expectedEmailCount = oldCount + 2;
    waitForServerToReceiveEmails(expectedEmailCount);

    MimeMessage[] mma = greenMailServer.getReceivedMessages();
    assertNotNull(mma);
    assertEquals(expectedEmailCount, mma.length);

    MimeMessage mm0 = mma[oldCount];
    MimeMultipart content0 = (MimeMultipart) mm0.getContent();
    String body0 = GreenMailUtil.getBody(content0.getBodyPart(0));

    MimeMessage mm1 = mma[oldCount + 1];
    MimeMultipart content1 = (MimeMultipart) mm1.getContent();
    String body1 = GreenMailUtil.getBody(content1.getBodyPart(0));
    // second body should not contain content from first message
    assertFalse(body1.contains(msg0));
  }

  @Test
  public void multiLineSubjectTruncatedAtFirstNewLine() throws Exception {
    String line1 = "line 1 of subject";
    String subject = line1 + "\nline 2 of subject\n";
    buildSMTPAppender(subject, ASYNCHRONOUS);

    smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
    smtpAppender.start();
    logger.addAppender(smtpAppender);
    logger.debug("hello");
    logger.error("en error", new Exception("an exception"));

    Thread.yield();
    waitUntilEmailIsSent();
    waitForServerToReceiveEmails(1);

    MimeMessage[] mma = greenMailServer.getReceivedMessages();
    assertEquals(1, mma.length);
    assertEquals(line1, mma[0].getSubject());
  }
}
public class ConditionalTest {

  LoggerContext context = new LoggerContext();
  Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);

  int diff = RandomUtil.getPositiveInt();
  String randomOutputDir = CoreTestConstants.OUTPUT_DIR_PREFIX + diff + "/";

  @Before
  public void setUp() throws UnknownHostException {
    context.setName("c" + diff);
    context.putProperty("randomOutputDir", randomOutputDir);
  }

  @After
  public void tearDown() {
    StatusPrinter.printIfErrorsOccured(context);
  }

  void configure(String file) throws JoranException {
    JoranConfigurator jc = new JoranConfigurator();
    jc.setContext(context);
    jc.doConfigure(file);
  }

  @SuppressWarnings("rawtypes")
  @Test
  public void conditionalConsoleApp_IF_THEN_True()
      throws JoranException, IOException, InterruptedException {
    InetAddress localhost = InetAddress.getLocalHost();
    System.out.println(
        "In conditionalConsoleApp_IF_THEN_True, canonicalHostName=\""
            + localhost.getCanonicalHostName()
            + "] and hostNmae=\""
            + localhost.getHostName()
            + "\"");
    context.putProperty("aHost", localhost.getHostName());

    String configFileAsStr =
        ClassicTestConstants.JORAN_INPUT_PREFIX + "conditional/conditionalConsoleApp.xml";
    configure(configFileAsStr);
    FileAppender fileAppender = (FileAppender) root.getAppender("FILE");
    assertNotNull(fileAppender);

    ConsoleAppender consoleAppender = (ConsoleAppender) root.getAppender("CON");
    assertNotNull(consoleAppender);
    StatusChecker checker = new StatusChecker(context);
    checker.assertIsErrorFree();
  }

  @SuppressWarnings("rawtypes")
  @Test
  public void conditionalConsoleApp_IF_THEN_False()
      throws JoranException, IOException, InterruptedException {

    String configFileAsStr =
        ClassicTestConstants.JORAN_INPUT_PREFIX + "conditional/conditionalConsoleApp.xml";
    configure(configFileAsStr);
    FileAppender fileAppender = (FileAppender) root.getAppender("FILE");
    assertNotNull(fileAppender);

    ConsoleAppender consoleAppender = (ConsoleAppender) root.getAppender("CON");
    assertNull(consoleAppender);
    StatusChecker checker = new StatusChecker(context);
    checker.assertIsErrorFree();
  }

  @SuppressWarnings("rawtypes")
  @Test
  public void conditionalConsoleApp_IF_THEN_ELSE()
      throws JoranException, IOException, InterruptedException {

    String configFileAsStr =
        ClassicTestConstants.JORAN_INPUT_PREFIX + "conditional/conditionalConsoleApp_ELSE.xml";
    configure(configFileAsStr);

    FileAppender fileAppender = (FileAppender) root.getAppender("FILE");
    assertNotNull(fileAppender);

    ConsoleAppender consoleAppender = (ConsoleAppender) root.getAppender("CON");
    assertNull(consoleAppender);

    ListAppender listAppender = (ListAppender) root.getAppender("LIST");
    assertNotNull(listAppender);

    // StatusPrinter.printIfErrorsOccured(context);
    StatusChecker checker = new StatusChecker(context);
    checker.assertIsErrorFree();
  }

  @Test
  public void conditionalInclusionWithExistingFile()
      throws JoranException, IOException, InterruptedException {

    String configFileAsStr =
        ClassicTestConstants.JORAN_INPUT_PREFIX + "conditional/conditionalIncludeExistingFile.xml";
    configure(configFileAsStr);

    ConsoleAppender consoleAppender = (ConsoleAppender) root.getAppender("CON");
    assertNotNull(consoleAppender);
    StatusChecker checker = new StatusChecker(context);
    checker.assertIsErrorFree();
  }

  @Test
  public void conditionalInclusionWithInexistentFile()
      throws JoranException, IOException, InterruptedException {

    String configFileAsStr =
        ClassicTestConstants.JORAN_INPUT_PREFIX
            + "conditional/conditionalIncludeInexistentFile.xml";
    configure(configFileAsStr);

    ConsoleAppender consoleAppender = (ConsoleAppender) root.getAppender("CON");
    assertNull(consoleAppender);
    StatusChecker checker = new StatusChecker(context);
    checker.assertIsErrorFree();
  }
}
public class DBAppenderHSQLTest {

  LoggerContext lc;
  Logger logger;
  DBAppender appender;
  DriverManagerConnectionSource connectionSource;

  DBAppenderHSQLTestFixture dbAppenderHSQLTestFixture;
  int diff = RandomUtil.getPositiveInt();

  @Before
  public void setUp() throws SQLException {
    dbAppenderHSQLTestFixture = new DBAppenderHSQLTestFixture();
    dbAppenderHSQLTestFixture.setUp();

    lc = new LoggerContext();
    lc.setName("default");
    logger = lc.getLogger("root");
    appender = new DBAppender();
    appender.setName("DB");
    appender.setContext(lc);
    connectionSource = new DriverManagerConnectionSource();
    connectionSource.setContext(lc);
    connectionSource.setDriverClass(DBAppenderHSQLTestFixture.HSQLDB_DRIVER_CLASS);
    connectionSource.setUrl(dbAppenderHSQLTestFixture.url);
    connectionSource.setUser(dbAppenderHSQLTestFixture.user);
    connectionSource.setPassword(dbAppenderHSQLTestFixture.password);
    connectionSource.start();
    appender.setConnectionSource(connectionSource);
    appender.start();
  }

  @After
  public void tearDown() throws SQLException {
    logger = null;
    lc = null;
    appender = null;
    connectionSource = null;
    dbAppenderHSQLTestFixture.tearDown();
  }

  @Test
  public void testAppendLoggingEvent() throws SQLException {
    ILoggingEvent event = createLoggingEvent();

    appender.append(event);
    StatusPrinter.printInCaseOfErrorsOrWarnings(lc);

    Statement stmt = connectionSource.getConnection().createStatement();
    ResultSet rs = null;
    rs = stmt.executeQuery("SELECT * FROM logging_event");
    if (rs.next()) {
      assertEquals(event.getTimeStamp(), rs.getLong(DBAppender.TIMESTMP_INDEX));
      assertEquals(event.getFormattedMessage(), rs.getString(DBAppender.FORMATTED_MESSAGE_INDEX));
      assertEquals(event.getLoggerName(), rs.getString(DBAppender.LOGGER_NAME_INDEX));
      assertEquals(event.getLevel().toString(), rs.getString(DBAppender.LEVEL_STRING_INDEX));
      assertEquals(event.getThreadName(), rs.getString(DBAppender.THREAD_NAME_INDEX));
      assertEquals(
          DBHelper.computeReferenceMask(event), rs.getShort(DBAppender.REFERENCE_FLAG_INDEX));
      assertEquals(String.valueOf(diff), rs.getString(DBAppender.ARG0_INDEX));
      StackTraceElement callerData = event.getCallerData()[0];
      assertEquals(callerData.getFileName(), rs.getString(DBAppender.CALLER_FILENAME_INDEX));
      assertEquals(callerData.getClassName(), rs.getString(DBAppender.CALLER_CLASS_INDEX));
      assertEquals(callerData.getMethodName(), rs.getString(DBAppender.CALLER_METHOD_INDEX));
    } else {
      fail("No row was inserted in the database");
    }

    rs.close();
    stmt.close();
  }

  @Test
  public void testAppendThrowable() throws SQLException {
    ILoggingEvent event = createLoggingEvent();

    appender.append(event);

    Statement stmt = connectionSource.getConnection().createStatement();
    ResultSet rs = null;
    rs = stmt.executeQuery("SELECT * FROM LOGGING_EVENT_EXCEPTION where EVENT_ID = 0");

    rs.next();
    String expected = "java.lang.Exception: test Ex";
    String firstLine = rs.getString(3);
    assertTrue(
        "[" + firstLine + "] does not match [" + expected + "]", firstLine.contains(expected));

    int i = 0;
    while (rs.next()) {
      expected = event.getThrowableProxy().getStackTraceElementProxyArray()[i].toString();
      String st = rs.getString(3);
      assertTrue("[" + st + "] does not match [" + expected + "]", st.contains(expected));
      i++;
    }
    assertTrue(i != 0);
    rs.close();
    stmt.close();
  }

  @Test
  public void testContextInfo() throws SQLException {
    lc.putProperty("testKey1", "testValue1");
    MDC.put("k" + diff, "v" + diff);
    ILoggingEvent event = createLoggingEvent();

    appender.append(event);

    Statement stmt = connectionSource.getConnection().createStatement();
    ResultSet rs = null;
    rs = stmt.executeQuery("SELECT * FROM LOGGING_EVENT_PROPERTY  WHERE EVENT_ID = 0");
    Map<String, String> map = appender.mergePropertyMaps(event);
    System.out.println("ma.size=" + map.size());
    int i = 0;
    while (rs.next()) {
      String key = rs.getString(2);
      assertEquals(map.get(key), rs.getString(3));
      i++;
    }
    assertTrue(map.size() != 0);
    assertEquals(map.size(), i);
    rs.close();
    stmt.close();
  }

  @Test
  public void testAppendMultipleEvents() throws SQLException {
    for (int i = 0; i < 10; i++) {
      ILoggingEvent event = createLoggingEvent();
      appender.append(event);
    }

    Statement stmt = connectionSource.getConnection().createStatement();
    ResultSet rs = null;
    rs = stmt.executeQuery("SELECT * FROM logging_event");
    int count = 0;
    while (rs.next()) {
      count++;
    }
    assertEquals(10, count);

    rs.close();
    stmt.close();
  }

  private ILoggingEvent createLoggingEvent() {
    return new LoggingEvent(
        this.getClass().getName(),
        logger,
        Level.DEBUG,
        "test message",
        new Exception("test Ex"),
        new Integer[] {diff});
  }
}
 File makeRandomJarFile() {
   File outputDir = new File(CoreTestConstants.OUTPUT_DIR_PREFIX);
   outputDir.mkdirs();
   int randomPart = RandomUtil.getPositiveInt();
   return new File(CoreTestConstants.OUTPUT_DIR_PREFIX + "foo-" + randomPart + ".jar");
 }
public class FileAppenderPerf {
  static String msgLong = "ABCDEGHIJKLMNOPQRSTUVWXYZabcdeghijklmnopqrstuvwxyz1234567890";

  static long LEN = 100 * 1000;
  static int DIFF = RandomUtil.getPositiveInt() % 1000;
  static String FILENAME;

  static LoggerContext buildLoggerContext(String filename, boolean safetyMode) {
    LoggerContext loggerContext = new LoggerContext();

    FileAppender<ILoggingEvent> fa = new FileAppender<ILoggingEvent>();

    PatternLayoutEncoder patternLayout = new PatternLayoutEncoder();
    patternLayout.setPattern("%5p %c - %m%n");
    patternLayout.setContext(loggerContext);
    patternLayout.start();

    fa.setEncoder(patternLayout);
    fa.setFile(filename);
    fa.setAppend(false);
    fa.setPrudent(safetyMode);
    fa.setContext(loggerContext);
    fa.start();

    ch.qos.logback.classic.Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
    root.addAppender(fa);

    return loggerContext;
  }

  static void usage(String msg) {
    System.err.println(msg);
    System.err.println("Usage: java " + FileAppenderPerf.class.getName() + " filename");

    System.exit(1);
  }

  public static void main(String[] argv) throws Exception {
    if (argv.length > 1) {
      usage("Wrong number of arguments.");
    }

    if (argv.length == 0) {
      FILENAME = DIFF + "";
    } else {
      FILENAME = argv[0];
    }

    perfCase(false);
    perfCase(true);
  }

  static void perfCase(boolean safetyMode) throws Exception {
    LoggerContext lc = buildLoggerContext(FILENAME + "-" + safetyMode + ".log", safetyMode);
    Logger logger = lc.getLogger(FileAppenderPerf.class);

    long start = System.nanoTime();
    for (int i = 0; i < LEN; i++) {
      logger.debug(msgLong + " " + i);
    }
    // in microseconds
    double durationPerLog = (System.nanoTime() - start) / (LEN * 1000.0);

    lc.stop();

    System.out.println(
        "Average duration of "
            + (durationPerLog)
            + " microseconds per log. Prudent mode="
            + safetyMode);
    System.out.println("------------------------------------------------");
  }
}
/**
 * @author Ceki G&uuml;lc&uuml;
 * @author Torsten Juergeleit
 */
public class AsyncAppenderTest {

  String thisClassName = this.getClass().getName();
  LoggerContext context = new LoggerContext();
  AsyncAppender asyncAppender = new AsyncAppender();
  ListAppender<ILoggingEvent> listAppender = new ListAppender<ILoggingEvent>();
  OnConsoleStatusListener onConsoleStatusListener = new OnConsoleStatusListener();
  LoggingEventBuilderInContext builder =
      new LoggingEventBuilderInContext(
          context, thisClassName, UnsynchronizedAppenderBase.class.getName());
  int diff = RandomUtil.getPositiveInt();

  @Before
  public void setUp() {
    onConsoleStatusListener.setContext(context);
    context.getStatusManager().add(onConsoleStatusListener);
    onConsoleStatusListener.start();

    asyncAppender.setContext(context);
    listAppender.setContext(context);
    listAppender.setName("list");
    listAppender.start();
  }

  @Test
  public void eventWasPreparedForDeferredProcessing() {
    asyncAppender.addAppender(listAppender);
    asyncAppender.start();

    String k = "k" + diff;
    MDC.put(k, "v");
    asyncAppender.doAppend(builder.build(diff));
    MDC.clear();

    asyncAppender.stop();
    assertFalse(asyncAppender.isStarted());

    // check the event
    assertEquals(1, listAppender.list.size());
    ILoggingEvent e = listAppender.list.get(0);

    // check that MDC values were correctly retained
    assertEquals("v", e.getMDCPropertyMap().get(k));
    assertFalse(e.hasCallerData());
  }

  @Test
  public void settingIncludeCallerDataPropertyCausedCallerDataToBeIncluded() {
    asyncAppender.addAppender(listAppender);
    asyncAppender.setIncludeCallerData(true);
    asyncAppender.start();

    asyncAppender.doAppend(builder.build(diff));
    asyncAppender.stop();

    // check the event
    assertEquals(1, listAppender.list.size());
    ILoggingEvent e = listAppender.list.get(0);
    assertTrue(e.hasCallerData());
    StackTraceElement ste = e.getCallerData()[0];
    assertEquals(thisClassName, ste.getClassName());
  }
}
public class MultiThreadedRollingTest {

  static final int NUM_THREADS = 10;
  static final int TOTAL_DURATION = 600;
  RunnableWithCounterAndDone[] runnableArray;

  Encoder<Object> encoder;
  Context context = new ContextBase();

  static String VERIFY_SH = "verify.sh";

  int diff = RandomUtil.getPositiveInt();
  String outputDirStr = CoreTestConstants.OUTPUT_DIR_PREFIX + "multi-" + diff + "/";

  RollingFileAppender<Object> rfa = new RollingFileAppender<Object>();

  String pathToBash = Env.getPathToBash();
  OutputStream scriptOS;

  @Before
  public void setUp() throws Exception {
    encoder = new EchoEncoder<Object>();
    File outputDir = new File(outputDirStr);
    outputDir.mkdirs();

    System.out.println("Output dir [" + outputDirStr + "]");

    scriptOS = openScript();

    rfa.setName("rolling");
    rfa.setEncoder(encoder);
    rfa.setContext(context);
    rfa.setFile(outputDirStr + "output.log");
  }

  void close(OutputStream os) {
    if (os != null) {
      try {
        os.close();
      } catch (IOException e) {
      }
    }
  }

  @After
  public void tearDown() throws Exception {
    rfa.stop();
  }

  public void setUpTimeBasedTriggeringPolicy(RollingFileAppender<Object> rfa) {
    String datePattern = "yyyy-MM-dd'T'HH_mm_ss_SSS";
    TimeBasedRollingPolicy tbrp = new TimeBasedRollingPolicy();
    tbrp.setFileNamePattern(outputDirStr + "test-%d{" + datePattern + "}");
    tbrp.setContext(context);
    tbrp.setParent(rfa);
    tbrp.start();

    rfa.setRollingPolicy(tbrp);
    rfa.start();
  }

  public void setUpSizeBasedTriggeringPolicy(RollingFileAppender<Object> rfa) {
    SizeBasedTriggeringPolicy<Object> zbtp = new SizeBasedTriggeringPolicy<Object>();
    zbtp.setContext(context);
    zbtp.setMaxFileSize("100KB");

    zbtp.start();
    rfa.setTriggeringPolicy(zbtp);

    FixedWindowRollingPolicy fwrp = new FixedWindowRollingPolicy();
    fwrp.setContext(context);
    fwrp.setFileNamePattern(outputDirStr + "test-%i.log");
    fwrp.setMaxIndex(20);
    fwrp.setMinIndex(0);
    fwrp.setParent(rfa);
    fwrp.start();
    rfa.setRollingPolicy(fwrp);
    rfa.start();
  }

  RunnableWithCounterAndDone[] buildRunnableArray(boolean withDelay) {
    RunnableWithCounterAndDone[] runnableArray = new RunnableWithCounterAndDone[NUM_THREADS];
    for (int i = 0; i < NUM_THREADS; i++) {
      runnableArray[i] = new RFARunnable(i, rfa, withDelay);
    }
    return runnableArray;
  }

  OutputStream openScript() throws IOException {
    return new FileOutputStream(outputDirStr + VERIFY_SH);
  }

  @Test
  public void multiThreadedTimedBased() throws InterruptedException, IOException {
    setUpTimeBasedTriggeringPolicy(rfa);
    executeHarness(TOTAL_DURATION, false);
    printScriptForTimeBased();
    verify();
  }

  int testFileCount() {
    File outputDir = new File(outputDirStr);
    FilenameFilter filter =
        new FilenameFilter() {
          public boolean accept(File dir, String name) {
            if (name.matches("test-\\d{1,2}.log")) {
              return true;
            }
            return false;
          }
        };
    File[] files = outputDir.listFiles(filter);
    return files.length;
  }

  void verify() throws IOException, InterruptedException {
    close(scriptOS);
    // no point in this test if we don't have bash
    if (pathToBash == null) {
      return;
    }
    ProcessBuilder pb = new ProcessBuilder();
    pb.command(pathToBash, VERIFY_SH);
    pb.directory(new File(outputDirStr));
    Process process = pb.start();
    process.waitFor();
    int exitCode = process.exitValue();

    assertEquals(SUCCESSFUL_EXIT_CODE, exitCode);
    System.out.println("External script based verification returned with exit code " + exitCode);
  }

  @Test
  public void multiThreadedSizeBased() throws InterruptedException, IOException {
    setUpSizeBasedTriggeringPolicy(rfa);
    // on a fast machine with a fast hard disk, if the tests runs for too
    // long the MAX_WINDOW_SIZE is reached, resulting in data loss which
    // we cannot test for.
    executeHarness(TOTAL_DURATION, true);
    int numFiles = testFileCount();
    printScriptForSizeBased(numFiles);
    verify();
  }

  private void printScriptHeader(String type) throws IOException {
    out("# ====================================================");
    out("# A script to check the exactness of the output ");
    out("# produced by " + type + " test");
    out("# ====================================================");
    out("# ");
  }

  private void printCommonScriptCore() throws IOException {
    out("");
    out("for t in $(seq 0 1 " + (NUM_THREADS - 1) + ")");
    out("do");
    out("  echo \"Testing results of thread $t\"");
    out("  grep \"$t \" aggregated | cut -d ' ' -f 2 > ${t}-sample");
    out("  for j in $(seq 1 1 ${end[$t]}); do echo $j; done > ${t}-witness");
    out("  diff -q -w ${t}-sample ${t}-witness;");
    out("  res=$?");
    out("  if [ $res != \"0\" ]; then");
    out("    echo \"FAILED for $t\"");
    out("    exit " + FAILURE_EXIT_CODE);
    out("  fi");
    out("done");
    out("");
    out("exit " + SUCCESSFUL_EXIT_CODE);
  }

  private void printScriptForTimeBased() throws IOException {
    printScriptHeader("TimeBased");
    for (int i = 0; i < NUM_THREADS; i++) {
      out("end[" + i + "]=" + this.runnableArray[i].getCounter());
    }
    out("");
    out("rm aggregated");
    out("cat test* output.log >> aggregated");
    printCommonScriptCore();
  }

  private void printScriptForSizeBased(int numfiles) throws IOException {
    printScriptHeader("SizeBased");

    for (int i = 0; i < NUM_THREADS; i++) {
      out("end[" + i + "]=" + this.runnableArray[i].getCounter());
    }
    out("");
    out("rm aggregated");
    out("for i in $(seq " + (numfiles - 1) + " -1 0); do cat test-$i.log >> aggregated; done");
    out("cat output.log >> aggregated");
    out("");
    printCommonScriptCore();
  }

  private void out(String msg) throws IOException {
    scriptOS.write(msg.getBytes());
    scriptOS.write("\n".getBytes());
  }

  private void executeHarness(int duration, boolean withDelay) throws InterruptedException {
    MultiThreadedHarness multiThreadedHarness = new MultiThreadedHarness(duration);
    this.runnableArray = buildRunnableArray(withDelay);
    multiThreadedHarness.execute(runnableArray);

    StatusChecker checker = new StatusChecker(context.getStatusManager());
    if (!checker.isErrorFree(0)) {
      StatusPrinter.print(context);
      fail("errors reported");
    }
  }

  long diff(long start) {
    return System.currentTimeMillis() - start;
  }

  static class RFARunnable extends RunnableWithCounterAndDone {
    RollingFileAppender<Object> rfa;
    int id;
    boolean withInducedDelay;

    RFARunnable(int id, RollingFileAppender<Object> rfa, boolean withInducedDelay) {
      this.id = id;
      this.rfa = rfa;
      this.withInducedDelay = withInducedDelay;
    }

    public void run() {
      while (!isDone()) {
        counter++;
        rfa.doAppend(id + " " + counter);
        if ((counter % 64 == 0) && withInducedDelay) {
          try {
            Thread.sleep(10);
          } catch (InterruptedException e) {
          }
        }
      }
    }
  }
}
public class ReconfigureOnChangeTaskTest {
  static final int THREAD_COUNT = 5;

  int diff = RandomUtil.getPositiveInt();

  // the space in the file name mandated by
  // http://jira.qos.ch/browse/LBCORE-119
  static final String SCAN1_FILE_AS_STR = JORAN_INPUT_PREFIX + "roct/scan 1.xml";

  static final String G_SCAN1_FILE_AS_STR = JORAN_INPUT_PREFIX + "roct/scan 1.groovy";

  static final String SCAN_LOGBACK_474_FILE_AS_STR =
      JORAN_INPUT_PREFIX + "roct/scan_logback_474.xml";

  static final String INCLUSION_SCAN_TOPLEVEL0_AS_STR =
      JORAN_INPUT_PREFIX + "roct/inclusion/topLevel0.xml";

  static final String INCLUSION_SCAN_TOP_BY_RESOURCE_AS_STR =
      JORAN_INPUT_PREFIX + "roct/inclusion/topByResource.xml";

  static final String INCLUSION_SCAN_INNER0_AS_STR =
      JORAN_INPUT_PREFIX + "roct/inclusion/inner0.xml";

  static final String INCLUSION_SCAN_INNER1_AS_STR = "target/test-classes/asResource/inner1.xml";

  LoggerContext loggerContext = new LoggerContext();
  Logger logger = loggerContext.getLogger(this.getClass());
  StatusChecker statusChecker = new StatusChecker(loggerContext);

  @BeforeClass
  public static void classSetup() {
    FileTestUtil.makeTestOutputDir();
  }

  void configure(File file) throws JoranException {
    JoranConfigurator jc = new JoranConfigurator();
    jc.setContext(loggerContext);
    jc.doConfigure(file);
  }

  void configure(InputStream is) throws JoranException {
    JoranConfigurator jc = new JoranConfigurator();
    jc.setContext(loggerContext);
    jc.doConfigure(is);
  }

  void gConfigure(File file) throws JoranException {
    GafferConfigurator gc = new GafferConfigurator(loggerContext);
    gc.run(file);
  }

  @Test(timeout = 4000L)
  public void checkBasicLifecyle() throws JoranException, IOException, InterruptedException {
    File file = new File(SCAN1_FILE_AS_STR);
    configure(file);
    List<File> fileList = getConfigurationWatchList(loggerContext);
    assertThatListContainsFile(fileList, file);
    checkThatTaskHasRan();
    checkThatTaskCanBeStopped();
  }

  @Test(timeout = 4000L)
  public void checkBasicLifecyleWithGaffer()
      throws JoranException, IOException, InterruptedException {
    File file = new File(G_SCAN1_FILE_AS_STR);
    gConfigure(file);
    List<File> fileList = getConfigurationWatchList(loggerContext);
    assertThatListContainsFile(fileList, file);
    checkThatTaskHasRan();
    checkThatTaskCanBeStopped();
  }

  private void checkThatTaskCanBeStopped() {
    ScheduledFuture<?> future = loggerContext.getScheduledFutures().get(0);
    loggerContext.stop();
    assertTrue(future.isCancelled());
  }

  private void checkThatTaskHasRan() throws InterruptedException {
    waitForReconfigureOnChangeTaskToRun();
  }

  List<File> getConfigurationWatchList(LoggerContext context) {
    ConfigurationWatchList configurationWatchList =
        ConfigurationWatchListUtil.getConfigurationWatchList(loggerContext);
    return configurationWatchList.getCopyOfFileWatchList();
  }

  @Test(timeout = 4000L)
  public void scanWithFileInclusion() throws JoranException, IOException, InterruptedException {
    File topLevelFile = new File(INCLUSION_SCAN_TOPLEVEL0_AS_STR);
    File innerFile = new File(INCLUSION_SCAN_INNER0_AS_STR);
    configure(topLevelFile);
    List<File> fileList = getConfigurationWatchList(loggerContext);
    assertThatListContainsFile(fileList, topLevelFile);
    assertThatListContainsFile(fileList, innerFile);
    checkThatTaskHasRan();
    checkThatTaskCanBeStopped();
  }

  @Test(timeout = 4000L)
  public void scanWithResourceInclusion() throws JoranException, IOException, InterruptedException {
    File topLevelFile = new File(INCLUSION_SCAN_TOP_BY_RESOURCE_AS_STR);
    File innerFile = new File(INCLUSION_SCAN_INNER1_AS_STR);
    configure(topLevelFile);
    List<File> fileList = getConfigurationWatchList(loggerContext);
    assertThatListContainsFile(fileList, topLevelFile);
    assertThatListContainsFile(fileList, innerFile);
  }

  // See also http://jira.qos.ch/browse/LOGBACK-338
  @Test(timeout = 4000L)
  public void reconfigurationIsNotPossibleInTheAbsenceOfATopFile()
      throws IOException, JoranException, InterruptedException {
    String configurationStr =
        "<configuration scan=\"true\" scanPeriod=\"50 millisecond\"><include resource=\"asResource/inner1.xml\"/></configuration>";
    configure(new ByteArrayInputStream(configurationStr.getBytes("UTF-8")));

    ConfigurationWatchList configurationWatchList =
        ConfigurationWatchListUtil.getConfigurationWatchList(loggerContext);
    assertNull(configurationWatchList);
    // assertNull(configurationWatchList.getMainURL());

    statusChecker.containsMatch(Status.WARN, "Due to missing top level");
    StatusPrinter.print(loggerContext);
    ReconfigureOnChangeTask roct = getRegisteredReconfigureTask();
    assertNull(roct);
    assertEquals(0, loggerContext.getScheduledFutures().size());
  }

  @Test(timeout = 3000L)
  public void fallbackToSafe_FollowedByRecovery()
      throws IOException, JoranException, InterruptedException {
    String path =
        CoreTestConstants.OUTPUT_DIR_PREFIX
            + "reconfigureOnChangeConfig_fallbackToSafe-"
            + diff
            + ".xml";
    File topLevelFile = new File(path);
    writeToFile(
        topLevelFile,
        "<configuration scan=\"true\" scanPeriod=\"5 millisecond\"><root level=\"ERROR\"/></configuration> ");
    configure(topLevelFile);
    CountDownLatch changeDetectedLatch = waitForReconfigurationToBeDone(null);
    ReconfigureOnChangeTask oldRoct = getRegisteredReconfigureTask();
    assertNotNull(oldRoct);
    writeToFile(
        topLevelFile,
        "<configuration scan=\"true\" scanPeriod=\"5 millisecond\">\n"
            + "  <root></configuration>");
    changeDetectedLatch.await();
    statusChecker.assertContainsMatch(Status.WARN, FALLING_BACK_TO_SAFE_CONFIGURATION);
    statusChecker.assertContainsMatch(Status.INFO, RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);

    loggerContext.getStatusManager().clear();

    CountDownLatch secondDoneLatch = waitForReconfigurationToBeDone(oldRoct);
    writeToFile(
        topLevelFile,
        "<configuration scan=\"true\" scanPeriod=\"5 millisecond\"><root level=\"ERROR\"/></configuration> ");
    secondDoneLatch.await();
    StatusPrinter.print(loggerContext);
    statusChecker.assertIsErrorFree();
    statusChecker.containsMatch(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
  }

  @Test(timeout = 4000L)
  public void fallbackToSafeWithIncludedFile_FollowedByRecovery()
      throws IOException, JoranException, InterruptedException, ExecutionException {
    String topLevelFileAsStr =
        CoreTestConstants.OUTPUT_DIR_PREFIX + "reconfigureOnChangeConfig_top-" + diff + ".xml";
    String innerFileAsStr =
        CoreTestConstants.OUTPUT_DIR_PREFIX + "reconfigureOnChangeConfig_inner-" + diff + ".xml";
    File topLevelFile = new File(topLevelFileAsStr);
    writeToFile(
        topLevelFile,
        "<configuration xdebug=\"true\" scan=\"true\" scanPeriod=\"5 millisecond\"><include file=\""
            + innerFileAsStr
            + "\"/></configuration> ");

    File innerFile = new File(innerFileAsStr);
    writeToFile(innerFile, "<included><root level=\"ERROR\"/></included> ");
    configure(topLevelFile);

    CountDownLatch doneLatch = waitForReconfigurationToBeDone(null);
    ReconfigureOnChangeTask oldRoct = getRegisteredReconfigureTask();
    assertNotNull(oldRoct);

    writeToFile(innerFile, "<included>\n<root>\n</included>");
    doneLatch.await();

    statusChecker.assertContainsMatch(Status.WARN, FALLING_BACK_TO_SAFE_CONFIGURATION);
    statusChecker.assertContainsMatch(Status.INFO, RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION);

    loggerContext.getStatusManager().clear();

    CountDownLatch secondDoneLatch = waitForReconfigurationToBeDone(oldRoct);
    writeToFile(innerFile, "<included><root level=\"ERROR\"/></included> ");
    secondDoneLatch.await();

    StatusPrinter.print(loggerContext);
    statusChecker.assertIsErrorFree();
    statusChecker.containsMatch(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
  }

  private ReconfigureOnChangeTask getRegisteredReconfigureTask() {
    return (ReconfigureOnChangeTask) loggerContext.getObject(RECONFIGURE_ON_CHANGE_TASK);
  }

  class RunMethodInvokedListener extends ReconfigureOnChangeTaskListener {
    CountDownLatch countDownLatch;

    RunMethodInvokedListener(CountDownLatch countDownLatch) {
      this.countDownLatch = countDownLatch;
    }

    @Override
    public void enteredRunMethod() {
      countDownLatch.countDown();
    }
  };

  class ChangeDetectedListener extends ReconfigureOnChangeTaskListener {
    CountDownLatch countDownLatch;

    ChangeDetectedListener(CountDownLatch countDownLatch) {
      this.countDownLatch = countDownLatch;
    }

    @Override
    public void changeDetected() {
      countDownLatch.countDown();
    }
  };

  class ReconfigurationDoneListener extends ReconfigureOnChangeTaskListener {
    CountDownLatch countDownLatch;

    ReconfigurationDoneListener(CountDownLatch countDownLatch) {
      this.countDownLatch = countDownLatch;
    }

    @Override
    public void doneReconfiguring() {
      countDownLatch.countDown();
    }
  };

  private ReconfigureOnChangeTask waitForReconfigureOnChangeTaskToRun()
      throws InterruptedException {
    ReconfigureOnChangeTask roct = null;
    while (roct == null) {
      roct = getRegisteredReconfigureTask();
      Thread.yield();
    }

    CountDownLatch countDownLatch = new CountDownLatch(1);
    roct.addListener(new RunMethodInvokedListener(countDownLatch));
    countDownLatch.await();
    return roct;
  }

  private CountDownLatch waitForReconfigurationToBeDone(ReconfigureOnChangeTask oldTask)
      throws InterruptedException {
    ReconfigureOnChangeTask roct = oldTask;
    while (roct == oldTask) {
      roct = getRegisteredReconfigureTask();
      Thread.yield();
    }

    CountDownLatch countDownLatch = new CountDownLatch(1);
    roct.addListener(new ReconfigurationDoneListener(countDownLatch));
    return countDownLatch;
  }

  private RunnableWithCounterAndDone[] buildRunnableArray(File configFile, UpdateType updateType) {
    RunnableWithCounterAndDone[] rArray = new RunnableWithCounterAndDone[THREAD_COUNT];
    rArray[0] = new Updater(configFile, updateType);
    for (int i = 1; i < THREAD_COUNT; i++) {
      rArray[i] = new LoggingRunnable(logger);
    }
    return rArray;
  }

  // check for deadlocks
  @Test(timeout = 4000L)
  public void scan_LOGBACK_474() throws JoranException, IOException, InterruptedException {
    loggerContext.setName("scan_LOGBACK_474");
    File file = new File(SCAN_LOGBACK_474_FILE_AS_STR);
    // StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new
    // OnConsoleStatusListener());
    configure(file);

    // ReconfigureOnChangeTask roct = waitForReconfigureOnChangeTaskToRun();

    int expectedResets = 2;
    Harness harness = new Harness(expectedResets);

    RunnableWithCounterAndDone[] runnableArray = buildRunnableArray(file, UpdateType.TOUCH);
    harness.execute(runnableArray);

    loggerContext.getStatusManager().add(new InfoStatus("end of execution ", this));
    StatusPrinter.print(loggerContext);
    checkResetCount(expectedResets);
  }

  private void assertThatListContainsFile(List<File> fileList, File file) {
    // conversion to absolute file seems to work nicely
    assertTrue(fileList.contains(file.getAbsoluteFile()));
  }

  private void checkResetCount(int expected) {
    StatusChecker checker = new StatusChecker(loggerContext);
    checker.assertIsErrorFree();

    int effectiveResets = checker.matchCount(CoreConstants.RESET_MSG_PREFIX);
    assertEquals(expected, effectiveResets);

    // String failMsg = "effective=" + effectiveResets + ", expected=" + expected;
    //
    // there might be more effective resets than the expected amount
    // since the harness may be sleeping while a reset occurs
    // assertTrue(failMsg, expected <= effectiveResets && (expected + 2) >= effectiveResets);

  }

  void addInfo(String msg, Object o) {
    loggerContext.getStatusManager().add(new InfoStatus(msg, o));
  }

  enum UpdateType {
    TOUCH,
    MALFORMED,
    MALFORMED_INNER
  }

  void writeToFile(File file, String contents) throws IOException {
    FileWriter fw = new FileWriter(file);
    fw.write(contents);
    fw.close();
    // on linux changes to last modified are not propagated if the
    // time stamp is near the previous time stamp hence the random delta
    file.setLastModified(System.currentTimeMillis() + RandomUtil.getPositiveInt());
  }

  class Harness extends AbstractMultiThreadedHarness {
    int changeCountLimit;

    Harness(int changeCount) {
      this.changeCountLimit = changeCount;
    }

    public void waitUntilEndCondition() throws InterruptedException {
      ReconfigureOnChangeTaskTest.this.addInfo(
          "Entering " + this.getClass() + ".waitUntilEndCondition()", this);

      int changeCount = 0;
      ReconfigureOnChangeTask lastRoct = null;
      CountDownLatch countDownLatch = null;

      while (changeCount < changeCountLimit) {
        ReconfigureOnChangeTask roct =
            (ReconfigureOnChangeTask) loggerContext.getObject(RECONFIGURE_ON_CHANGE_TASK);
        if (lastRoct != roct && roct != null) {
          lastRoct = roct;
          countDownLatch = new CountDownLatch(1);
          roct.addListener(new ChangeDetectedListener(countDownLatch));
        } else if (countDownLatch != null) {
          countDownLatch.await();
          countDownLatch = null;
          changeCount++;
        }
        Thread.yield();
      }
      ReconfigureOnChangeTaskTest.this.addInfo(
          "*****Exiting " + this.getClass() + ".waitUntilEndCondition()", this);
    }
  }

  class Updater extends RunnableWithCounterAndDone {
    File configFile;
    UpdateType updateType;

    // it actually takes time for Windows to propagate file modification changes
    // values below 100 milliseconds can be problematic the same propagation
    // latency occurs in Linux but is even larger (>600 ms)
    // final static int DEFAULT_SLEEP_BETWEEN_UPDATES = 60;

    int sleepBetweenUpdates = 100;

    Updater(File configFile, UpdateType updateType) {
      this.configFile = configFile;
      this.updateType = updateType;
    }

    Updater(File configFile) {
      this(configFile, UpdateType.TOUCH);
    }

    public void run() {
      while (!isDone()) {
        try {
          Thread.sleep(sleepBetweenUpdates);
        } catch (InterruptedException e) {
        }
        if (isDone()) {
          ReconfigureOnChangeTaskTest.this.addInfo("Exiting Updater.run()", this);
          return;
        }
        counter++;
        ReconfigureOnChangeTaskTest.this.addInfo("Touching [" + configFile + "]", this);
        switch (updateType) {
          case TOUCH:
            touchFile();
            break;
          case MALFORMED:
            try {
              malformedUpdate();
            } catch (IOException e) {
              e.printStackTrace();
              fail("malformedUpdate failed");
            }
            break;
          case MALFORMED_INNER:
            try {
              malformedInnerUpdate();
            } catch (IOException e) {
              e.printStackTrace();
              fail("malformedInnerUpdate failed");
            }
        }
      }
      ReconfigureOnChangeTaskTest.this.addInfo("Exiting Updater.run()", this);
    }

    private void malformedUpdate() throws IOException {
      writeToFile(
          configFile,
          "<configuration scan=\"true\" scanPeriod=\"50 millisecond\">\n"
              + "  <root level=\"ERROR\">\n"
              + "</configuration>");
    }

    private void malformedInnerUpdate() throws IOException {
      writeToFile(configFile, "<included>\n" + "  <root>\n" + "</included>");
    }

    void touchFile() {
      configFile.setLastModified(System.currentTimeMillis());
    }
  }
}