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