Add basic PingPongTransactionChainTest 53/97953/3
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 17 Oct 2021 13:14:28 +0000 (15:14 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 17 Oct 2021 13:28:11 +0000 (15:28 +0200)
Covert the basics of behaviour, this still does not deal with various
race conditions.

JIRA: MDSAL-698
Change-Id: I53103cf3226ca8426dd12eed1f013052258ceb1e
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
dom/mdsal-dom-spi/src/test/java/org/opendaylight/mdsal/dom/spi/PingPongTransactionChainTest.java [new file with mode: 0644]

diff --git a/dom/mdsal-dom-spi/src/test/java/org/opendaylight/mdsal/dom/spi/PingPongTransactionChainTest.java b/dom/mdsal-dom-spi/src/test/java/org/opendaylight/mdsal/dom/spi/PingPongTransactionChainTest.java
new file mode 100644 (file)
index 0000000..ee916f5
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, 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.mdsal.dom.spi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class PingPongTransactionChainTest {
+    @Mock
+    public Function<DOMTransactionChainListener, DOMTransactionChain> delegateFactory;
+    @Mock
+    public DOMTransactionChainListener listener;
+    @Mock
+    public DOMTransactionChain chain;
+    @Mock
+    public DOMDataTreeReadWriteTransaction rwTx;
+
+    public DOMTransactionChainListener pingPongListener;
+    public PingPongTransactionChain pingPong;
+
+    @Before
+    public void before() {
+        // Slightly complicated bootstrap
+        doAnswer(invocation -> {
+            pingPongListener = invocation.getArgument(0);
+            return chain;
+        }).when(delegateFactory).apply(any());
+        pingPong = new PingPongTransactionChain(delegateFactory, listener);
+        verify(delegateFactory).apply(any());
+
+        doReturn(rwTx).when(chain).newReadWriteTransaction();
+    }
+
+    @Test
+    public void testIdleClose() {
+        doNothing().when(chain).close();
+        pingPong.close();
+        verify(chain).close();
+
+        doNothing().when(listener).onTransactionChainSuccessful(pingPong);
+        pingPongListener.onTransactionChainSuccessful(chain);
+        verify(listener).onTransactionChainSuccessful(pingPong);
+    }
+
+    @Test
+    public void testIdleFailure() {
+        final var cause = new Throwable();
+        doNothing().when(listener).onTransactionChainFailed(pingPong, null, cause);
+        pingPongListener.onTransactionChainFailed(chain, rwTx, cause);
+        verify(listener).onTransactionChainFailed(pingPong, null, cause);
+    }
+
+    @Test
+    public void testReadOnly() {
+        final var tx = pingPong.newReadOnlyTransaction();
+        assertGetIdentifier(tx);
+        assertReadOperations(tx);
+        assertCommit(tx::close);
+    }
+
+    @Test
+    public void testReadWrite() {
+        final var tx = pingPong.newReadWriteTransaction();
+        assertGetIdentifier(tx);
+        assertReadOperations(tx);
+        assertWriteOperations(tx);
+        assertCommit(tx::commit);
+    }
+
+    @Test
+    public void testWriteOnly() {
+        final var tx = pingPong.newWriteOnlyTransaction();
+        assertGetIdentifier(tx);
+        assertWriteOperations(tx);
+        assertCommit(tx::commit);
+    }
+
+    private void assertGetIdentifier(final DOMDataTreeTransaction tx) {
+        final var id = mock(Object.class);
+        doReturn(id).when(rwTx).getIdentifier();
+        assertSame(id, tx.getIdentifier());
+    }
+
+    private void assertReadOperations(final DOMDataTreeReadOperations tx) {
+        doReturn(FluentFutures.immediateTrueFluentFuture()).when(rwTx).exists(
+            LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        final var exists = tx.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        verify(rwTx).exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        assertEquals(Boolean.TRUE, assertDone(exists));
+
+        doReturn(FluentFutures.immediateFluentFuture(Optional.empty())).when(rwTx).read(
+            LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        final var read = tx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        verify(rwTx).read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        assertEquals(Optional.empty(), assertDone(read));
+    }
+
+    private void assertWriteOperations(final DOMDataTreeWriteOperations tx) {
+        doNothing().when(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        tx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+        verify(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
+
+        final var data = mock(NormalizedNode.class);
+        doNothing().when(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+        tx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+        verify(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+
+        doNothing().when(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+        tx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+        verify(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
+    }
+
+    private void assertCommit(final Runnable commitMethod) {
+        doReturn(CommitInfo.emptyFluentFuture()).when(rwTx).commit();
+        commitMethod.run();
+        verify(rwTx).commit();
+    }
+
+    @Test
+    public void testCommitFailure() {
+        assertCommitFailure(() -> { });
+    }
+
+    @Test
+    public void testCommitFailureAfterClose() {
+        assertCommitFailure(() -> {
+            doNothing().when(chain).close();
+            pingPong.close();
+            verify(chain).close();
+        });
+    }
+
+    private void assertCommitFailure(final Runnable asyncAction) {
+        final var tx = pingPong.newWriteOnlyTransaction();
+
+        final var rwTxFuture = SettableFuture.<CommitInfo>create();
+        doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
+
+        final var txFuture = tx.commit();
+        verify(rwTx).commit();
+        assertFalse(txFuture.isDone());
+
+        asyncAction.run();
+
+        final var cause = new TransactionCommitFailedException("cause");
+        rwTxFuture.setException(cause);
+        assertSame(cause, assertThrows(ExecutionException.class, () -> Futures.getDone(txFuture)).getCause());
+    }
+
+    @Test
+    public void testSimpleCancelFalse() {
+        assertSimpleCancel(false);
+    }
+
+    @Test
+    public void testSimpleCancelTrue() {
+        assertSimpleCancel(true);
+    }
+
+    private void assertSimpleCancel(final boolean result) {
+        final var tx = pingPong.newWriteOnlyTransaction();
+
+        doNothing().when(chain).close();
+        doReturn(result).when(rwTx).cancel();
+        doReturn("mock").when(rwTx).toString();
+
+        // FIXME: it seems we are doing the wrong, we should see 'result' returned here
+        assertTrue(tx.cancel());
+
+        verify(rwTx).cancel();
+        verify(chain).close();
+    }
+
+    private static <T> T assertDone(final FluentFuture<T> future) {
+        try {
+            return Futures.getDone(future);
+        } catch (ExecutionException e) {
+            throw new AssertionError(e);
+        }
+    }
+}