Add AbstractClientHandle derived classes tests 54/53354/8
authorAndrej Mak <andrej.mak@pantheon.tech>
Wed, 15 Mar 2017 14:24:56 +0000 (15:24 +0100)
committerTom Pantelis <tpanteli@brocade.com>
Fri, 17 Mar 2017 06:29:44 +0000 (06:29 +0000)
Change-Id: I9eee30289814c92b7f3e7d5b2eebf49ee575b9b3
Signed-off-by: Andrej Mak <andrej.mak@pantheon.tech>
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/access/client/AccessClientUtil.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractClientHandleTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientSnapshotTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientTransactionTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/TestUtils.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/VotingFutureTest.java

index 999aad8637b167afe45b7cfb7a37cf1bbb86ad9f..4f6625b6c5d0da0fb5d812b0b2e5b2b76e37f373 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.access.client;
 import akka.actor.ActorRef;
 import akka.actor.ActorSystem;
 import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.ResponseEnvelope;
 
 /**
  * Util class to access package private members in cds-access-client for test purposes.
@@ -21,4 +22,9 @@ public class AccessClientUtil {
         return new ClientActorContext(actor, system.scheduler(), system.dispatcher(), persistenceId, id);
     }
 
+    public static void completeRequest(final AbstractClientConnection<? extends BackendInfo> connection,
+                                       final ResponseEnvelope<?> envelope) {
+        connection.receiveResponse(envelope);
+    }
+
 }
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractClientHandleTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractClientHandleTest.java
new file mode 100644 (file)
index 0000000..9579178
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
+import akka.actor.ActorSystem;
+import akka.testkit.JavaTestKit;
+import akka.testkit.TestProbe;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.Function;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.cluster.access.client.AbstractClientConnection;
+import org.opendaylight.controller.cluster.access.client.AccessClientUtil;
+import org.opendaylight.controller.cluster.access.client.ClientActorContext;
+import org.opendaylight.controller.cluster.access.client.InternalCommand;
+import org.opendaylight.controller.cluster.access.commands.AbortLocalTransactionRequest;
+import org.opendaylight.controller.cluster.access.commands.ConnectClientRequest;
+import org.opendaylight.controller.cluster.access.commands.ConnectClientSuccess;
+import org.opendaylight.controller.cluster.access.concepts.ClientIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.Envelope;
+import org.opendaylight.controller.cluster.access.concepts.FailureEnvelope;
+import org.opendaylight.controller.cluster.access.concepts.FrontendIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.FrontendType;
+import org.opendaylight.controller.cluster.access.concepts.LocalHistoryIdentifier;
+import org.opendaylight.controller.cluster.access.concepts.MemberName;
+import org.opendaylight.controller.cluster.access.concepts.Request;
+import org.opendaylight.controller.cluster.access.concepts.RequestEnvelope;
+import org.opendaylight.controller.cluster.access.concepts.RequestFailure;
+import org.opendaylight.controller.cluster.access.concepts.RequestSuccess;
+import org.opendaylight.controller.cluster.access.concepts.Response;
+import org.opendaylight.controller.cluster.access.concepts.SuccessEnvelope;
+import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
+import org.opendaylight.controller.cluster.datastore.messages.PrimaryShardInfo;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import scala.concurrent.Promise;
+
+public abstract class AbstractClientHandleTest<T extends AbstractClientHandle<AbstractProxyTransaction>> {
+
+    private static final MemberName MEMBER_NAME = MemberName.forName("member-1");
+    private static final FrontendType FRONTEND_TYPE = FrontendType.forName("type-1");
+    private static final FrontendIdentifier FRONTEND_ID = FrontendIdentifier.create(MEMBER_NAME, FRONTEND_TYPE);
+    private static final ClientIdentifier CLIENT_ID = ClientIdentifier.create(FRONTEND_ID, 0);
+    private static final LocalHistoryIdentifier HISTORY_ID = new LocalHistoryIdentifier(CLIENT_ID, 0L);
+    private static final String PERSISTENCE_ID = "per-1";
+    private static final YangInstanceIdentifier PATH = YangInstanceIdentifier.EMPTY;
+    protected static final TransactionIdentifier TRANSACTION_ID = new TransactionIdentifier(HISTORY_ID, 0L);
+
+    @Mock
+    private DataTree dataTree;
+    @Mock
+    private DataTreeSnapshot dataTreeSnapshot;
+    private ActorSystem system;
+    private TestProbe backendProbe;
+    private AbstractClientHistory parent;
+    private AbstractDataStoreClientBehavior client;
+    private T handle;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        system = ActorSystem.apply();
+        final TestProbe contextProbe = new TestProbe(system, "context");
+        final TestProbe clientContextProbe = new TestProbe(system, "client-context");
+        backendProbe = new TestProbe(system, "backend");
+        //create handle dependencies
+        final ActorContext actorContext = createActorContextMock(system, contextProbe.ref());
+        final ClientActorContext clientContext =
+                AccessClientUtil.createClientActorContext(system, clientContextProbe.ref(), CLIENT_ID, PERSISTENCE_ID);
+        client = new SimpleDataStoreClientBehavior(clientContext, actorContext, "shard");
+        client.createLocalHistory();
+        parent = new SingleClientHistory(client, HISTORY_ID);
+        //connect client
+        client.getConnection(0L);
+        contextProbe.expectMsgClass(ConnectClientRequest.class);
+        final long sequence = 0L;
+        contextProbe.reply(new ConnectClientSuccess(CLIENT_ID, sequence, backendProbe.ref(),
+                Collections.emptyList(), dataTree, 3));
+        final InternalCommand command = clientContextProbe.expectMsgClass(InternalCommand.class);
+        command.execute(client);
+        //data tree mock
+        when(dataTree.takeSnapshot()).thenReturn(dataTreeSnapshot);
+
+        handle = createHandle(parent);
+    }
+
+    protected abstract T createHandle(AbstractClientHistory parent);
+
+    /**
+     * Do a operation with handle.
+     * Used for testing, whether closed handle throws exception when the operation is performed.
+     *
+     * @param handle handle
+     */
+    protected abstract void doHandleOperation(T handle);
+
+    @After
+    public void tearDown() throws Exception {
+        JavaTestKit.shutdownActorSystem(system);
+    }
+
+    @Test
+    public void testGetIdentifier() throws Exception {
+        Assert.assertEquals(TRANSACTION_ID, handle.getIdentifier());
+    }
+
+    @Test
+    public void testAbort() throws Exception {
+        doHandleOperation(handle);
+        handle.abort();
+        final Envelope envelope = backendProbe.expectMsgClass(Envelope.class);
+        final AbortLocalTransactionRequest request = (AbortLocalTransactionRequest) envelope.getMessage();
+        Assert.assertEquals(TRANSACTION_ID, request.getTarget());
+        checkClosed();
+    }
+
+    @Test
+    public void testLocalAbort() throws Exception {
+        doHandleOperation(handle);
+        handle.localAbort(new RuntimeException("fail"));
+        final Envelope envelope = backendProbe.expectMsgClass(Envelope.class);
+        final AbortLocalTransactionRequest request = (AbortLocalTransactionRequest) envelope.getMessage();
+        Assert.assertEquals(TRANSACTION_ID, request.getTarget());
+        checkClosed();
+    }
+
+    @Test
+    public void testEnsureClosed() throws Exception {
+        doHandleOperation(handle);
+        final Collection<AbstractProxyTransaction> transactions = handle.ensureClosed();
+        Assert.assertNotNull(transactions);
+        Assert.assertEquals(1, transactions.size());
+    }
+
+    @Test
+    public void testEnsureProxy() throws Exception {
+        final Function<Long, AbstractProxyTransaction> function = mock(Function.class);
+        final AbstractProxyTransaction expected = mock(AbstractProxyTransaction.class);
+        when(function.apply(0L)).thenReturn(expected);
+        final AbstractProxyTransaction proxy = handle.ensureProxy(PATH, function);
+        verify(function).apply(0L);
+        Assert.assertEquals(expected, proxy);
+    }
+
+    @Test
+    public void testParent() throws Exception {
+        Assert.assertEquals(parent, handle.parent());
+    }
+
+    protected void checkClosed() throws Exception {
+        TestUtils.assertOperationThrowsException(() -> doHandleOperation(handle), IllegalStateException.class);
+    }
+
+    /**
+     * Checks, whether backend actor has received request of expected class wrapped in RequestEnvelope.
+     * Then given response wrapped in ResponseEnvelope is sent.
+     *
+     * @param expectedRequestClass expected request class
+     * @param response             response
+     * @param <R>                  expected request type
+     * @return request message
+     */
+    protected <R extends Request> R backendRespondToRequest(final Class<R> expectedRequestClass,
+                                                            final Response response) {
+        final RequestEnvelope envelope = backendProbe.expectMsgClass(RequestEnvelope.class);
+        Assert.assertEquals(expectedRequestClass, envelope.getMessage().getClass());
+        final AbstractClientConnection<ShardBackendInfo> connection = client.getConnection(0L);
+        final long sessionId = envelope.getSessionId();
+        final long txSequence = envelope.getTxSequence();
+        final long executionTime = 0L;
+        if (response instanceof RequestSuccess) {
+            final RequestSuccess<?, ?> success = (RequestSuccess<?, ?>) response;
+            final SuccessEnvelope responseEnvelope = new SuccessEnvelope(success, sessionId, txSequence, executionTime);
+            AccessClientUtil.completeRequest(connection, responseEnvelope);
+        } else if (response instanceof RequestFailure) {
+            final RequestFailure<?, ?> fail = (RequestFailure<?, ?>) response;
+            final FailureEnvelope responseEnvelope = new FailureEnvelope(fail, sessionId, txSequence, executionTime);
+            AccessClientUtil.completeRequest(connection, responseEnvelope);
+        }
+        return (R) envelope.getMessage();
+    }
+
+    protected T getHandle() {
+        return handle;
+    }
+
+    protected DataTreeSnapshot getDataTreeSnapshot() {
+        return dataTreeSnapshot;
+    }
+
+    private static ActorContext createActorContextMock(final ActorSystem system, final ActorRef actor) {
+        final ActorContext mock = mock(ActorContext.class);
+        final Promise<PrimaryShardInfo> promise = new scala.concurrent.impl.Promise.DefaultPromise<>();
+        final ActorSelection selection = system.actorSelection(actor.path());
+        final PrimaryShardInfo shardInfo = new PrimaryShardInfo(selection, (short) 0);
+        promise.success(shardInfo);
+        when(mock.findPrimaryShardAsync(any())).thenReturn(promise.future());
+        return mock;
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientSnapshotTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientSnapshotTest.java
new file mode 100644 (file)
index 0000000..709335c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.getWithTimeout;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class ClientSnapshotTest extends AbstractClientHandleTest<ClientSnapshot> {
+
+    private static final YangInstanceIdentifier PATH = YangInstanceIdentifier.EMPTY;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        when(getDataTreeSnapshot().readNode(PATH)).thenReturn(Optional.absent());
+    }
+
+    @Override
+    protected ClientSnapshot createHandle(final AbstractClientHistory parent) {
+        return parent.takeSnapshot();
+    }
+
+    @Override
+    protected void doHandleOperation(final ClientSnapshot snapshot) {
+        snapshot.read(PATH);
+    }
+
+    @Test
+    public void testExists() throws Exception {
+        final CheckedFuture<Boolean, ReadFailedException> exists = getHandle().exists(PATH);
+        verify(getDataTreeSnapshot()).readNode(PATH);
+        Assert.assertFalse(getWithTimeout(exists));
+    }
+
+    @Test
+    public void testRead() throws Exception {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> exists = getHandle().read(PATH);
+        verify(getDataTreeSnapshot()).readNode(PATH);
+        Assert.assertFalse(getWithTimeout(exists).isPresent());
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientTransactionTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ClientTransactionTest.java
new file mode 100644 (file)
index 0000000..7b10cef
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.assertFutureEquals;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.assertOperationThrowsException;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.getWithTimeout;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.cluster.access.commands.CommitLocalTransactionRequest;
+import org.opendaylight.controller.cluster.access.commands.TransactionCommitSuccess;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
+import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.CursorAwareDataTreeModification;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+
+public class ClientTransactionTest extends AbstractClientHandleTest<ClientTransaction> {
+
+    private static final YangInstanceIdentifier PATH = YangInstanceIdentifier.builder()
+            .node(QName.create("ns-1", "node-1"))
+            .build();
+    private static final NormalizedNode<?, ?> DATA = Builders.containerBuilder()
+            .withNodeIdentifier(YangInstanceIdentifier.NodeIdentifier.create(PATH.getLastPathArgument().getNodeType()))
+            .build();
+
+    @Mock
+    private CursorAwareDataTreeModification modification;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        when(getDataTreeSnapshot().newModification()).thenReturn(modification);
+        when(modification.readNode(PATH)).thenReturn(Optional.of(DATA));
+    }
+
+    @Override
+    protected ClientTransaction createHandle(final AbstractClientHistory parent) {
+        return parent.createTransaction();
+    }
+
+    @Override
+    protected void doHandleOperation(final ClientTransaction transaction) {
+        transaction.read(PATH);
+    }
+
+    @Test
+    public void testOpenCloseCursor() throws Exception {
+        final DOMDataTreeWriteCursor cursor = getHandle().openCursor();
+        getHandle().closeCursor(cursor);
+        getHandle().openCursor().delete(PATH.getLastPathArgument());
+        verify(modification).delete(PATH);
+    }
+
+    @Test
+    public void testOpenSecondCursor() throws Exception {
+        getHandle().openCursor();
+        assertOperationThrowsException(getHandle()::openCursor, IllegalStateException.class);
+    }
+
+    @Test
+    public void testExists() throws Exception {
+        final CheckedFuture<Boolean, ReadFailedException> exists = getHandle().exists(PATH);
+        verify(modification).readNode(PATH);
+        Assert.assertTrue(getWithTimeout(exists));
+    }
+
+    @Test
+    public void testRead() throws Exception {
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> resultFuture = getHandle().read(PATH);
+        verify(modification).readNode(PATH);
+        final Optional<NormalizedNode<?, ?>> result = getWithTimeout(resultFuture);
+        Assert.assertTrue(result.isPresent());
+        Assert.assertEquals(DATA, result.get());
+    }
+
+    @Test
+    public void testDelete() throws Exception {
+        getHandle().delete(PATH);
+        verify(modification).delete(PATH);
+    }
+
+    @Test
+    public void testMerge() throws Exception {
+        getHandle().merge(PATH, DATA);
+        verify(modification).merge(PATH, DATA);
+    }
+
+    @Test
+    public void testWrite() throws Exception {
+        getHandle().write(PATH, DATA);
+        verify(modification).write(PATH, DATA);
+    }
+
+    @Test
+    public void testReadyEmpty() throws Exception {
+        final DOMStoreThreePhaseCommitCohort cohort = getHandle().ready();
+        assertFutureEquals(true, cohort.canCommit());
+        assertFutureEquals(null, cohort.preCommit());
+        assertFutureEquals(null, cohort.commit());
+    }
+
+    @Test
+    public void testReady() throws Exception {
+        getHandle().write(PATH, DATA);
+        final DOMStoreThreePhaseCommitCohort cohort = getHandle().ready();
+        final TransactionCommitSuccess response = new TransactionCommitSuccess(TRANSACTION_ID, 0L);
+        final ListenableFuture<Boolean> actual = cohort.canCommit();
+        final CommitLocalTransactionRequest request =
+                backendRespondToRequest(CommitLocalTransactionRequest.class, response);
+        Assert.assertEquals(modification, request.getModification());
+        assertFutureEquals(true, actual);
+        assertFutureEquals(null, cohort.preCommit());
+        assertFutureEquals(null, cohort.commit());
+    }
+
+    @Test
+    public void testReadyNoFurtherOperationsAllowed() throws Exception {
+        getHandle().ready();
+        checkClosed();
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/TestUtils.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/TestUtils.java
new file mode 100644 (file)
index 0000000..d713ba4
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.junit.Assert;
+
+class TestUtils {
+
+    @FunctionalInterface
+    public interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    private static final long TIMEOUT = 3;
+
+    /**
+     * Asserts, that future result when it completes is equal to given object.
+     * Future must complete in {@link TestUtils#TIMEOUT} seconds.
+     *
+     * @param expected expected result
+     * @param actual   future
+     * @param <T>      type
+     * @throws Exception exception
+     */
+    static <T> void assertFutureEquals(final T expected, final Future<T> actual) throws Exception {
+        Assert.assertEquals(expected, getWithTimeout(actual));
+    }
+
+    /**
+     * Calls {@link Future#get(long, TimeUnit)} with {@link TestUtils#TIMEOUT} in seconds.
+     *
+     * @param future future
+     * @param <T>    type
+     * @return future result
+     * @throws Exception exception
+     */
+    static <T> T getWithTimeout(final Future<T> future) throws Exception {
+        return future.get(TIMEOUT, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Asserts that given operation invocation, will throw an exception of given class.
+     *
+     * @param operation         operation
+     * @param expectedException expected exception class
+     * @param message           message, when expected exception isn't thrown
+     * @return expected exception instance. Can be used for additional assertions.
+     * @throws Exception unexpected exception.
+     */
+    //Throwable is propagated if doesn't match the expected type
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    static Throwable assertOperationThrowsException(final RunnableWithException operation,
+                                                    final Class<? extends Throwable> expectedException,
+                                                    final String message) throws Exception {
+        try {
+            operation.run();
+            throw new AssertionError(message + expectedException);
+        } catch (final Throwable e) {
+            if (!e.getClass().equals(expectedException)) {
+                throw e;
+            }
+            return e;
+        }
+    }
+
+    /**
+     * Asserts, that when given operation is run, exception of given class is thrown.
+     *
+     * @param operation         operation
+     * @param expectedException expected exception class
+     * @return expected exception instance. Can be used for additional assertions.
+     * @throws Exception unexpected exception.
+     */
+    static Throwable assertOperationThrowsException(final RunnableWithException operation,
+                                                    final Class<? extends Throwable> expectedException)
+            throws Exception {
+        return assertOperationThrowsException(operation, expectedException, "Operation should throw exception: ");
+    }
+}
index 85d6582ecfd93e89ed15e50dfb3b3b7942c5cec5..c2c55a4a73c678a2341aeb15d3f6a01603844fd0 100644 (file)
@@ -7,6 +7,10 @@
  */
 package org.opendaylight.controller.cluster.databroker.actors.dds;
 
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.assertFutureEquals;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.assertOperationThrowsException;
+import static org.opendaylight.controller.cluster.databroker.actors.dds.TestUtils.getWithTimeout;
+
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -20,8 +24,6 @@ import org.junit.Test;
 
 public class VotingFutureTest {
 
-    private static final int TIMEOUT = 3;
-
     private Object result;
     private ScheduledExecutorService executor;
     private VotingFuture<Object> future;
@@ -42,7 +44,7 @@ public class VotingFutureTest {
     public void testTrivialCases() throws Exception {
         final VotingFuture<Object> oneYesVoteFuture = new VotingFuture<>(result, 1);
         oneYesVoteFuture.voteYes();
-        checkSuccess(oneYesVoteFuture, result);
+        assertFutureEquals(result, oneYesVoteFuture);
         final VotingFuture<Object> oneNoVoteFuture = new VotingFuture<>(result, 1);
         final RuntimeException cause = new RuntimeException("fail");
         oneNoVoteFuture.voteNo(cause);
@@ -54,7 +56,7 @@ public class VotingFutureTest {
         future.voteYes();
         future.voteYes();
         future.voteYes();
-        checkSuccess(future, result);
+        assertFutureEquals(result, future);
     }
 
     @Test
@@ -66,7 +68,7 @@ public class VotingFutureTest {
             voted.set(true);
             future.voteYes();
         }, 1, TimeUnit.SECONDS);
-        checkSuccess(future, result);
+        assertFutureEquals(result, future);
         Assert.assertTrue("Future completed before vote", voted.get());
     }
 
@@ -118,30 +120,18 @@ public class VotingFutureTest {
         final RuntimeException cause2 = new RuntimeException("fail");
         future.voteNo(cause1);
         future.voteNo(cause2);
-        try {
-            future.get(TIMEOUT, TimeUnit.SECONDS);
-            Assert.fail("ExecutionException expected");
-        } catch (final ExecutionException e) {
-            //first no is set as cause
-            Assert.assertEquals(cause1, e.getCause());
-            //subsequent no causes are added as suppressed
-            final Throwable[] suppressed = e.getCause().getSuppressed();
-            Assert.assertEquals(1, suppressed.length);
-            Assert.assertEquals(cause2, suppressed[0]);
-        }
-    }
-
-    private static void checkException(final Future future, final RuntimeException cause) throws Exception {
-        try {
-            future.get(TIMEOUT, TimeUnit.SECONDS);
-            Assert.fail("ExecutionException expected");
-        } catch (final ExecutionException e) {
-            Assert.assertEquals(cause, e.getCause());
-        }
+        final Throwable thrown = assertOperationThrowsException(() -> getWithTimeout(future), ExecutionException.class);
+        //first no is set as cause
+        Assert.assertEquals(cause1, thrown.getCause());
+        //subsequent no causes are added as suppressed
+        final Throwable[] suppressed = thrown.getCause().getSuppressed();
+        Assert.assertEquals(1, suppressed.length);
+        Assert.assertEquals(cause2, suppressed[0]);
     }
 
-    private static void checkSuccess(final Future future, final Object result) throws Exception {
-        Assert.assertEquals(result, future.get(TIMEOUT, TimeUnit.SECONDS));
+    private static void checkException(final Future<Object> future, final RuntimeException cause) throws Exception {
+        final Throwable thrown = assertOperationThrowsException(() -> getWithTimeout(future), ExecutionException.class);
+        Assert.assertEquals(cause, thrown.getCause());
     }
 
 }
\ No newline at end of file