Merge "Bug 1224, Bug 1221: Added Test case scenarios for Data Change Events."
authorEd Warnicke <eaw@cisco.com>
Tue, 8 Jul 2014 18:45:45 +0000 (18:45 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 8 Jul 2014 18:45:45 +0000 (18:45 +0000)
opendaylight/md-sal/sal-inmemory-datastore/pom.xml
opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDataChangeListenerTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DatastoreTestTask.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DefaultDataChangeListenerTestSuite.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/RootScopeSubtreeTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeBaseTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeOneTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeSubtreeTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-mdsal-list-test.yang [new file with mode: 0644]

index dadef82..9068474 100644 (file)
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-simple</artifactId>
-      <version>${slf4j.version}</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>binding-generator-impl</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-test-model</artifactId>
+      <scope>test</scope>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
   <build>
index 71a3534..b679de5 100644 (file)
@@ -7,12 +7,17 @@
  */
 package org.opendaylight.controller.md.sal.dom.store.impl;
 
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
+import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
 import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
@@ -32,16 +37,12 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
 
 /**
  * Resolve Data Change Events based on modifications and listeners
@@ -438,17 +439,19 @@ final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListe
         Builder subtree = builder(DataChangeScope.SUBTREE).
                 setBefore(modification.getDataBefore().get()).
                 setAfter(modification.getDataAfter().get());
-
+        boolean oneModified = false;
         for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
             PathArgument childId = childMod.getIdentifier();
             InstanceIdentifier childPath = path.node(childId);
             Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
 
+
             switch (childMod.getModificationType()) {
             case WRITE:
             case MERGE:
             case DELETE:
                 one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
+                oneModified = true;
                 break;
             case SUBTREE_MODIFIED:
                 subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
@@ -458,11 +461,20 @@ final class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListe
                 break;
             }
         }
-        DOMImmutableDataChangeEvent oneChangeEvent = one.build();
-        subtree.merge(oneChangeEvent);
+        final DOMImmutableDataChangeEvent oneChangeEvent;
+        if(oneModified) {
+            one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
+            oneChangeEvent = one.build();
+            subtree.merge(oneChangeEvent);
+        } else {
+            oneChangeEvent = null;
+            subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
+        }
         DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
         if (!listeners.isEmpty()) {
-            addPartialTask(listeners, oneChangeEvent);
+            if(oneChangeEvent != null) {
+                addPartialTask(listeners, oneChangeEvent);
+            }
             addPartialTask(listeners, subtreeEvent);
         }
         return subtreeEvent;
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDataChangeListenerTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDataChangeListenerTest.java
new file mode 100644 (file)
index 0000000..4d2d078
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.opendaylight.controller.md.sal.dom.store.impl.DatastoreTestTask.WriteTransactionCustomizer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.TwoLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedList;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+public abstract class AbstractDataChangeListenerTest {
+
+    protected static final InstanceIdentifier TOP_LEVEL = InstanceIdentifier
+            .of(Top.QNAME);
+    private static final QName NAME_QNAME = QName.create(Top.QNAME, "name");
+    protected static final String FOO = "foo";
+    protected static final String BAR = "bar";
+    protected static final String BAZ = "baz";
+
+    private InMemoryDOMDataStore datastore;
+    private SchemaContext schemaContext;
+
+    @Before
+    public final void setup() throws Exception {
+        YangModuleInfo moduleInfo = BindingReflections
+                .getModuleInfo(TwoLevelList.class);
+        ModuleInfoBackedContext context = ModuleInfoBackedContext.create();
+        context.registerModuleInfo(moduleInfo);
+        schemaContext = context.tryToCreateSchemaContext().get();
+        datastore = new InMemoryDOMDataStore("TEST",
+                MoreExecutors.sameThreadExecutor());
+        datastore.onGlobalContextUpdated(schemaContext);
+    }
+
+    public final DatastoreTestTask newTestTask() {
+        return new DatastoreTestTask(datastore).cleanup(DatastoreTestTask
+                .simpleDelete(TOP_LEVEL));
+    }
+
+
+    public static final InstanceIdentifier path(final String topName,
+            final String nestedName) {
+        return path(topName).node(NestedList.QNAME).node(
+                new NodeIdentifierWithPredicates(NestedList.QNAME, NAME_QNAME,
+                        nestedName));
+    }
+
+    public static final InstanceIdentifier path(final String topName) {
+        return TOP_LEVEL.node(TopLevelList.QNAME).node(
+                new NodeIdentifierWithPredicates(TopLevelList.QNAME,
+                        NAME_QNAME, topName));
+    }
+
+    protected static DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> top() {
+        return Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(Top.QNAME));
+    }
+
+
+
+    protected static void assertEmpty(final Collection<?> set) {
+        Assert.assertTrue(set.isEmpty());
+    }
+
+    protected static void assertEmpty(final Map<?,?> set) {
+        Assert.assertTrue(set.isEmpty());
+    }
+
+    protected static <K> void assertContains(final Collection<K> set, final K... values) {
+        for (K key : values) {
+            Assert.assertTrue(set.contains(key));
+        }
+
+    }
+
+    protected static <K> void assertNotContains(final Collection<K> set, final K... values) {
+        for (K key : values) {
+            Assert.assertFalse(set.contains(key));
+        }
+    }
+
+    protected static <K> void assertContains(final Map<K,?> map, final K... values) {
+        for (K key : values) {
+            Assert.assertTrue(map.containsKey(key));
+        }
+    }
+
+    protected static <K> void assertNotContains(final Map<K,?> map, final K... values) {
+        for (K key : values) {
+            Assert.assertFalse(map.containsKey(key));
+        }
+    }
+
+    protected static CollectionNodeBuilder<MapEntryNode, MapNode> topLevelMap() {
+        return ImmutableNodes.mapNodeBuilder(TopLevelList.QNAME);
+    }
+
+    protected static CollectionNodeBuilder<MapEntryNode, OrderedMapNode> nestedMap() {
+        return Builders.orderedMapBuilder().withNodeIdentifier(new NodeIdentifier(NestedList.QNAME));
+    }
+
+    public static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> topLevelList(
+            final String key) {
+        return ImmutableNodes.mapEntryBuilder(TopLevelList.QNAME, NAME_QNAME,
+                key);
+    }
+
+    public static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> nestedList(
+            final String key) {
+        return ImmutableNodes
+                .mapEntryBuilder(NestedList.QNAME, NAME_QNAME, key);
+    }
+
+    public static final WriteTransactionCustomizer writeOneTopMultipleNested(
+            final String topName, final String... nestedName) {
+        CollectionNodeBuilder<MapEntryNode, OrderedMapNode> nestedMapBuilder = nestedMap();
+        for (String nestedItem : nestedName) {
+            nestedMapBuilder.addChild(nestedList(nestedItem).build());
+        }
+
+        final ContainerNode data = top().addChild(
+                topLevelMap().addChild(
+                        topLevelList(topName)
+                                .addChild(nestedMapBuilder.build()).build())
+                        .build()).build();
+
+        return DatastoreTestTask.simpleWrite(TOP_LEVEL, data);
+    }
+
+    public static final  WriteTransactionCustomizer deleteNested(final String topName,
+            final String nestedName) {
+        return DatastoreTestTask.simpleDelete(path(topName, nestedName));
+    }
+}
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DatastoreTestTask.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DatastoreTestTask.java
new file mode 100644 (file)
index 0000000..8ac93b1
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.sal.core.spi.data.DOMStore;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.SettableFuture;
+
+public class DatastoreTestTask {
+
+    private final DOMStore store;
+    private AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> changeListener;
+
+    private WriteTransactionCustomizer setup;
+    private WriteTransactionCustomizer write;
+    private ReadTransactionVerifier read;
+    private WriteTransactionCustomizer cleanup;
+    private InstanceIdentifier changePath;
+    private DataChangeScope changeScope;
+    private boolean postSetup = false;
+    private final ChangeEventListener internalListener;
+
+    public DatastoreTestTask(final DOMStore datastore) {
+        this.store = datastore;
+        internalListener = new ChangeEventListener();
+    }
+
+    public DatastoreTestTask changeListener(final InstanceIdentifier path, final DataChangeScope scope,
+            final AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> changeListener) {
+        this.changeListener = changeListener;
+        this.changePath = path;
+        this.changeScope = scope;
+        return this;
+    }
+
+    public DatastoreTestTask changeListener(final InstanceIdentifier path, final DataChangeScope scope) {
+        this.changePath = path;
+        this.changeScope = scope;
+        return this;
+    }
+
+    public DatastoreTestTask setup(final WriteTransactionCustomizer setup) {
+        this.setup = setup;
+        return this;
+    }
+
+    public DatastoreTestTask test(final WriteTransactionCustomizer write) {
+        this.write = write;
+        return this;
+    }
+
+    public DatastoreTestTask read(final ReadTransactionVerifier read) {
+        this.read = read;
+        return this;
+    }
+
+    public DatastoreTestTask cleanup(final WriteTransactionCustomizer cleanup) {
+        this.cleanup = cleanup;
+        return this;
+    }
+
+    public void run() throws InterruptedException, ExecutionException {
+        if (setup != null) {
+            execute(setup);
+        }
+        ListenerRegistration<ChangeEventListener> registration = null;
+        if (changePath != null) {
+            registration = store.registerChangeListener(changePath, internalListener, changeScope);
+        }
+
+        Preconditions.checkState(write != null, "Write Transaction must be set.");
+        postSetup = true;
+        execute(write);
+        if (registration != null) {
+            registration.close();
+        }
+        if (changeListener != null) {
+            changeListener.onDataChanged(internalListener.receivedChange.get());
+        }
+        if (read != null) {
+            read.verify(store.newReadOnlyTransaction());
+        }
+        if (cleanup != null) {
+            execute(cleanup);
+        }
+    }
+
+    public Future<AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>>> getChangeEvent() {
+        return internalListener.receivedChange;
+    }
+
+    private void execute(final WriteTransactionCustomizer writeCustomizer) throws InterruptedException,
+            ExecutionException {
+        DOMStoreReadWriteTransaction tx = store.newReadWriteTransaction();
+        writeCustomizer.customize(tx);
+        DOMStoreThreePhaseCommitCohort cohort = tx.ready();
+        assertTrue(cohort.canCommit().get());
+        cohort.preCommit().get();
+        cohort.commit().get();
+    }
+
+    public interface WriteTransactionCustomizer {
+        public void customize(DOMStoreReadWriteTransaction tx);
+    }
+
+    public interface ReadTransactionVerifier {
+        public void verify(DOMStoreReadTransaction tx);
+    }
+
+    private final class ChangeEventListener implements
+            AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> {
+
+        protected final SettableFuture<AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>>> receivedChange = SettableFuture
+                .create();
+
+        @Override
+        public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change) {
+            if (postSetup) {
+                receivedChange.set(change);
+            }
+        }
+    }
+
+    public static final WriteTransactionCustomizer simpleWrite(final InstanceIdentifier path,
+            final NormalizedNode<?, ?> data) {
+        return new WriteTransactionCustomizer() {
+
+            @Override
+            public void customize(final DOMStoreReadWriteTransaction tx) {
+                tx.write(path, data);
+            }
+        };
+    }
+
+    public static final WriteTransactionCustomizer simpleMerge(final InstanceIdentifier path,
+            final NormalizedNode<?, ?> data) {
+        return new WriteTransactionCustomizer() {
+
+            @Override
+            public void customize(final DOMStoreReadWriteTransaction tx) {
+                tx.merge(path, data);
+            }
+        };
+    }
+
+    public static final WriteTransactionCustomizer simpleDelete(final InstanceIdentifier path) {
+        return new WriteTransactionCustomizer() {
+            @Override
+            public void customize(final DOMStoreReadWriteTransaction tx) {
+                tx.delete(path);
+            }
+        };
+    }
+}
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DefaultDataChangeListenerTestSuite.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/DefaultDataChangeListenerTestSuite.java
new file mode 100644 (file)
index 0000000..54d2043
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import java.util.concurrent.ExecutionException;
+
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.store.impl.DatastoreTestTask.WriteTransactionCustomizer;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+
+public abstract class DefaultDataChangeListenerTestSuite extends AbstractDataChangeListenerTest {
+
+    protected static final String FOO_SIBLING = "foo-sibling";
+
+    abstract protected void customizeTask(DatastoreTestTask task);
+
+    @Test
+    public final void putTopLevelOneNested() throws InterruptedException, ExecutionException {
+
+        DatastoreTestTask task = newTestTask().test(writeOneTopMultipleNested(FOO, BAR));
+        customizeTask(task);
+        task.run();
+        putTopLevelOneNested(task);
+    }
+
+    @Test
+    public final void existingTopWriteSibling() throws InterruptedException, ExecutionException {
+        DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO)).test(
+                new WriteTransactionCustomizer() {
+                    @Override
+                    public void customize(final DOMStoreReadWriteTransaction tx) {
+                        tx.write(path(FOO_SIBLING), topLevelList(FOO_SIBLING).build());
+                    }
+                });
+        customizeTask(task);
+        task.run();
+        existingTopWriteSibling(task);
+    }
+
+    protected abstract void existingTopWriteSibling(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+
+    @Test
+    public final void existingTopWriteTwoNested() throws InterruptedException, ExecutionException {
+        DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO)).test(
+                new WriteTransactionCustomizer() {
+                    @Override
+                    public void customize(final DOMStoreReadWriteTransaction tx) {
+                        tx.write(path(FOO,BAR), nestedList(BAR).build());
+                        tx.write(path(FOO,BAZ), nestedList(BAZ).build());
+                    }
+                });
+        customizeTask(task);
+        task.run();
+        existingTopWriteTwoNested(task);
+    }
+
+    protected abstract void existingTopWriteTwoNested(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+
+    @Test
+    public final void existingOneNestedWriteAdditionalNested() throws InterruptedException, ExecutionException {
+        DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR)).test(
+                new WriteTransactionCustomizer() {
+                    @Override
+                    public void customize(final DOMStoreReadWriteTransaction tx) {
+                        tx.write(path(FOO,BAZ), nestedList(BAZ).build());
+                    }
+                });
+        customizeTask(task);
+        task.run();
+        existingOneNestedWriteAdditionalNested(task);
+    }
+
+    protected abstract void existingOneNestedWriteAdditionalNested(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+    protected abstract void putTopLevelOneNested(DatastoreTestTask task) throws InterruptedException,
+            ExecutionException;
+
+    @Test
+    public final void replaceTopLevelNestedChanged() throws InterruptedException, ExecutionException {
+        DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR)).test(
+                writeOneTopMultipleNested(FOO, BAZ));
+        customizeTask(task);
+        task.run();
+        replaceTopLevelNestedChanged(task);
+    }
+
+    protected abstract void replaceTopLevelNestedChanged(DatastoreTestTask task) throws InterruptedException,
+            ExecutionException;
+
+    @Test
+    public final void putTopLevelWithTwoNested() throws InterruptedException, ExecutionException {
+
+        DatastoreTestTask task = newTestTask().test(writeOneTopMultipleNested(FOO, BAR, BAZ));
+        customizeTask(task);
+        task.run();
+        putTopLevelWithTwoNested(task);
+    }
+
+    protected abstract void putTopLevelWithTwoNested(DatastoreTestTask task) throws InterruptedException,
+            ExecutionException;
+
+    @Test
+    public final void twoNestedExistsOneIsDeleted() throws InterruptedException, ExecutionException {
+
+        DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR, BAZ)).test(
+                deleteNested(FOO, BAZ));
+        customizeTask(task);
+        task.run();
+        twoNestedExistsOneIsDeleted(task);
+    }
+
+    protected abstract void twoNestedExistsOneIsDeleted(DatastoreTestTask task) throws InterruptedException,
+            ExecutionException;
+
+    @Test
+    public final void nestedListExistsRootDeleted() throws InterruptedException, ExecutionException {
+
+        DatastoreTestTask task = newTestTask().cleanup(null).setup(writeOneTopMultipleNested(FOO, BAR, BAZ))
+                .test(DatastoreTestTask.simpleDelete(TOP_LEVEL));
+        customizeTask(task);
+        task.run();
+        nestedListExistsRootDeleted(task);
+    }
+
+    protected abstract void nestedListExistsRootDeleted(DatastoreTestTask task) throws InterruptedException,
+            ExecutionException;
+}
index 369a7da..96369de 100644 (file)
@@ -1,8 +1,18 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
 package org.opendaylight.controller.md.sal.dom.store.impl;
 
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -14,11 +24,9 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-import java.util.concurrent.ExecutionException;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 
 
 public class InMemoryDataStoreTest {
@@ -237,7 +245,7 @@ public class InMemoryDataStoreTest {
   /**
    * Reads /test from readTx Read should return container.
    */
-  private static Optional<NormalizedNode<?, ?>> assertTestContainerExists(DOMStoreReadTransaction readTx)
+  private static Optional<NormalizedNode<?, ?>> assertTestContainerExists(final DOMStoreReadTransaction readTx)
       throws InterruptedException, ExecutionException {
 
     ListenableFuture<Optional<NormalizedNode<?, ?>>> writeTxContainer = readTx.read(TestModel.TEST_PATH);
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/RootScopeSubtreeTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/RootScopeSubtreeTest.java
new file mode 100644 (file)
index 0000000..ef2a474
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class RootScopeSubtreeTest extends DefaultDataChangeListenerTestSuite {
+
+    @Override
+    protected void customizeTask(final DatastoreTestTask task) {
+        task.changeListener(TOP_LEVEL, DataChangeScope.SUBTREE);
+    }
+
+    @Override
+    public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), TOP_LEVEL, path(FOO), path(FOO, BAR));
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO, BAZ));
+        assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+        assertContains(change.getRemovedPaths(), path(FOO, BAR));
+    }
+
+    @Override
+    protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), TOP_LEVEL, path(FOO), path(FOO, BAR), path(FOO, BAZ));
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertEmpty(change.getCreatedData());
+        assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+        assertContains(change.getRemovedPaths(), path(FOO, BAZ));
+    }
+
+    @Override
+    protected void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertEmpty(change.getCreatedData());
+        assertEmpty(change.getUpdatedData());
+        assertContains(change.getRemovedPaths(), TOP_LEVEL, path(FOO), path(FOO, BAR), path(FOO, BAZ));
+    }
+
+    @Override
+    protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO,BAZ));
+        assertNotContains(change.getCreatedData(), path(FOO,BAR));
+        assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO,BAR),path(FOO,BAZ));
+        assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+        assertNotContains(change.getUpdatedData(), path(FOO,BAR));
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO_SIBLING));
+        assertContains(change.getUpdatedData(), TOP_LEVEL);
+        assertNotContains(change.getUpdatedData(), path(FOO));
+        assertEmpty(change.getRemovedPaths());
+    }
+}
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeBaseTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeBaseTest.java
new file mode 100644 (file)
index 0000000..4c1a860
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeBaseTest extends DefaultDataChangeListenerTestSuite {
+
+    private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+            TopLevelList.QNAME);
+
+    @Override
+    protected void customizeTask(final DatastoreTestTask task) {
+        task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.BASE);
+    }
+
+    @Override
+    public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertNotNull(change);
+
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+
+        assertContains(change.getCreatedData(), path(FOO, BAZ));
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+    }
+
+    @Override
+    protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+        assertFalse(change.getCreatedData().isEmpty());
+
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        Future<?> future = task.getChangeEvent();
+        /*
+         * Base listener should be notified only and only if actual node changed its state,
+         * since deletion of child, did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertEmpty(change.getCreatedData());
+        assertEmpty(change.getUpdatedData());
+
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+    }
+
+    @Override
+    protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) {
+        Future<?> future = task.getChangeEvent();
+        /*
+         * One listener should be notified only and only if actual node changed its state,
+         * since deletion of nested child (in this case /nested-list/nested-list[foo],
+         * did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        Future<?> future = task.getChangeEvent();
+        /*
+         * One listener should be notified only and only if actual node changed its state,
+         * since deletion of nested child (in this case /nested-list/nested-list[foo],
+         * did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO_SIBLING));
+        assertNotContains(change.getUpdatedData(), path(FOO), TOP_LEVEL);
+        assertEmpty(change.getRemovedPaths());
+    }
+}
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeOneTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeOneTest.java
new file mode 100644 (file)
index 0000000..1f2cb22
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeOneTest extends DefaultDataChangeListenerTestSuite {
+
+    private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+            TopLevelList.QNAME);
+
+    @Override
+    protected void customizeTask(final DatastoreTestTask task) {
+        task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.ONE);
+    }
+
+    @Override
+    public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertNotNull(change);
+
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+
+        assertContains(change.getCreatedData(), path(FOO, BAZ));
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+    }
+
+    @Override
+    protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+        assertFalse(change.getCreatedData().isEmpty());
+
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        Future<?> future = task.getChangeEvent();
+        /*
+         * One listener should be notified only and only if actual node changed its state,
+         * since deletion of nested child (in this case /nested-list/nested-list[foo],
+         * did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertEmpty(change.getCreatedData());
+        assertEmpty(change.getUpdatedData());
+
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+    }
+
+    @Override
+    protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) {
+        Future<?> future = task.getChangeEvent();
+        /*
+         * One listener should be notified only and only if actual node changed its state,
+         * since deletion of nested child (in this case /nested-list/nested-list[foo],
+         * did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        Future<?> future = task.getChangeEvent();
+        /*
+         * One listener should be notified only and only if actual node changed its state,
+         * since deletion of nested child (in this case /nested-list/nested-list[foo],
+         * did not result in change of node we are listening
+         * for, we should not be getting data change event
+         * and this means settable future containing receivedDataChangeEvent is not done.
+         *
+         */
+        assertFalse(future.isDone());
+    }
+
+    @Override
+    protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO_SIBLING));
+        assertNotContains(change.getUpdatedData(),path(FOO), TOP_LEVEL);
+        assertEmpty(change.getRemovedPaths());
+    }
+}
diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeSubtreeTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/WildcardedScopeSubtreeTest.java
new file mode 100644 (file)
index 0000000..0102048
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeSubtreeTest extends DefaultDataChangeListenerTestSuite {
+
+    private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+            TopLevelList.QNAME);
+
+    @Override
+    protected void customizeTask(final DatastoreTestTask task) {
+        task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.SUBTREE);
+    }
+
+    @Override
+    public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+
+        assertContains(change.getCreatedData(), path(FOO, BAZ));
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+    }
+
+    @Override
+    protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+        assertFalse(change.getCreatedData().isEmpty());
+
+        assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+        assertNotContains(change.getCreatedData(), TOP_LEVEL);
+        assertEmpty(change.getUpdatedData());
+        assertEmpty(change.getRemovedPaths());
+
+    }
+
+    @Override
+    protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+        assertNotNull(change);
+        assertTrue(change.getCreatedData().isEmpty());
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(),path(FOO, BAZ));
+    }
+
+    @Override
+    public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+            ExecutionException {
+
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertEmpty(change.getCreatedData());
+        assertEmpty(change.getUpdatedData());
+
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+    }
+
+    @Override
+    protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO,BAZ));
+        assertNotContains(change.getCreatedData(), path(FOO,BAR));
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO,BAR),path(FOO,BAZ));
+        assertContains(change.getUpdatedData(), path(FOO));
+        assertNotContains(change.getUpdatedData(), TOP_LEVEL, path(FOO,BAR));
+        assertEmpty(change.getRemovedPaths());
+    }
+
+    @Override
+    protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+        AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+        assertContains(change.getCreatedData(), path(FOO_SIBLING));
+        assertNotContains(change.getUpdatedData(), path(FOO), TOP_LEVEL);
+        assertEmpty(change.getRemovedPaths());
+    }
+}
diff --git a/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-mdsal-list-test.yang b/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-mdsal-list-test.yang
new file mode 100644 (file)
index 0000000..48666e1
--- /dev/null
@@ -0,0 +1,51 @@
+module opendaylight-mdsal-list-test {
+
+    namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:list";
+    prefix list-test;
+
+
+    description
+        "This module contains a collection of YANG definitions used for
+        some test cases.";
+
+    revision 2014-07-01 {
+        description
+        "Test model for testing data broker with nested lists.";
+    }
+
+    grouping two-level-list {
+        list top-level-list {
+            description
+                "Top Level List";
+            key "name";
+            leaf name {
+                type string;
+            }
+            list nested-list {
+                key "name";
+                leaf name {
+                    type string;
+                }
+                leaf type {
+                    type string;
+                    mandatory true;
+                    description
+                        "Mandatory type of list.";
+                }
+                ordered-by user;
+                description
+                    "A list of service functions that compose the service chain";
+            }
+        }
+    }
+
+    container top {
+        uses two-level-list;
+    }
+
+    rpc put-top {
+        input {
+            uses two-level-list;
+        }
+    }
+}
\ No newline at end of file

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.