2 * Copyright (c) 2018 Inocybe Technologies and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.adapter.test;
10 import static java.util.Objects.requireNonNull;
11 import static org.junit.Assert.fail;
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.Collection;
18 import java.util.Deque;
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;
32 * Abstract base that provides a DTCL for verification.
34 * @author Thomas Pantelis
36 public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest {
38 protected interface Matcher<T extends DataObject> {
40 boolean apply(DataTreeModification<T> modification);
44 protected interface DataMatcher<T extends DataObject> {
46 boolean apply(T data);
49 protected static final class ModificationCollector<T extends DataObject> implements AutoCloseable {
50 private final TestListener<T> listener;
51 private final Registration reg;
53 private ModificationCollector(final TestListener<T> listener, final Registration reg) {
54 this.listener = requireNonNull(listener);
55 this.reg = requireNonNull(reg);
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());
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.getModificationType(), mod.getRootPath().getRootIdentifier(), rootNode.getDataBefore(),
70 rootNode.getDataAfter()));
75 final var count = listener.changeCount();
77 throw new AssertionError("Expected no more changes, %s remain".formatted(count));
87 private static final class TestListener<T extends DataObject> implements DataTreeChangeListener<T> {
88 private final Deque<DataTreeModification<T>> accumulatedChanges = new ArrayDeque<>();
90 private boolean synced;
93 public synchronized void onDataTreeChanged(final Collection<DataTreeModification<T>> changes) {
94 accumulatedChanges.addAll(changes);
99 public synchronized void onInitialData() {
104 final var sw = Stopwatch.createStarted();
107 synchronized (this) {
113 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
114 } while (sw.elapsed(TimeUnit.SECONDS) < 5);
116 throw new AssertionError("Failed to achieve initial sync");
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;
125 synchronized (this) {
126 while (remaining != 0) {
127 final var change = accumulatedChanges.poll();
128 if (change == null) {
137 if (remaining == 0) {
140 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
141 } while (sw.elapsed(TimeUnit.SECONDS) < 5);
143 throw new AssertionError("Expected %s changes, received only %s".formatted(expectedCount, ret.size()));
146 synchronized int changeCount() {
147 return accumulatedChanges.size();
151 protected AbstractDataTreeChangeListenerTest() {
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.create(store, path),
160 listener.awaitSync();
161 return new ModificationCollector<>(listener, reg);
164 public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
165 final InstanceIdentifier<T> path, final DataMatcher<T> checkDataBefore,
166 final DataMatcher<T> checkDataAfter) {
167 return modification -> type == modification.getRootNode().getModificationType()
168 && path.equals(modification.getRootPath().getRootIdentifier())
169 && checkDataBefore.apply(modification.getRootNode().getDataBefore())
170 && checkDataAfter.apply(modification.getRootNode().getDataAfter());
173 public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
174 final InstanceIdentifier<T> path, final T expDataBefore, final T expDataAfter) {
175 return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore),
176 (DataMatcher<T>) dataAfter -> Objects.equals(expDataAfter, dataAfter));
179 public static <T extends DataObject> @NonNull Matcher<T> added(final InstanceIdentifier<T> path, final T data) {
180 return match(ModificationType.WRITE, path, null, data);
183 public static <T extends DataObject> @NonNull Matcher<T> replaced(final InstanceIdentifier<T> path,
184 final T dataBefore, final T dataAfter) {
185 return match(ModificationType.WRITE, path, dataBefore, dataAfter);
188 public static <T extends DataObject> @NonNull Matcher<T> deleted(final InstanceIdentifier<T> path,
189 final T dataBefore) {
190 return match(ModificationType.DELETE, path, dataBefore, null);
193 public static <T extends DataObject> @NonNull Matcher<T> subtreeModified(final InstanceIdentifier<T> path,
194 final T dataBefore, final T dataAfter) {
195 return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter);