Bug 9060: TracingBroker printOpenTransactions 03/62503/1
authorMichael Vorburger <vorburger@redhat.com>
Mon, 28 Aug 2017 16:38:50 +0000 (18:38 +0200)
committerMichael Vorburger <vorburger@redhat.com>
Thu, 31 Aug 2017 14:14:16 +0000 (16:14 +0200)
This method is intended to be used from a Karaf CLI command in the next
change (and maybe JMX or something else like that later), which can be
invoked during future automated testing to detect Tx leaks during CSIT.

This is one of a serious of commits which is part of a solution I'm
proposing in order to be able to detect OOM issues such as Bug 9034,
based on using the mdsal-trace DataBroker.

Change-Id: I682700bef9644834e8b4ca36b21729f021a76bf0
Signed-off-by: Michael Vorburger <vorburger@redhat.com>
12 files changed:
opendaylight/md-sal/mdsal-trace/api/src/main/java/org/opendaylight/controller/md/sal/trace/api/TracingDOMDataBroker.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/AbstractCloseTracked.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTracked.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistry.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedTrait.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingBroker.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadWriteTransaction.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingTransactionChain.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingWriteTransaction.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/tests/CloseTrackedRegistryTest.java
opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/tests/TracingBrokerTest.java [new file with mode: 0644]

index 14d30b123e82c172eb1df421f0b4ad47d0054dbf..34d5eb4a2ad18aabaa4f16f6cc482ffd98b9e846 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.md.sal.trace.api;
 
+import java.io.PrintStream;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
 
 /**
@@ -14,4 +15,11 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
  */
 public interface TracingDOMDataBroker extends DOMDataBroker {
 
+    /**
+     * Prints a human-readable "report" of all opened but not closed transactions,
+     * including transactions chains and transactions opened by them, onto the printStream.
+     * @return true if there were any open transactions, false if none
+     */
+    boolean printOpenTransactions(PrintStream printStream);
+
 }
index aaafeb4bec11d6e52e0fb843e0d2fc43b4d4c37e..785e2ef6ae86e1b154c447bc469b636c54582152 100644 (file)
@@ -19,7 +19,7 @@ public abstract class AbstractCloseTracked<T extends AbstractCloseTracked<T>> im
     private final CloseTrackedTrait<T> closeTracker;
 
     protected AbstractCloseTracked(CloseTrackedRegistry<T> transactionChainRegistry) {
-        this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry);
+        this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry, this);
     }
 
     protected void removeFromTrackedRegistry() {
@@ -30,4 +30,9 @@ public abstract class AbstractCloseTracked<T extends AbstractCloseTracked<T>> im
     public @Nullable StackTraceElement[] getAllocationContextStackTrace() {
         return closeTracker.getAllocationContextStackTrace();
     }
+
+    @Override
+    public final CloseTracked<T> getRealCloseTracked() {
+        return this;
+    }
 }
index f5fd341b55c4a169e19b6a82b593de9b70e92536..926c8b3b8ee5e69dfb4a221bcafb5c815b257e7b 100644 (file)
@@ -10,9 +10,9 @@ package org.opendaylight.controller.md.sal.trace.closetracker.impl;
 import javax.annotation.Nullable;
 
 /**
- * Object which can track where it has been created, and if it has been correctly "closed".
+ * Object which can track where something has been created, and if it has been correctly "closed".
  *
- * <p>Includes preserving the context of the call stack which created this object, and the instant it was created.
+ * <p>Includes preserving the context of the call stack which created an object, and the instant it was created.
  *
  * @author Michael Vorburger.ch
  */
@@ -20,4 +20,5 @@ public interface CloseTracked<T extends CloseTracked<T>> {
 
     @Nullable StackTraceElement[] getAllocationContextStackTrace();
 
+    CloseTracked<T> getRealCloseTracked();
 }
index 7689831644585c9af13fdd661abe6862c4df4ea1..4e608396303b94e2dd1bca31130349a856ac7ebc 100644 (file)
@@ -7,6 +7,9 @@
  */
 package org.opendaylight.controller.md.sal.trace.closetracker.impl;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -25,9 +28,8 @@ import javax.annotation.concurrent.ThreadSafe;
 @ThreadSafe
 public class CloseTrackedRegistry<T extends CloseTracked<T>> {
 
-    // unused OK for now, at least we'll be able to see this in HPROF heap dumps and know what is which
-    private final @SuppressWarnings("unused") Object anchor;
-    private final @SuppressWarnings("unused") String createDescription;
+    private final Object anchor;
+    private final String createDescription;
 
     private final Set<CloseTracked<T>> tracked = new ConcurrentSkipListSet<>(
         (o1, o2) -> Integer.compare(System.identityHashCode(o1), System.identityHashCode(o2)));
@@ -58,6 +60,14 @@ public class CloseTrackedRegistry<T extends CloseTracked<T>> {
         return isDebugContextEnabled;
     }
 
+    public Object getAnchor() {
+        return anchor;
+    }
+
+    public String getCreateDescription() {
+        return createDescription;
+    }
+
     // package protected, not public; only CloseTrackedTrait invokes this
     void add(CloseTracked<T> closeTracked) {
         tracked.add(closeTracked);
@@ -72,20 +82,33 @@ public class CloseTrackedRegistry<T extends CloseTracked<T>> {
      * Creates and returns a "report" of (currently) tracked but not (yet) closed
      * instances.
      *
-     * @return Map where key is the StackTraceElement[] identifying a unique
-     *         allocation contexts (or an empty List if debugContextEnabled is false),
-     *         and value is the number of open instances created at that point.
+     * @return Set of CloseTrackedRegistryReportEntry, of which each the stack trace
+     *         element identifies a unique allocation context (or an empty List if
+     *         debugContextEnabled is false), and value is the number of open
+     *         instances created at that place in the code.
      */
-    public Map<List<StackTraceElement>, Long> getAllUnique() {
-        Map<List<StackTraceElement>,Long> mapToReturn = new HashMap<>();
+    public Set<CloseTrackedRegistryReportEntry<T>> getAllUnique() {
+        Map<List<StackTraceElement>, Long> map = new HashMap<>();
         Set<CloseTracked<T>> copyOfTracked = new HashSet<>(tracked);
         for (CloseTracked<T> closeTracked : copyOfTracked) {
             final StackTraceElement[] stackTraceArray = closeTracked.getAllocationContextStackTrace();
             List<StackTraceElement> stackTraceElements =
                     stackTraceArray != null ? Arrays.asList(stackTraceArray) : Collections.emptyList();
-            mapToReturn.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
+            map.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
         }
-        return mapToReturn;
+
+        Set<CloseTrackedRegistryReportEntry<T>> report = new HashSet<>();
+        map.forEach((stackTraceElements, number) -> {
+            copyOfTracked.stream().filter(closeTracked -> {
+                StackTraceElement[] closeTrackedStackTraceArray = closeTracked.getAllocationContextStackTrace();
+                List<StackTraceElement> closeTrackedStackTraceElements =
+                        closeTrackedStackTraceArray != null ? asList(closeTrackedStackTraceArray) : emptyList();
+                return closeTrackedStackTraceElements.equals(stackTraceElements);
+            }).findAny().ifPresent(exampleCloseTracked -> {
+                report.add(new CloseTrackedRegistryReportEntry<>(exampleCloseTracked, number, stackTraceElements));
+            });
+        });
+        return report;
     }
 
 }
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java
new file mode 100644 (file)
index 0000000..1681eb8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.trace.closetracker.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+/**
+ * Element of a "report" created by a {@link CloseTrackedRegistry}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class CloseTrackedRegistryReportEntry<T extends CloseTracked<T>> {
+
+    private final CloseTracked<T> exampleCloseTracked;
+    private final long numberAddedNotRemoved;
+    private final List<StackTraceElement> stackTraceElements;
+
+    public CloseTrackedRegistryReportEntry(CloseTracked<T> exampleCloseTracked, long numberAddedNotRemoved,
+            List<StackTraceElement> stackTraceElements) {
+        super();
+        this.exampleCloseTracked = requireNonNull(exampleCloseTracked, "closeTracked");
+        this.numberAddedNotRemoved = requireNonNull(numberAddedNotRemoved, "numberAddedNotRemoved");
+        this.stackTraceElements = requireNonNull(stackTraceElements, "stackTraceElements");
+    }
+
+    public long getNumberAddedNotRemoved() {
+        return numberAddedNotRemoved;
+    }
+
+    public CloseTracked<T> getExampleCloseTracked() {
+        return exampleCloseTracked;
+    }
+
+    public List<StackTraceElement> getStackTraceElements() {
+        return stackTraceElements;
+    }
+
+    @Override
+    public String toString() {
+        return "CloseTrackedRegistryReportEntry [numberAddedNotRemoved=" + numberAddedNotRemoved + ", closeTracked="
+                + exampleCloseTracked + ", stackTraceElements.size=" + stackTraceElements.size() + "]";
+    }
+
+
+}
index b846c042d192e831bc67b8eca223c3a275b919de..b1b41319e5527610cf84eb649bcf9db669b099e2 100644 (file)
@@ -24,13 +24,15 @@ public class CloseTrackedTrait<T extends CloseTracked<T>> implements CloseTracke
 
     private final @Nullable Throwable allocationContext;
     private final CloseTrackedRegistry<T> closeTrackedRegistry;
+    private final CloseTracked<T> realCloseTracked;
 
-    public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry) {
+    public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry, CloseTracked<T> realCloseTracked) {
         if (transactionChainRegistry.isDebugContextEnabled()) {
             this.allocationContext = new Throwable("allocated at");
         } else {
             this.allocationContext = null;
         }
+        this.realCloseTracked = Objects.requireNonNull(realCloseTracked, "realCloseTracked");
         this.closeTrackedRegistry = Objects.requireNonNull(transactionChainRegistry, "transactionChainRegistry");
         this.closeTrackedRegistry.add(this);
     }
@@ -44,4 +46,9 @@ public class CloseTrackedTrait<T extends CloseTracked<T>> implements CloseTracke
         closeTrackedRegistry.remove(this);
     }
 
+    @Override
+    public CloseTracked<T> getRealCloseTracked() {
+        return realCloseTracked;
+    }
+
 }
index c01c1d9cd3b36bae78ecb03c0bf2fc00173091ef..b08e19ace7337d4a4d2e5c954845ba4e486a34e3 100644 (file)
@@ -7,13 +7,13 @@
  */
 package org.opendaylight.controller.md.sal.trace.dom.impl;
 
+import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import javax.annotation.Nonnull;
-
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
@@ -356,4 +356,47 @@ public class TracingBroker implements TracingDOMDataBroker {
 
         return res;
     }
+
+    public boolean printOpenTransactions(PrintStream ps) {
+        if (transactionChainsRegistry.getAllUnique().isEmpty()
+            && readOnlyTransactionsRegistry.getAllUnique().isEmpty()
+            && writeTransactionsRegistry.getAllUnique().isEmpty()
+            && readWriteTransactionsRegistry.getAllUnique().isEmpty()) {
+
+            return false;
+        }
+
+        ps.println(getClass().getSimpleName() + " found not yet (or never..) closed transaction[chain]s");
+        ps.println();
+        printRegistryOpenTransactions(readOnlyTransactionsRegistry, ps, "");
+        printRegistryOpenTransactions(writeTransactionsRegistry, ps, "");
+        printRegistryOpenTransactions(readWriteTransactionsRegistry, ps, "");
+
+        // Now print details for each non-closed TransactionChain
+        // incl. in turn each ones own read/Write[Only]TransactionsRegistry
+        ps.println(transactionChainsRegistry.getCreateDescription());
+        transactionChainsRegistry.getAllUnique().forEach(entry -> {
+            ps.println("  " + entry.getNumberAddedNotRemoved() + "x TransactionChains opened, which are not closed:");
+            entry.getStackTraceElements().forEach(line -> ps.println("    " + line));
+            @SuppressWarnings("resource")
+            TracingTransactionChain txChain = (TracingTransactionChain) entry
+                .getExampleCloseTracked().getRealCloseTracked();
+            printRegistryOpenTransactions(txChain.getReadOnlyTransactionsRegistry(), ps, "    ");
+            printRegistryOpenTransactions(txChain.getWriteTransactionsRegistry(), ps, "    ");
+            printRegistryOpenTransactions(txChain.getReadWriteTransactionsRegistry(), ps, "    ");
+        });
+        ps.println();
+
+        return true;
+    }
+
+    private void printRegistryOpenTransactions(CloseTrackedRegistry<?> registry, PrintStream ps, String indent) {
+        ps.println(indent + registry.getCreateDescription());
+        registry.getAllUnique().forEach(entry -> {
+            ps.println(indent + "  " + entry.getNumberAddedNotRemoved()
+                + "x transactions opened here, which are not closed:");
+            entry.getStackTraceElements().forEach(line -> ps.print("    " + line));
+        });
+        ps.println();
+    }
 }
index fe57c8108d817a94d0699f3704daa5b6eb8e179d..091e2a45f79a532561d466c04cddfee6aee9756f 100644 (file)
@@ -33,7 +33,7 @@ class TracingReadWriteTransaction
     TracingReadWriteTransaction(DOMDataReadWriteTransaction delegate, TracingBroker tracingBroker,
             CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry) {
         super(delegate, tracingBroker);
-        this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry);
+        this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry, this);
         this.delegate = Objects.requireNonNull(delegate);
     }
 
@@ -70,4 +70,9 @@ class TracingReadWriteTransaction
     public StackTraceElement[] getAllocationContextStackTrace() {
         return closeTracker.getAllocationContextStackTrace();
     }
+
+    @Override
+    public CloseTracked<TracingReadWriteTransaction> getRealCloseTracked() {
+        return this;
+    }
 }
index c8f4539cc7ba65353946a993f585a276bf53086b..ac6aaa8bdb3c94587413bf43931c05c4ab969a83 100644 (file)
@@ -29,10 +29,11 @@ class TracingTransactionChain extends AbstractCloseTracked<TracingTransactionCha
         this.delegate = Objects.requireNonNull(delegate);
         this.tracingBroker = Objects.requireNonNull(tracingBroker);
 
-        final boolean isDebugging = transactionChainsRegistry.isDebugContextEnabled();
-        this.readOnlyTransactionsRegistry  = new CloseTrackedRegistry<>(this, "newReadOnlyTransaction", isDebugging);
-        this.writeTransactionsRegistry     = new CloseTrackedRegistry<>(this, "newWriteOnlyTransaction", isDebugging);
-        this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadWriteTransaction", isDebugging);
+        final boolean isDebug = transactionChainsRegistry.isDebugContextEnabled();
+        String pf = "TransactionChain_" + toString();
+        this.readOnlyTransactionsRegistry  = new CloseTrackedRegistry<>(this, pf + "newReadOnlyTransaction", isDebug);
+        this.writeTransactionsRegistry     = new CloseTrackedRegistry<>(this, pf + "newWriteOnlyTransaction", isDebug);
+        this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, pf + "newReadWriteTransaction", isDebug);
     }
 
     @Override
@@ -60,4 +61,15 @@ class TracingTransactionChain extends AbstractCloseTracked<TracingTransactionCha
         super.removeFromTrackedRegistry();
     }
 
+    public CloseTrackedRegistry<TracingReadOnlyTransaction> getReadOnlyTransactionsRegistry() {
+        return readOnlyTransactionsRegistry;
+    }
+
+    public CloseTrackedRegistry<TracingReadWriteTransaction> getReadWriteTransactionsRegistry() {
+        return readWriteTransactionsRegistry;
+    }
+
+    public CloseTrackedRegistry<TracingWriteTransaction> getWriteTransactionsRegistry() {
+        return writeTransactionsRegistry;
+    }
 }
index 7e98e40e2ab8e7f4592e7c34f36bbbb8ba850814..5cd0d631d3212378f51f05cccf28c313dbf3b3a1 100644 (file)
@@ -25,7 +25,7 @@ class TracingWriteTransaction extends AbstractTracingWriteTransaction
     TracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker,
             CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry) {
         super(delegate, tracingBroker);
-        this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry);
+        this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry, this);
     }
 
     @Override
@@ -51,4 +51,9 @@ class TracingWriteTransaction extends AbstractTracingWriteTransaction
         return closeTracker.getAllocationContextStackTrace();
     }
 
+    @Override
+    public CloseTracked<TracingWriteTransaction> getRealCloseTracked() {
+        return this;
+    }
+
 }
index ba34cbf28c385b3fb40ed5d061dd55be49afb0e8..a3a8b495bcde65fff339b5066b38237860afcfb2 100644 (file)
@@ -10,12 +10,12 @@ package org.opendaylight.controller.md.sal.trace.closetracker.impl.tests;
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 
-import java.util.List;
-import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 import org.junit.Test;
 import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
 import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistryReportEntry;
 
 public class CloseTrackedRegistryTest {
 
@@ -43,18 +43,21 @@ public class CloseTrackedRegistryTest {
         @SuppressWarnings({ "resource", "unused" })
         SomethingClosable forgotToCloseOnce = new SomethingClosable(registry);
 
-        Map<List<StackTraceElement>, Long> uniqueNonClosed = registry.getAllUnique();
-        assertThat(uniqueNonClosed.values()).containsExactly(100L, 1L);
-        uniqueNonClosed.forEach((stackTraceElements, occurrences) -> {
-            if (occurrences == 100) {
-                assertThatIterableContains(stackTraceElements,
+        Set<CloseTrackedRegistryReportEntry<SomethingClosable>> uniqueNonClosed = registry.getAllUnique();
+        assertThat(uniqueNonClosed).hasSize(2);
+        assertThatIterableContains(uniqueNonClosed, entry ->
+            entry.getNumberAddedNotRemoved() == 100 || entry.getNumberAddedNotRemoved() == 1);
+        uniqueNonClosed.forEach(entry -> {
+            if (entry.getNumberAddedNotRemoved() == 100) {
+                assertThatIterableContains(entry.getStackTraceElements(),
                     element -> element.getMethodName().equals("someOtherMethodWhichDoesNotClose"));
-            } else { // occurrences == 1
-                assertThatIterableContains(stackTraceElements,
+            } else if (entry.getNumberAddedNotRemoved() == 1) {
+                assertThatIterableContains(entry.getStackTraceElements(),
                     element -> element.getMethodName().equals("testDuplicateAllocationContexts"));
+            } else {
+                fail("Unexpected number of added, not removed: " + entry.getNumberAddedNotRemoved());
             }
         });
-        // one of the two has a StackTraceElement containing
     }
 
     // Something like this really should be in Google Truth...
@@ -80,8 +83,13 @@ public class CloseTrackedRegistryTest {
 
         SomethingClosable forgotToCloseOnce = new SomethingClosable(debugContextDisabledRegistry);
 
-        assertThat(debugContextDisabledRegistry.getAllUnique()).hasSize(1);
-        assertThat(debugContextDisabledRegistry.getAllUnique().values().iterator().next()).isEqualTo(1);
-        assertThat(debugContextDisabledRegistry.getAllUnique().keySet().iterator().next()).isEmpty();
+        Set<CloseTrackedRegistryReportEntry<SomethingClosable>>
+            closeRegistryReport = debugContextDisabledRegistry.getAllUnique();
+        assertThat(closeRegistryReport).hasSize(1);
+
+        CloseTrackedRegistryReportEntry<SomethingClosable>
+            closeRegistryReportEntry1 = closeRegistryReport.iterator().next();
+        assertThat(closeRegistryReportEntry1.getNumberAddedNotRemoved()).isEqualTo(1);
+        assertThat(closeRegistryReportEntry1.getStackTraceElements()).isEmpty();
     }
 }
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/tests/TracingBrokerTest.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/tests/TracingBrokerTest.java
new file mode 100644 (file)
index 0000000..0324986
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 Red Hat, 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.trace.closetracker.impl.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.ConfigBuilder;
+
+/**
+ * Test of {@link TracingBroker}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class TracingBrokerTest {
+
+    @Test
+    @SuppressWarnings({ "resource", "unused" }) // Finding resource leaks is the point of this test
+    public void testPrintOpenTransactions() {
+        DOMDataBroker domDataBroker = mock(DOMDataBroker.class, RETURNS_DEEP_STUBS);
+        Config config = new ConfigBuilder().setTransactionDebugContextEnabled(true).build();
+        BindingNormalizedNodeSerializer codec = mock(BindingNormalizedNodeSerializer.class);
+        TracingBroker tracingBroker = new TracingBroker(domDataBroker, config, codec);
+
+        DOMTransactionChain txChain = tracingBroker.createTransactionChain(null);
+        DOMDataReadWriteTransaction tx = txChain.newReadWriteTransaction();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        boolean printReturnValue = tracingBroker.printOpenTransactions(ps);
+        String output = new String(baos.toByteArray(), UTF_8);
+
+        assertThat(printReturnValue).isTrue();
+        assertThat(output).contains("testPrintOpenTransactions(TracingBrokerTest.java:41)"); // in a stack trace
+
+        // We don't do any verify/times on the mocks,
+        // because the main point of the test is just to verify that
+        // printOpenTransactions runs through without any exceptions
+        // (e.g. it used to have a ClassCastException).
+    }
+
+}