/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 1999-2015, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */ package ch.qos.logback.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import ch.qos.logback.core.helpers.NOPAppender; import ch.qos.logback.core.read.ListAppender; import ch.qos.logback.core.status.OnConsoleStatusListener; import ch.qos.logback.core.status.StatusChecker; import ch.qos.logback.core.testUtil.DelayingListAppender; import ch.qos.logback.core.testUtil.NPEAppender; /** * @author Ceki Gülcü * @author Torsten Juergeleit */ public class AsyncAppenderBaseTest { Context context = new ContextBase(); AsyncAppenderBase<Integer> asyncAppenderBase = new AsyncAppenderBase<Integer>(); LossyAsyncAppender lossyAsyncAppender = new LossyAsyncAppender(); DelayingListAppender<Integer> delayingListAppender = new DelayingListAppender<Integer>(); ListAppender<Integer> listAppender = new ListAppender<Integer>(); OnConsoleStatusListener onConsoleStatusListener = new OnConsoleStatusListener(); StatusChecker statusChecker = new StatusChecker(context); @Before public void setUp() { onConsoleStatusListener.setContext(context); context.getStatusManager().add(onConsoleStatusListener); onConsoleStatusListener.start(); asyncAppenderBase.setContext(context); lossyAsyncAppender.setContext(context); listAppender.setContext(context); listAppender.setName("list"); listAppender.start(); delayingListAppender.setContext(context); delayingListAppender.setName("list"); delayingListAppender.start(); } @Test(timeout = 2000) public void smoke() { asyncAppenderBase.addAppender(listAppender); asyncAppenderBase.start(); asyncAppenderBase.doAppend(0); asyncAppenderBase.stop(); verify(listAppender, 1); } @Test public void exceptionsShouldNotCauseHalting() throws InterruptedException { NPEAppender<Integer> npeAppender = new NPEAppender<Integer>(); npeAppender.setName("bad"); npeAppender.setContext(context); npeAppender.start(); asyncAppenderBase.addAppender(npeAppender); asyncAppenderBase.start(); assertTrue(asyncAppenderBase.isStarted()); for (int i = 0; i < 10; i++) asyncAppenderBase.append(i); asyncAppenderBase.stop(); assertFalse(asyncAppenderBase.isStarted()); assertEquals(AppenderBase.ALLOWED_REPEATS, statusChecker.matchCount("Appender \\[bad\\] failed to append.")); } @Test(timeout = 2000) public void emptyQueueShouldBeStoppable() { asyncAppenderBase.addAppender(listAppender); asyncAppenderBase.start(); asyncAppenderBase.stop(); verify(listAppender, 0); } @Test(timeout = 2000) public void workerShouldStopEvenIfInterruptExceptionConsumedWithinSubappender() { delayingListAppender.delay = 100; asyncAppenderBase.addAppender(delayingListAppender); asyncAppenderBase.start(); asyncAppenderBase.doAppend(0); asyncAppenderBase.stop(); verify(delayingListAppender, 1); assertTrue(delayingListAppender.interrupted); Thread.interrupted(); } @Test(timeout = 2000) public void noEventLoss() { int bufferSize = 10; int loopLen = bufferSize * 2; asyncAppenderBase.addAppender(delayingListAppender); asyncAppenderBase.setQueueSize(bufferSize); asyncAppenderBase.start(); for (int i = 0; i < loopLen; i++) { asyncAppenderBase.doAppend(i); } asyncAppenderBase.stop(); verify(delayingListAppender, loopLen); } @Test(timeout = 2000) public void eventLossIfNeverBlock() { int bufferSize = 10; int loopLen = bufferSize * 200; delayingListAppender.setDelay(5); // (loopLen*delay) much bigger than test timeout asyncAppenderBase.addAppender(delayingListAppender); asyncAppenderBase.setQueueSize(bufferSize); asyncAppenderBase.setNeverBlock(true); asyncAppenderBase.start(); for (int i = 0; i < loopLen; i++) { asyncAppenderBase.doAppend(i); } asyncAppenderBase.stop(); // ListAppender size isn't a reliable test here, so just make sure we didn't // have any errors, and that we could complete the test in time. statusChecker.assertIsErrorFree(); } @Test(timeout = 2000) public void lossyAppenderShouldOnlyLoseCertainEvents() { int bufferSize = 5; int loopLen = bufferSize * 2; lossyAsyncAppender.addAppender(delayingListAppender); lossyAsyncAppender.setQueueSize(bufferSize); lossyAsyncAppender.setDiscardingThreshold(1); lossyAsyncAppender.start(); for (int i = 0; i < loopLen; i++) { lossyAsyncAppender.doAppend(i); } lossyAsyncAppender.stop(); // events 0, 3, 6 and 9 are discardable. However, for events 0 and 3 // the buffer is not not yet full. Thus, only events 6 and 9 will be // effectively discarded. verify(delayingListAppender, loopLen - 2); } @Test(timeout = 2000) public void lossyAppenderShouldBeNonLossyIfDiscardingThresholdIsZero() { int bufferSize = 5; int loopLen = bufferSize * 2; lossyAsyncAppender.addAppender(delayingListAppender); lossyAsyncAppender.setQueueSize(bufferSize); lossyAsyncAppender.setDiscardingThreshold(0); lossyAsyncAppender.start(); for (int i = 0; i < loopLen; i++) { lossyAsyncAppender.doAppend(i); } lossyAsyncAppender.stop(); verify(delayingListAppender, loopLen); } @Test public void invalidQueueCapacityShouldResultInNonStartedAppender() { asyncAppenderBase.addAppender(new NOPAppender<Integer>()); asyncAppenderBase.setQueueSize(0); assertEquals(0, asyncAppenderBase.getQueueSize()); asyncAppenderBase.start(); assertFalse(asyncAppenderBase.isStarted()); statusChecker.assertContainsMatch("Invalid queue size"); } @SuppressWarnings("deprecation") @Test public void workerThreadFlushesOnStop() { int loopLen = 5; int maxRuntime = (loopLen + 1) * Math.max(1000, delayingListAppender.delay); ListAppender<Integer> la = delayingListAppender; asyncAppenderBase.addAppender(la); asyncAppenderBase.setDiscardingThreshold(0); asyncAppenderBase.setMaxFlushTime(maxRuntime); asyncAppenderBase.start(); asyncAppenderBase.worker.suspend(); for (int i = 0; i < loopLen; i++) { asyncAppenderBase.doAppend(i); } assertEquals(loopLen, asyncAppenderBase.getNumberOfElementsInQueue()); assertEquals(0, la.list.size()); asyncAppenderBase.worker.resume(); asyncAppenderBase.stop(); assertEquals(0, asyncAppenderBase.getNumberOfElementsInQueue()); verify(la, loopLen); } // @SuppressWarnings("deprecation") @Test public void stopExitsWhenMaxRuntimeReached() throws InterruptedException { int maxFlushTime = 1; // runtime of 0 means wait forever, so use 1 ms instead int loopLen = 10; ListAppender<Integer> la = delayingListAppender; asyncAppenderBase.addAppender(la); asyncAppenderBase.setMaxFlushTime(maxFlushTime); asyncAppenderBase.start(); for (int i = 0; i < loopLen; i++) { asyncAppenderBase.doAppend(i); } asyncAppenderBase.stop(); // confirms that stop exited when runtime reached statusChecker.assertContainsMatch("Max queue flush timeout \\(" + maxFlushTime + " ms\\) exceeded."); asyncAppenderBase.worker.join(); // confirms that all entries do end up being flushed if we wait long enough verify(la, loopLen); } // Interruption of current thread when in doAppend method should not be // consumed by async appender. See also http://jira.qos.ch/browse/LOGBACK-910 @Test public void verifyInterruptionIsNotSwallowed() { asyncAppenderBase.addAppender(delayingListAppender); asyncAppenderBase.start(); Thread.currentThread().interrupt(); asyncAppenderBase.doAppend(new Integer(0)); assertTrue(Thread.currentThread().isInterrupted()); // clear interrupt flag for subsequent tests Thread.interrupted(); } // Interruption of current thread should not prevent logging. // See also http://jira.qos.ch/browse/LOGBACK-910 // and https://jira.qos.ch/browse/LOGBACK-1247 @Test public void verifyInterruptionDoesNotPreventLogging() { asyncAppenderBase.addAppender(listAppender); asyncAppenderBase.start(); asyncAppenderBase.doAppend(new Integer(0)); Thread.currentThread().interrupt(); asyncAppenderBase.doAppend(new Integer(1)); asyncAppenderBase.doAppend(new Integer(1)); assertTrue(Thread.currentThread().isInterrupted()); // the interruption needs to be consumed Thread.interrupted(); asyncAppenderBase.stop(); verify(listAppender, 3); } @Test public void verifyInterruptionFlagWhenStopping_INTERUPPTED() { asyncAppenderBase.addAppender(listAppender); asyncAppenderBase.start(); Thread.currentThread().interrupt(); asyncAppenderBase.stop(); assertTrue(Thread.currentThread().isInterrupted()); Thread.interrupted(); } @Test public void verifyInterruptionFlagWhenStopping_NOT_INTERUPPTED() { asyncAppenderBase.addAppender(listAppender); asyncAppenderBase.start(); asyncAppenderBase.stop(); assertFalse(Thread.currentThread().isInterrupted()); } @Test public void verifyInterruptionOfWorkerIsSwallowed() { asyncAppenderBase.addAppender(delayingListAppender); asyncAppenderBase.start(); asyncAppenderBase.stop(); assertFalse(asyncAppenderBase.worker.isInterrupted()); } private void verify(ListAppender<Integer> la, int atLeast) { assertFalse(la.isStarted()); assertTrue(atLeast + " <= " + la.list.size(), atLeast <= la.list.size()); statusChecker.assertIsErrorFree(); statusChecker.assertContainsMatch("Worker thread will flush remaining events before exiting."); } static class LossyAsyncAppender extends AsyncAppenderBase<Integer> { @Override protected boolean isDiscardable(Integer i) { return (i % 3 == 0); } } @Test public void checkThatStartMethodIsIdempotent() { asyncAppenderBase.addAppender(lossyAsyncAppender); asyncAppenderBase.start(); // we don't need mockito for this test, but if we did here is how it would look // AsyncAppenderBase<Integer> spied = Mockito.spy(asyncAppenderBase); // Mockito.doThrow(new IllegalStateException("non idempotent start")).when((UnsynchronizedAppenderBase<Integer>) // spied).start(); // a second invocation of start will cause a IllegalThreadStateException thrown by the asyncAppenderBase.worker // thread asyncAppenderBase.start(); } }