Fix lead transaction cancellation
[mdsal.git] / dom / mdsal-dom-spi / src / test / java / org / opendaylight / mdsal / dom / spi / PingPongTransactionChainTest.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.dom.spi;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertSame;
13 import static org.junit.Assert.assertThrows;
14 import static org.mockito.ArgumentMatchers.any;
15 import static org.mockito.Mockito.doAnswer;
16 import static org.mockito.Mockito.doNothing;
17 import static org.mockito.Mockito.doReturn;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.verify;
20
21 import com.google.common.util.concurrent.FluentFuture;
22 import com.google.common.util.concurrent.Futures;
23 import com.google.common.util.concurrent.SettableFuture;
24 import java.util.Optional;
25 import java.util.concurrent.ExecutionException;
26 import java.util.function.Function;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.mockito.Mock;
31 import org.mockito.junit.MockitoJUnitRunner;
32 import org.opendaylight.mdsal.common.api.CommitInfo;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
36 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
37 import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
38 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
39 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
40 import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
41 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44
45 @RunWith(MockitoJUnitRunner.StrictStubs.class)
46 public class PingPongTransactionChainTest {
47     @Mock
48     public Function<DOMTransactionChainListener, DOMTransactionChain> delegateFactory;
49     @Mock
50     public DOMTransactionChainListener listener;
51     @Mock
52     public DOMTransactionChain chain;
53     @Mock
54     public DOMDataTreeReadWriteTransaction rwTx;
55
56     public DOMTransactionChainListener pingPongListener;
57     public PingPongTransactionChain pingPong;
58
59     @Before
60     public void before() {
61         // Slightly complicated bootstrap
62         doAnswer(invocation -> {
63             pingPongListener = invocation.getArgument(0);
64             return chain;
65         }).when(delegateFactory).apply(any());
66         pingPong = new PingPongTransactionChain(delegateFactory, listener);
67         verify(delegateFactory).apply(any());
68
69         doReturn(rwTx).when(chain).newReadWriteTransaction();
70     }
71
72     @Test
73     public void testIdleClose() {
74         doNothing().when(chain).close();
75         pingPong.close();
76         verify(chain).close();
77
78         doNothing().when(listener).onTransactionChainSuccessful(pingPong);
79         pingPongListener.onTransactionChainSuccessful(chain);
80         verify(listener).onTransactionChainSuccessful(pingPong);
81     }
82
83     @Test
84     public void testIdleFailure() {
85         final var cause = new Throwable();
86         doNothing().when(listener).onTransactionChainFailed(pingPong, null, cause);
87         pingPongListener.onTransactionChainFailed(chain, rwTx, cause);
88         verify(listener).onTransactionChainFailed(pingPong, null, cause);
89     }
90
91     @Test
92     public void testReadOnly() {
93         final var tx = pingPong.newReadOnlyTransaction();
94         assertGetIdentifier(tx);
95         assertReadOperations(tx);
96         assertCommit(tx::close);
97     }
98
99     @Test
100     public void testReadWrite() {
101         final var tx = pingPong.newReadWriteTransaction();
102         assertGetIdentifier(tx);
103         assertReadOperations(tx);
104         assertWriteOperations(tx);
105         assertCommit(tx::commit);
106     }
107
108     @Test
109     public void testWriteOnly() {
110         final var tx = pingPong.newWriteOnlyTransaction();
111         assertGetIdentifier(tx);
112         assertWriteOperations(tx);
113         assertCommit(tx::commit);
114     }
115
116     private void assertGetIdentifier(final DOMDataTreeTransaction tx) {
117         final var id = mock(Object.class);
118         doReturn(id).when(rwTx).getIdentifier();
119         assertSame(id, tx.getIdentifier());
120     }
121
122     private void assertReadOperations(final DOMDataTreeReadOperations tx) {
123         doReturn(FluentFutures.immediateTrueFluentFuture()).when(rwTx).exists(
124             LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
125         final var exists = tx.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
126         verify(rwTx).exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
127         assertEquals(Boolean.TRUE, assertDone(exists));
128
129         doReturn(FluentFutures.immediateFluentFuture(Optional.empty())).when(rwTx).read(
130             LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
131         final var read = tx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
132         verify(rwTx).read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
133         assertEquals(Optional.empty(), assertDone(read));
134     }
135
136     private void assertWriteOperations(final DOMDataTreeWriteOperations tx) {
137         doNothing().when(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
138         tx.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
139         verify(rwTx).delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty());
140
141         final var data = mock(NormalizedNode.class);
142         doNothing().when(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
143         tx.merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
144         verify(rwTx).merge(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
145
146         doNothing().when(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
147         tx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
148         verify(rwTx).put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), data);
149     }
150
151     private void assertCommit(final Runnable commitMethod) {
152         doReturn(CommitInfo.emptyFluentFuture()).when(rwTx).commit();
153         commitMethod.run();
154         verify(rwTx).commit();
155     }
156
157     @Test
158     public void testCommitFailure() {
159         assertCommitFailure(() -> { });
160     }
161
162     @Test
163     public void testCommitFailureAfterClose() {
164         assertCommitFailure(() -> {
165             doNothing().when(chain).close();
166             pingPong.close();
167             verify(chain).close();
168         });
169     }
170
171     private void assertCommitFailure(final Runnable asyncAction) {
172         final var tx = pingPong.newWriteOnlyTransaction();
173
174         final var rwTxFuture = SettableFuture.<CommitInfo>create();
175         doReturn(FluentFuture.from(rwTxFuture)).when(rwTx).commit();
176
177         final var txFuture = tx.commit();
178         verify(rwTx).commit();
179         assertFalse(txFuture.isDone());
180
181         asyncAction.run();
182
183         final var cause = new TransactionCommitFailedException("cause");
184         rwTxFuture.setException(cause);
185         assertSame(cause, assertThrows(ExecutionException.class, () -> Futures.getDone(txFuture)).getCause());
186     }
187
188     @Test
189     public void testSimpleCancelFalse() {
190         assertSimpleCancel(false);
191     }
192
193     @Test
194     public void testSimpleCancelTrue() {
195         assertSimpleCancel(true);
196     }
197
198     private void assertSimpleCancel(final boolean result) {
199         final var tx = pingPong.newWriteOnlyTransaction();
200
201         doReturn(result).when(rwTx).cancel();
202         assertEquals(result, tx.cancel());
203         verify(rwTx).cancel();
204     }
205
206     private static <T> T assertDone(final FluentFuture<T> future) {
207         try {
208             return Futures.getDone(future);
209         } catch (ExecutionException e) {
210             throw new AssertionError(e);
211         }
212     }
213 }