Improve PingPongTransactionChain coverage 07/100807/13
authorSamuel Schneider <samuel.schneider@pantheon.tech>
Tue, 26 Apr 2022 07:56:26 +0000 (09:56 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 23 May 2022 16:18:28 +0000 (18:18 +0200)
Add tests for PingPongTransactionChain covering unhappy paths

JIRA: MDSAL-698
Change-Id: I330458ac53d4f1ca58bbed0edaf2886745aef916
Signed-off-by: Samuel Schneider <samuel.schneider@pantheon.tech>
dom/mdsal-dom-spi/src/test/java/org/opendaylight/mdsal/dom/spi/PingPongTransactionChainTest.java

index 866bfdb34d039108adb9af4eaaf73b765bbd5f7e..1d22b532aab3f764245078951b551e8a49e12525 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.mdsal.dom.spi;
 
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -21,6 +22,7 @@ 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.never;
 import static org.mockito.Mockito.verify;
 
 import com.google.common.util.concurrent.FluentFuture;
@@ -57,6 +59,10 @@ public class PingPongTransactionChainTest {
     public DOMTransactionChain chain;
     @Mock
     public DOMDataTreeReadWriteTransaction rwTx;
+    @Mock
+    public DOMDataTreeReadWriteTransaction rwTx1;
+    @Mock
+    public DOMDataTreeReadWriteTransaction rwTx2;
 
     public DOMTransactionChainListener pingPongListener;
     public PingPongTransactionChain pingPong;
@@ -227,6 +233,103 @@ public class PingPongTransactionChainTest {
             containsString(" raced with transaction PingPongTransaction")));
     }
 
+    @Test
+    public void testReadWriteReuse() {
+        final var tx = pingPong.newReadWriteTransaction();
+        final var rwTxFuture = SettableFuture.<CommitInfo>create();
+        doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
+        // Now rwTx is inflight, but does not commit immediately
+        final var txFuture = tx.commit();
+        verify(rwTx).commit();
+
+        // Assert identity without delving into details
+        final var id = mock(Object.class);
+        doReturn(id).when(rwTx).getIdentifier();
+        assertSame(tx.getIdentifier(), id);
+
+        doReturn(rwTx1).when(chain).newReadWriteTransaction();
+        final var tx1 = pingPong.newWriteOnlyTransaction();
+        // now rwTx1 is ready, waiting for inflight to be completed
+        final var tx1Future = tx1.commit();
+
+        final var id1 = mock(Object.class);
+        doReturn(id1).when(rwTx1).getIdentifier();
+        assertSame(tx1.getIdentifier(), id1);
+
+        // Ready transaction is picked up by fast path allocation
+        final var tx2 = pingPong.newWriteOnlyTransaction();
+        assertSame(tx2.getIdentifier(), id1);
+
+        // Complete inflight transaction...
+        rwTxFuture.set(CommitInfo.empty());
+        assertDone(txFuture);
+        // ... but we are still holding the follow-up frontend transaction ...
+        assertFalse(tx1Future.isDone());
+        verify(rwTx1, never()).commit();
+
+        // ... and it will commit once we commit tx2 ...
+        doReturn(CommitInfo.emptyFluentFuture()).when(rwTx1).commit();
+        final var tx2Future = tx2.commit();
+        // ... at which point both complete
+        assertDone(tx1Future);
+        assertDone(tx2Future);
+    }
+
+    @Test
+    public void commitWhileInflight() {
+        final var tx = pingPong.newReadWriteTransaction();
+
+        final var rwTxFuture = SettableFuture.<CommitInfo>create();
+        doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
+        // rwTxFuture is inflight
+        final var txFuture = tx.commit();
+        verify(rwTx).commit();
+        assertFalse(txFuture.isDone());
+
+        doReturn(rwTx1).when(chain).newReadWriteTransaction();
+        final var rwTxFuture1 = SettableFuture.<CommitInfo>create();
+        final var tx1 = pingPong.newWriteOnlyTransaction();
+        final var tx1Future = tx1.commit();
+
+        doReturn(FluentFuture.from(rwTxFuture1)).when(rwTx1).commit();
+        rwTxFuture.set(CommitInfo.empty());
+        assertDone(txFuture);
+        verify(rwTx1).commit();
+
+        rwTxFuture1.set(CommitInfo.empty());
+        assertDone(tx1Future);
+    }
+
+    @Test
+    public void testNewAfterAsyncShutdown() {
+        // Setup inflight transaction
+        final var tx = pingPong.newReadWriteTransaction();
+        final var rwTxFuture = SettableFuture.<CommitInfo>create();
+        doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
+        final var txFuture = tx.commit();
+        assertFalse(txFuture.isDone());
+
+        // Setup ready transaction
+        doReturn(rwTx1).when(chain).newReadWriteTransaction();
+        final var rwTx1Future = SettableFuture.<CommitInfo>create();
+        doReturn(FluentFuture.from(rwTx1Future)).when(rwTx1).commit();
+
+        final var tx1Future = pingPong.newReadWriteTransaction().commit();
+        assertFalse(tx1Future.isDone());
+
+        pingPong.close();
+
+        final var ex = assertThrows(IllegalStateException.class, pingPong::newWriteOnlyTransaction);
+        assertThat(ex.getMessage(), allOf(startsWith("Transaction chain "), endsWith(" has been shut down")));
+        doNothing().when(chain).close();
+        rwTxFuture.set(CommitInfo.empty());
+        assertDone(txFuture);
+        verify(chain).close();
+
+        rwTx1Future.set(CommitInfo.empty());
+        assertDone(tx1Future);
+    }
+
     private static <T> T assertDone(final FluentFuture<T> future) {
         try {
             return Futures.getDone(future);