e5402601c1c248616d1185a87021144d7f118f22
[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 org.junit.Assert.fail;
11
12 import com.google.common.util.concurrent.SettableFuture;
13 import com.google.common.util.concurrent.Uninterruptibles;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21 import java.util.function.Function;
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.yang.binding.DataObject;
28 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
29
30 /**
31  * Abstract base that provides a DTCL for verification.
32  *
33  * @author Thomas Pantelis
34  */
35 public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest {
36     protected static final class TestListener<T extends DataObject> implements DataTreeChangeListener<T> {
37         private final SettableFuture<List<DataTreeModification<T>>> future = SettableFuture.create();
38         private final List<DataTreeModification<T>> accumulatedChanges = new ArrayList<>();
39         private final Function<DataTreeModification<T>, Boolean>[] matchers;
40         private final int expChangeCount;
41
42         private TestListener(final Function<DataTreeModification<T>, Boolean>[] matchers) {
43             expChangeCount = matchers.length;
44             this.matchers = matchers;
45         }
46
47         @Override
48         public void onDataTreeChanged(final Collection<DataTreeModification<T>> changes) {
49             synchronized (accumulatedChanges) {
50                 accumulatedChanges.addAll(changes);
51                 if (expChangeCount <= accumulatedChanges.size()) {
52                     future.set(List.copyOf(accumulatedChanges));
53                 }
54             }
55         }
56
57         public List<DataTreeModification<T>> changes() {
58             try {
59                 final var changes = future.get(5, TimeUnit.SECONDS);
60                 Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
61                 return changes;
62             } catch (InterruptedException | TimeoutException | ExecutionException e) {
63                 throw new AssertionError(String.format(
64                     "Data tree change notifications not received. Expected: %s. Actual: %s - %s",
65                         expChangeCount, accumulatedChanges.size(), accumulatedChanges), e);
66             }
67         }
68
69         public void verify() {
70             final var changes = new ArrayList<>(changes());
71             final var iter = changes.iterator();
72             while (iter.hasNext()) {
73                 final var dataTreeModification = iter.next();
74                 for (var matcher : matchers) {
75                     if (matcher.apply(dataTreeModification)) {
76                         iter.remove();
77                         break;
78                     }
79                 }
80             }
81
82             if (!changes.isEmpty()) {
83                 final var mod = changes.iterator().next();
84                 final var rootNode = mod.getRootNode();
85                 fail("Received unexpected notification: type: %s, path: %s, before: %s, after: %s".formatted(
86                     rootNode.getModificationType(), mod.getRootPath().getRootIdentifier(), rootNode.getDataBefore(),
87                     rootNode.getDataAfter()));
88             }
89         }
90
91         public boolean hasChanges() {
92             synchronized (accumulatedChanges) {
93                 return !accumulatedChanges.isEmpty();
94             }
95         }
96     }
97
98     protected AbstractDataTreeChangeListenerTest() {
99         super(true);
100     }
101
102     @SafeVarargs
103     protected final <T extends DataObject> TestListener<T> createListener(final LogicalDatastoreType store,
104             final InstanceIdentifier<T> path, final Function<DataTreeModification<T>, Boolean>... matchers) {
105         TestListener<T> listener = new TestListener<>(matchers);
106         getDataBroker().registerDataTreeChangeListener(DataTreeIdentifier.create(store, path), listener);
107         return listener;
108     }
109
110     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> match(
111             final ModificationType type, final InstanceIdentifier<T> path, final Function<T, Boolean> checkDataBefore,
112             final Function<T, Boolean> checkDataAfter) {
113         return modification -> type == modification.getRootNode().getModificationType()
114                 && path.equals(modification.getRootPath().getRootIdentifier())
115                 && checkDataBefore.apply(modification.getRootNode().getDataBefore())
116                 && checkDataAfter.apply(modification.getRootNode().getDataAfter());
117     }
118
119     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> match(final ModificationType type,
120             final InstanceIdentifier<T> path, final T expDataBefore, final T expDataAfter) {
121         return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore),
122             (Function<T, Boolean>) dataAfter -> Objects.equals(expDataAfter, dataAfter));
123     }
124
125     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> added(
126             final InstanceIdentifier<T> path, final T data) {
127         return match(ModificationType.WRITE, path, null, data);
128     }
129
130     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> replaced(
131             final InstanceIdentifier<T> path, final T dataBefore, final T dataAfter) {
132         return match(ModificationType.WRITE, path, dataBefore, dataAfter);
133     }
134
135     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> deleted(
136             final InstanceIdentifier<T> path, final T dataBefore) {
137         return match(ModificationType.DELETE, path, dataBefore, null);
138     }
139
140     public static <T extends DataObject> Function<DataTreeModification<T>, Boolean> subtreeModified(
141             final InstanceIdentifier<T> path, final T dataBefore, final T dataAfter) {
142         return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter);
143     }
144 }