X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-adapter%2Fsrc%2Ftest%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fadapter%2Ftest%2FAbstractDataTreeChangeListenerTest.java;h=b7db42fddfcc8e877c56635e27de77f0aa7dcd4f;hb=refs%2Fchanges%2F30%2F110030%2F1;hp=e5402601c1c248616d1185a87021144d7f118f22;hpb=d91796ad29e72f96f4e045ddfa13b244d518bb2c;p=mdsal.git diff --git a/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/test/AbstractDataTreeChangeListenerTest.java b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/test/AbstractDataTreeChangeListenerTest.java index e5402601c1..b7db42fddf 100644 --- a/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/test/AbstractDataTreeChangeListenerTest.java +++ b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/test/AbstractDataTreeChangeListenerTest.java @@ -7,23 +7,24 @@ */ package org.opendaylight.mdsal.binding.dom.adapter.test; +import static java.util.Objects.requireNonNull; import static org.junit.Assert.fail; -import com.google.common.util.concurrent.SettableFuture; +import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.Uninterruptibles; -import java.util.ArrayList; -import java.util.Collection; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; import java.util.List; import java.util.Objects; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Function; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.binding.api.DataObjectModification.ModificationType; import org.opendaylight.mdsal.binding.api.DataTreeChangeListener; import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; import org.opendaylight.mdsal.binding.api.DataTreeModification; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @@ -33,65 +34,117 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; * @author Thomas Pantelis */ public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest { - protected static final class TestListener implements DataTreeChangeListener { - private final SettableFuture>> future = SettableFuture.create(); - private final List> accumulatedChanges = new ArrayList<>(); - private final Function, Boolean>[] matchers; - private final int expChangeCount; - - private TestListener(final Function, Boolean>[] matchers) { - expChangeCount = matchers.length; - this.matchers = matchers; + @FunctionalInterface + protected interface Matcher { + + boolean apply(DataTreeModification modification); + } + + @FunctionalInterface + protected interface DataMatcher { + + boolean apply(T data); + } + + protected static final class ModificationCollector implements AutoCloseable { + private final TestListener listener; + private final Registration reg; + + private ModificationCollector(final TestListener listener, final Registration reg) { + this.listener = requireNonNull(listener); + this.reg = requireNonNull(reg); } - @Override - public void onDataTreeChanged(final Collection> changes) { - synchronized (accumulatedChanges) { - accumulatedChanges.addAll(changes); - if (expChangeCount <= accumulatedChanges.size()) { - future.set(List.copyOf(accumulatedChanges)); + @SafeVarargs + public final void verifyModifications(final Matcher... inOrder) { + final var matchers = new ArrayDeque<>(Arrays.asList(inOrder)); + final var changes = listener.awaitChanges(matchers.size()); + + while (!changes.isEmpty()) { + final var mod = changes.pop(); + final var matcher = matchers.pop(); + if (!matcher.apply(mod)) { + final var rootNode = mod.getRootNode(); + fail("Received unexpected notification: type: %s, path: %s, before: %s, after: %s".formatted( + rootNode.modificationType(), mod.getRootPath().path(), rootNode.dataBefore(), + rootNode.dataAfter())); + return; } } - } - public List> changes() { - try { - final var changes = future.get(5, TimeUnit.SECONDS); - Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS); - return changes; - } catch (InterruptedException | TimeoutException | ExecutionException e) { - throw new AssertionError(String.format( - "Data tree change notifications not received. Expected: %s. Actual: %s - %s", - expChangeCount, accumulatedChanges.size(), accumulatedChanges), e); + final var count = listener.changeCount(); + if (count != 0) { + throw new AssertionError("Expected no more changes, %s remain".formatted(count)); } } - public void verify() { - final var changes = new ArrayList<>(changes()); - final var iter = changes.iterator(); - while (iter.hasNext()) { - final var dataTreeModification = iter.next(); - for (var matcher : matchers) { - if (matcher.apply(dataTreeModification)) { - iter.remove(); - break; + @Override + public void close() { + reg.close(); + } + } + + private static final class TestListener implements DataTreeChangeListener { + private final Deque> accumulatedChanges = new ArrayDeque<>(); + + private boolean synced; + + @Override + public synchronized void onDataTreeChanged(final List> changes) { + accumulatedChanges.addAll(changes); + synced = true; + } + + @Override + public synchronized void onInitialData() { + synced = true; + } + + void awaitSync() { + final var sw = Stopwatch.createStarted(); + + do { + synchronized (this) { + if (synced) { + return; } } - } - if (!changes.isEmpty()) { - final var mod = changes.iterator().next(); - final var rootNode = mod.getRootNode(); - fail("Received unexpected notification: type: %s, path: %s, before: %s, after: %s".formatted( - rootNode.getModificationType(), mod.getRootPath().getRootIdentifier(), rootNode.getDataBefore(), - rootNode.getDataAfter())); - } + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + } while (sw.elapsed(TimeUnit.SECONDS) < 5); + + throw new AssertionError("Failed to achieve initial sync"); } - public boolean hasChanges() { - synchronized (accumulatedChanges) { - return !accumulatedChanges.isEmpty(); - } + Deque> awaitChanges(final int expectedCount) { + final var ret = new ArrayDeque>(expectedCount); + final var sw = Stopwatch.createStarted(); + int remaining = expectedCount; + + do { + synchronized (this) { + while (remaining != 0) { + final var change = accumulatedChanges.poll(); + if (change == null) { + break; + } + + remaining--; + ret.add(change); + } + } + + if (remaining == 0) { + return ret; + } + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + } while (sw.elapsed(TimeUnit.SECONDS) < 5); + + throw new AssertionError("Expected %s changes, received only %s".formatted(expectedCount, ret.size())); + } + + synchronized int changeCount() { + return accumulatedChanges.size(); } } @@ -99,46 +152,45 @@ public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBr super(true); } - @SafeVarargs - protected final TestListener createListener(final LogicalDatastoreType store, - final InstanceIdentifier path, final Function, Boolean>... matchers) { - TestListener listener = new TestListener<>(matchers); - getDataBroker().registerDataTreeChangeListener(DataTreeIdentifier.create(store, path), listener); - return listener; + protected final @NonNull ModificationCollector createCollector( + final LogicalDatastoreType store, final InstanceIdentifier path) { + final var listener = new TestListener(); + final var reg = getDataBroker().registerDataTreeChangeListener(DataTreeIdentifier.of(store, path), listener); + listener.awaitSync(); + return new ModificationCollector<>(listener, reg); } - public static Function, Boolean> match( - final ModificationType type, final InstanceIdentifier path, final Function checkDataBefore, - final Function checkDataAfter) { - return modification -> type == modification.getRootNode().getModificationType() - && path.equals(modification.getRootPath().getRootIdentifier()) - && checkDataBefore.apply(modification.getRootNode().getDataBefore()) - && checkDataAfter.apply(modification.getRootNode().getDataAfter()); + public static @NonNull Matcher match(final ModificationType type, + final InstanceIdentifier path, final DataMatcher checkDataBefore, + final DataMatcher checkDataAfter) { + return modification -> type == modification.getRootNode().modificationType() + && path.equals(modification.getRootPath().path()) + && checkDataBefore.apply(modification.getRootNode().dataBefore()) + && checkDataAfter.apply(modification.getRootNode().dataAfter()); } - public static Function, Boolean> match(final ModificationType type, + public static @NonNull Matcher match(final ModificationType type, final InstanceIdentifier path, final T expDataBefore, final T expDataAfter) { return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore), - (Function) dataAfter -> Objects.equals(expDataAfter, dataAfter)); + (DataMatcher) dataAfter -> Objects.equals(expDataAfter, dataAfter)); } - public static Function, Boolean> added( - final InstanceIdentifier path, final T data) { + public static @NonNull Matcher added(final InstanceIdentifier path, final T data) { return match(ModificationType.WRITE, path, null, data); } - public static Function, Boolean> replaced( - final InstanceIdentifier path, final T dataBefore, final T dataAfter) { + public static @NonNull Matcher replaced(final InstanceIdentifier path, + final T dataBefore, final T dataAfter) { return match(ModificationType.WRITE, path, dataBefore, dataAfter); } - public static Function, Boolean> deleted( - final InstanceIdentifier path, final T dataBefore) { + public static @NonNull Matcher deleted(final InstanceIdentifier path, + final T dataBefore) { return match(ModificationType.DELETE, path, dataBefore, null); } - public static Function, Boolean> subtreeModified( - final InstanceIdentifier path, final T dataBefore, final T dataAfter) { + public static @NonNull Matcher subtreeModified(final InstanceIdentifier path, + final T dataBefore, final T dataAfter) { return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter); } }