/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flume.channel;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.flume.ChannelException;
import org.apache.flume.Event;
import org.apache.flume.event.EventBuilder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import com.google.common.base.Preconditions;
public abstract class AbstractBasicChannelSemanticsTest {
protected static List<Event> events;
static {
Event[] array = new Event[7];
for (int i = 0; i < array.length; ++i) {
array[i] = EventBuilder.withBody(("test event " + i).getBytes());
}
events = Collections.unmodifiableList(Arrays.asList(array));
}
protected ExecutorService executor = null;
protected TestChannel channel = null;
protected static class TestChannel extends BasicChannelSemantics {
private Queue<Event> queue = new ArrayDeque<Event>();
public enum Mode {
NORMAL,
THROW_ERROR,
THROW_RUNTIME,
THROW_CHANNEL,
SLEEP
}
private Mode mode = Mode.NORMAL;
private boolean lastTransactionCommitted = false;
private boolean lastTransactionRolledBack = false;
private boolean lastTransactionClosed = false;
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public boolean wasLastTransactionCommitted() {
return lastTransactionCommitted;
}
public boolean wasLastTransactionRolledBack() {
return lastTransactionRolledBack;
}
public boolean wasLastTransactionClosed() {
return lastTransactionClosed;
}
@Override
protected BasicTransactionSemantics createTransaction() {
return new TestTransaction();
}
protected class TestTransaction extends BasicTransactionSemantics {
protected void doMode() throws InterruptedException {
switch (mode) {
case THROW_ERROR:
throw new TestError();
case THROW_RUNTIME:
throw new TestRuntimeException();
case THROW_CHANNEL:
throw new ChannelException("test");
case SLEEP:
Thread.sleep(300000);
break;
}
}
@Override
protected void doBegin() throws InterruptedException {
doMode();
}
@Override
protected void doPut(Event event) throws InterruptedException {
doMode();
synchronized (queue) {
queue.add(event);
}
}
@Override
protected Event doTake() throws InterruptedException {
doMode();
synchronized (queue) {
return queue.poll();
}
}
@Override
protected void doCommit() throws InterruptedException {
doMode();
lastTransactionCommitted = true;
}
@Override
protected void doRollback() throws InterruptedException {
lastTransactionRolledBack = true;
doMode();
}
@Override
protected void doClose() {
lastTransactionClosed = true;
Preconditions.checkState(mode != TestChannel.Mode.SLEEP,
"doClose() can't throw InterruptedException, so why SLEEP?");
try {
doMode();
} catch (InterruptedException e) {
Assert.fail();
}
}
}
}
protected static class TestError extends Error {
static final long serialVersionUID = -1;
}
protected static class TestRuntimeException extends RuntimeException {
static final long serialVersionUID = -1;
}
protected void testException(Class<? extends Throwable> exceptionClass,
Runnable test) {
try {
test.run();
Assert.fail();
} catch (Throwable e) {
if (exceptionClass == InterruptedException.class
&& e instanceof ChannelException
&& e.getCause() instanceof InterruptedException) {
Assert.assertTrue(Thread.interrupted());
} else if (!exceptionClass.isInstance(e)) {
throw new AssertionError(e);
}
}
}
protected void testIllegalArgument(Runnable test) {
testException(IllegalArgumentException.class, test);
}
protected void testIllegalState(Runnable test) {
testException(IllegalStateException.class, test);
}
protected void testWrongThread(final Runnable test) throws Exception {
executor.submit(new Runnable() {
@Override
public void run() {
testIllegalState(test);
}
}).get();
}
protected void testMode(TestChannel.Mode mode, Runnable test) {
TestChannel.Mode oldMode = channel.getMode();
try {
channel.setMode(mode);
test.run();
} finally {
channel.setMode(oldMode);
}
}
protected void testException(TestChannel.Mode mode,
final Class<? extends Throwable> exceptionClass, final Runnable test) {
testMode(mode, new Runnable() {
@Override
public void run() {
testException(exceptionClass, test);
}
});
}
protected void testError(Runnable test) {
testException(TestChannel.Mode.THROW_ERROR, TestError.class, test);
}
protected void testRuntimeException(Runnable test) {
testException(TestChannel.Mode.THROW_RUNTIME, TestRuntimeException.class,
test);
}
protected void testChannelException(Runnable test) {
testException(TestChannel.Mode.THROW_CHANNEL, ChannelException.class, test);
}
protected void testInterrupt(final Runnable test) {
testMode(TestChannel.Mode.SLEEP, new Runnable() {
@Override
public void run() {
testException(InterruptedException.class, new Runnable() {
@Override
public void run() {
interruptTest(test);
}
});
}
});
}
protected void interruptTest(final Runnable test) {
final Thread mainThread = Thread.currentThread();
Future<?> future = executor.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
mainThread.interrupt();
}
});
test.run();
try {
future.get();
} catch (Exception e) {
throw new AssertionError(e);
}
}
protected void testExceptions(Runnable test) throws Exception {
testWrongThread(test);
testBasicExceptions(test);
testInterrupt(test);
}
protected void testBasicExceptions(Runnable test) throws Exception {
testError(test);
testRuntimeException(test);
testChannelException(test);
}
@Before
public void before() {
Preconditions.checkState(channel == null, "test cleanup failed!");
Preconditions.checkState(executor == null, "test cleanup failed!");
channel = new TestChannel();
executor = Executors.newCachedThreadPool();
}
@After
public void after() {
channel = null;
executor.shutdown();
executor = null;
}
}