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.List;
20 import java.util.Objects;
21 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType;
24 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
25 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.mdsal.binding.api.DataTreeModification;
27 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
28 import org.opendaylight.yangtools.concepts.Registration;
29 import org.opendaylight.yangtools.yang.binding.DataObject;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
33 * Abstract base that provides a DTCL for verification.
35 * @author Thomas Pantelis
37 public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest {
39 protected interface Matcher<T extends DataObject> {
41 boolean apply(DataTreeModification<T> modification);
45 protected interface DataMatcher<T extends DataObject> {
47 boolean apply(T data);
50 protected static final class ModificationCollector<T extends DataObject> implements AutoCloseable {
51 private final TestListener<T> listener;
52 private final Registration reg;
54 private ModificationCollector(final TestListener<T> listener, final Registration reg) {
55 this.listener = requireNonNull(listener);
56 this.reg = requireNonNull(reg);
60 public final void assertModifications(final Matcher<T>... inOrder) {
61 final var matchers = new ArrayDeque<>(Arrays.asList(inOrder));
62 final var changes = new ArrayDeque<>(listener.awaitChanges(matchers.size()));
64 while (!changes.isEmpty()) {
65 final var mod = changes.pop();
66 final var matcher = matchers.peek();
67 if (matcher == null || !matcher.apply(mod)) {
68 final var rootNode = mod.getRootNode();
69 fail("Received unexpected notification: type: %s, path: %s, before: %s, after: %s".formatted(
70 rootNode.getModificationType(), mod.getRootPath().getRootIdentifier(), rootNode.getDataBefore(),
71 rootNode.getDataAfter()));
78 if (!matchers.isEmpty()) {
79 fail("Unsatisfied matchers " + matchers);
89 private static final class TestListener<T extends DataObject> implements DataTreeChangeListener<T> {
90 private final Deque<DataTreeModification<T>> accumulatedChanges = new ArrayDeque<>();
92 private boolean synced;
95 public synchronized void onDataTreeChanged(final Collection<DataTreeModification<T>> changes) {
96 accumulatedChanges.addAll(changes);
101 public synchronized void onInitialData() {
105 private void awaitSync() {
106 final var sw = Stopwatch.createStarted();
109 synchronized (this) {
115 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
116 } while (sw.elapsed(TimeUnit.SECONDS) < 5);
118 throw new AssertionError("Failed to achieve initial sync");
121 private List<DataTreeModification<T>> awaitChanges(final int expectedCount) {
122 final var sw = Stopwatch.createStarted();
125 synchronized (this) {
126 if (accumulatedChanges.size() >= expectedCount) {
127 return List.copyOf(accumulatedChanges);
131 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
132 } while (sw.elapsed(TimeUnit.SECONDS) < 5);
134 synchronized (this) {
135 throw new AssertionError("Expected %s changes, received only %s".formatted(expectedCount,
136 accumulatedChanges.size()));
141 protected AbstractDataTreeChangeListenerTest() {
145 protected final <T extends DataObject> @NonNull ModificationCollector<T> createCollector(
146 final LogicalDatastoreType store, final InstanceIdentifier<T> path) {
147 final var listener = new TestListener<T>();
148 final var reg = getDataBroker().registerDataTreeChangeListener(DataTreeIdentifier.create(store, path),
150 listener.awaitSync();
151 return new ModificationCollector<>(listener, reg);
154 public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
155 final InstanceIdentifier<T> path, final DataMatcher<T> checkDataBefore,
156 final DataMatcher<T> checkDataAfter) {
157 return modification -> type == modification.getRootNode().getModificationType()
158 && path.equals(modification.getRootPath().getRootIdentifier())
159 && checkDataBefore.apply(modification.getRootNode().getDataBefore())
160 && checkDataAfter.apply(modification.getRootNode().getDataAfter());
163 public static <T extends DataObject> @NonNull Matcher<T> match(final ModificationType type,
164 final InstanceIdentifier<T> path, final T expDataBefore, final T expDataAfter) {
165 return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore),
166 (DataMatcher<T>) dataAfter -> Objects.equals(expDataAfter, dataAfter));
169 public static <T extends DataObject> @NonNull Matcher<T> added(final InstanceIdentifier<T> path, final T data) {
170 return match(ModificationType.WRITE, path, null, data);
173 public static <T extends DataObject> @NonNull Matcher<T> replaced(final InstanceIdentifier<T> path,
174 final T dataBefore, final T dataAfter) {
175 return match(ModificationType.WRITE, path, dataBefore, dataAfter);
178 public static <T extends DataObject> @NonNull Matcher<T> deleted(final InstanceIdentifier<T> path,
179 final T dataBefore) {
180 return match(ModificationType.DELETE, path, dataBefore, null);
183 public static <T extends DataObject> @NonNull Matcher<T> subtreeModified(final InstanceIdentifier<T> path,
184 final T dataBefore, final T dataAfter) {
185 return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter);