/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.logstash.logback.appender;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import net.logstash.logback.appender.AsyncDisruptorAppender.LogEvent;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.StatusManager;
import com.lmax.disruptor.EventHandler;
@RunWith(MockitoJUnitRunner.class)
public class AsyncDisruptorAppenderTest {
private static final int VERIFICATION_TIMEOUT = 1000 * 30;
@InjectMocks
private AsyncDisruptorAppender<ILoggingEvent> appender = new AsyncDisruptorAppender<ILoggingEvent>() {};
@Mock
private EventHandler<LogEvent<ILoggingEvent>> eventHandler;
@Mock
private Context context;
@Mock
private StatusManager statusManager;
@Mock
private ILoggingEvent event1;
@Mock
private ILoggingEvent event2;
@Before
public void setup() {
when(context.getStatusManager()).thenReturn(statusManager);
}
@After
public void tearDown() {
appender.stop();
}
@SuppressWarnings("unchecked")
@Test
public void testEventHandlerCalled() throws Exception {
final AtomicReference<Object> capturedEvent = new AtomicReference<Object>();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
capturedEvent.set(invocation.getArgumentAt(0, LogEvent.class).event);
return null;
}
}).when(eventHandler).onEvent(any(LogEvent.class), anyLong(), eq(true));
appender.start();
appender.append(event1);
@SuppressWarnings("rawtypes")
ArgumentCaptor<LogEvent> captor = ArgumentCaptor.forClass(LogEvent.class);
verify(eventHandler, timeout(VERIFICATION_TIMEOUT)).onEvent(captor.capture(), anyLong(), eq(true));
// When eventHandler is invoked, the event should be event1
Assert.assertEquals(event1, capturedEvent.get());
// The event should be set back to null after invocation
Assert.assertNull(captor.getValue().event);
verify(event1).prepareForDeferredProcessing();
}
@Test
public void testThreadDaemon() throws Exception {
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
appender.setDaemon(true);
assertThat(appender.getThreadFactory().newThread(runnable).isDaemon()).isTrue();
appender.setDaemon(false);
assertThat(appender.getThreadFactory().newThread(runnable).isDaemon()).isFalse();
}
@Test
public void testThreadName() throws Exception {
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
appender.setThreadNameFormat("threadNamePrefix");
assertThat(appender.getThreadFactory().newThread(runnable).getName()).startsWith("threadNamePrefix");
}
@SuppressWarnings("unchecked")
@Test
public void testEventDroppedWhenFull() throws Exception {
appender.setRingBufferSize(1);
appender.start();
final CountDownLatch eventHandlerWaiter = new CountDownLatch(1);
final CountDownLatch mainWaiter = new CountDownLatch(1);
/*
* Cause the first event handling to block until we're done with the test.
*/
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
mainWaiter.countDown();
eventHandlerWaiter.await();
return null;
}
}).when(eventHandler).onEvent(any(LogEvent.class), anyLong(), anyBoolean());
/*
* This one will block during event handling
*/
appender.append(event1);
mainWaiter.await(VERIFICATION_TIMEOUT, TimeUnit.MILLISECONDS);
/*
* This one should be dropped
*/
appender.append(event2);
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
verify(statusManager, timeout(VERIFICATION_TIMEOUT)).add(statusCaptor.capture());
Assert.assertEquals(Status.WARN, statusCaptor.getValue().getLevel());
Assert.assertTrue(statusCaptor.getValue().getMessage().startsWith("Dropped"));
eventHandlerWaiter.countDown();
}
@SuppressWarnings("unchecked")
@Test
public void testEventHandlerThrowsException() throws Exception {
appender.start();
final Throwable throwable = new RuntimeException("message");
doThrow(throwable).when(eventHandler).onEvent(any(LogEvent.class), anyLong(), anyBoolean());
appender.append(event1);
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
verify(statusManager, timeout(VERIFICATION_TIMEOUT)).add(statusCaptor.capture());
Assert.assertEquals(Status.ERROR, statusCaptor.getValue().getLevel());
Assert.assertTrue(statusCaptor.getValue().getMessage().startsWith("Unable to process event"));
}
}