Migrate DataTreeModification method users
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / test / java / org / opendaylight / mdsal / binding / dom / adapter / test / AbstractDataTreeChangeListenerTest.java
1 /*
2  * Copyright (c) 2018 Inocybe Technologies 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.binding.dom.adapter.test;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.junit.Assert.fail;
12
13 import com.google.common.base.Stopwatch;
14 import com.google.common.util.concurrent.Uninterruptibles;
15 import java.util.ArrayDeque;
16 import java.util.Arrays;
17 import java.util.Deque;
18 import java.util.List;
19 import java.util.Objects;
20 import java.util.concurrent.TimeUnit;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
23 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
24 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
25 import org.opendaylight.mdsal.binding.api.DataTreeModification;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.yangtools.concepts.Registration;
28 import org.opendaylight.yangtools.yang.binding.DataObject;
29 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
30
31 /**
32  * Abstract base that provides a DTCL for verification.
33  *
34  * @author Thomas Pantelis
35  */
36 public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest {
37     @FunctionalInterface
38     protected interface Matcher<T extends DataObject> {
39
40         boolean apply(DataTreeModification<T> modification);
41     }
42
43     @FunctionalInterface
44     protected interface DataMatcher<T extends DataObject> {
45
46         boolean apply(T data);
47     }
48
49     protected static final class ModificationCollector<T extends DataObject> implements AutoCloseable {
50         private final TestListener<T> listener;
51         private final Registration reg;
52
53         private ModificationCollector(final TestListener<T> listener, final Registration reg) {
54             this.listener = requireNonNull(listener);
55             this.reg = requireNonNull(reg);
56         }
57
58         @SafeVarargs
59         public final void verifyModifications(final Matcher<T>... inOrder) {
60             final var matchers = new ArrayDeque<>(Arrays.asList(inOrder));
61             final var changes = listener.awaitChanges(matchers.size());
62
63             while (!changes.isEmpty()) {
64                 final var mod = changes.pop();
65                 final var matcher = matchers.pop();
66                 if (!matcher.apply(mod)) {
67                     final var rootNode = mod.getRootNode();
68                     fail("Received unexpected notification: type: %s, path: %s, before: %s, after: %s".formatted(
69                         rootNode.modificationType(), mod.getRootPath().path(), rootNode.dataBefore(),
70                         rootNode.dataAfter()));
71                     return;
72                 }
73             }
74
75             final var count = listener.changeCount();
76             if (count != 0) {
77                 throw new AssertionError("Expected no more changes, %s remain".formatted(count));
78             }
79         }
80
81         @Override
82         public void close() {
83             reg.close();
84         }
85     }
86
87     private static final class TestListener<T extends DataObject> implements DataTreeChangeListener<T> {
88         private final Deque<DataTreeModification<T>> accumulatedChanges = new ArrayDeque<>();
89
90         private boolean synced;
91
92         @Override
93         public synchronized void onDataTreeChanged(final List<DataTreeModification<T>> changes) {
94             accumulatedChanges.addAll(changes);
95             synced = true;
96         }
97
98         @Override
99         public synchronized void onInitialData() {
100             synced = true;
101         }
102
103         void awaitSync() {
104             final var sw = Stopwatch.createStarted();
105
106             do {
107                 synchronized (this) {
108                     if (synced) {
109                         return;
110                     }
111                 }
112
113                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
114             } while (sw.elapsed(TimeUnit.SECONDS) < 5);
115
116             throw new AssertionError("Failed to achieve initial sync");
117         }
118
119         Deque<DataTreeModification<T>> awaitChanges(final int expectedCount) {
120             final var ret = new ArrayDeque<DataTreeModification<T>>(expectedCount);
121             final var sw = Stopwatch.createStarted();
122             int remaining = expectedCount;
123
124             do {
125                 synchronized (this) {
126                     while (remaining != 0) {
127                         final var change = accumulatedChanges.poll();
128                         if (change == null) {
129                             break;
130                         }
131
132                         remaining--;
133                         ret.add(change);
134                     }
135                 }
136
137                 if (remaining == 0) {
138                     return ret;
139                 }
140                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
141             } while (sw.elapsed(TimeUnit.SECONDS) < 5);
142
143             throw new AssertionError("Expected %s changes, received only %s".formatted(expectedCount, ret.size()));
144         }
145
146         synchronized int changeCount() {
147             return accumulatedChanges.size();
148         }
149     }
150
151     protected AbstractDataTreeChangeListenerTest() {
152         super(true);
153     }
154
155     protected final <T extends DataObject> @NonNull ModificationCollector<T> createCollector(
156             final LogicalDatastoreType store, final InstanceIdentifier<T> path) {
157         final var listener = new TestListener<T>();
158         final var reg = getDataBroker().registerDataTreeChangeListener(DataTreeIdentifier.of(store, path), listener);
159         listener.awaitSync();
160         return new ModificationCollector<>(listener, reg);
161     }
162
163     public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
164             final InstanceIdentifier<T> path, final DataMatcher<T> checkDataBefore,
165             final DataMatcher<T> checkDataAfter) {
166         return modification -> type == modification.getRootNode().modificationType()
167                 && path.equals(modification.getRootPath().path())
168                 && checkDataBefore.apply(modification.getRootNode().dataBefore())
169                 && checkDataAfter.apply(modification.getRootNode().dataAfter());
170     }
171
172     public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
173             final InstanceIdentifier<T> path, final T expDataBefore, final T expDataAfter) {
174         return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore),
175             (DataMatcher<T>) dataAfter -> Objects.equals(expDataAfter, dataAfter));
176     }
177
178     public static <T extends DataObject> @NonNull Matcher<T> added(final InstanceIdentifier<T> path, final T data) {
179         return match(ModificationType.WRITE, path, null, data);
180     }
181
182     public static <T extends DataObject> @NonNull Matcher<T> replaced(final InstanceIdentifier<T> path,
183             final T dataBefore, final T dataAfter) {
184         return match(ModificationType.WRITE, path, dataBefore, dataAfter);
185     }
186
187     public static <T extends DataObject> @NonNull Matcher<T> deleted(final InstanceIdentifier<T> path,
188             final T dataBefore) {
189         return match(ModificationType.DELETE, path, dataBefore, null);
190     }
191
192     public static <T extends DataObject> @NonNull Matcher<T> subtreeModified(final InstanceIdentifier<T> path,
193             final T dataBefore, final T dataAfter) {
194         return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter);
195     }
196 }