Refactor AbstractDataTreeChangeListenerTest
[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.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;
31
32 /**
33  * Abstract base that provides a DTCL for verification.
34  *
35  * @author Thomas Pantelis
36  */
37 public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest {
38     @FunctionalInterface
39     protected interface Matcher<T extends DataObject> {
40
41         boolean apply(DataTreeModification<T> modification);
42     }
43
44     @FunctionalInterface
45     protected interface DataMatcher<T extends DataObject> {
46
47         boolean apply(T data);
48     }
49
50     protected static final class ModificationCollector<T extends DataObject> implements AutoCloseable {
51         private final TestListener<T> listener;
52         private final Registration reg;
53
54         private ModificationCollector(final TestListener<T> listener, final Registration reg) {
55             this.listener = requireNonNull(listener);
56             this.reg = requireNonNull(reg);
57         }
58
59         @SafeVarargs
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()));
63
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()));
72                     return;
73                 }
74
75                 matchers.pop();
76             }
77
78             if (!matchers.isEmpty()) {
79                 fail("Unsatisfied matchers " + matchers);
80             }
81         }
82
83         @Override
84         public void close() {
85             reg.close();
86         }
87     }
88
89     private static final class TestListener<T extends DataObject> implements DataTreeChangeListener<T> {
90         private final Deque<DataTreeModification<T>> accumulatedChanges = new ArrayDeque<>();
91
92         private boolean synced;
93
94         @Override
95         public synchronized void onDataTreeChanged(final Collection<DataTreeModification<T>> changes) {
96             accumulatedChanges.addAll(changes);
97             synced = true;
98         }
99
100         @Override
101         public synchronized void onInitialData() {
102             synced = true;
103         }
104
105         private void awaitSync() {
106             final var sw = Stopwatch.createStarted();
107
108             do {
109                 synchronized (this) {
110                     if (synced) {
111                         return;
112                     }
113                 }
114
115                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
116             } while (sw.elapsed(TimeUnit.SECONDS) < 5);
117
118             throw new AssertionError("Failed to achieve initial sync");
119         }
120
121         private List<DataTreeModification<T>> awaitChanges(final int expectedCount) {
122             final var sw = Stopwatch.createStarted();
123
124             do {
125                 synchronized (this) {
126                     if (accumulatedChanges.size() >= expectedCount) {
127                         return List.copyOf(accumulatedChanges);
128                     }
129                 }
130
131                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
132             } while (sw.elapsed(TimeUnit.SECONDS) < 5);
133
134             synchronized (this) {
135                 throw new AssertionError("Expected %s changes, received only %s".formatted(expectedCount,
136                     accumulatedChanges.size()));
137             }
138         }
139     }
140
141     protected AbstractDataTreeChangeListenerTest() {
142         super(true);
143     }
144
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),
149             listener);
150         listener.awaitSync();
151         return new ModificationCollector<>(listener, reg);
152     }
153
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());
161     }
162
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));
167     }
168
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);
171     }
172
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);
176     }
177
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);
181     }
182
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);
186     }
187 }