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.
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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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: ");
+ }
+}
*/
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;
public class VotingFutureTest {
- private static final int TIMEOUT = 3;
-
private Object result;
private ScheduledExecutorService executor;
private VotingFuture<Object> future;
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);
future.voteYes();
future.voteYes();
future.voteYes();
- checkSuccess(future, result);
+ assertFutureEquals(result, future);
}
@Test
voted.set(true);
future.voteYes();
}, 1, TimeUnit.SECONDS);
- checkSuccess(future, result);
+ assertFutureEquals(result, future);
Assert.assertTrue("Future completed before vote", voted.get());
}
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