analysis: basic checks for acyclic property of TmfGraph
authorFrancis Giraldeau <francis.giraldeau@gmail.com>
Thu, 22 Oct 2015 01:23:57 +0000 (21:23 -0400)
committerMatthew Khouzam <matthew.khouzam@ericsson.com>
Thu, 22 Oct 2015 23:29:21 +0000 (19:29 -0400)
The TmfGraph is Directed Acyclic Graph (DAG). This patch adds three
checks:

 * verify that a node is not linked to itself (this was possible because
the timestamp is greater or equal to itself).
 * assert that getHead() will terminate in the special case where the
tail points to the head indirectly.
 * fix infinite loop bug with TmfGraph.scanLineTraverse() with a
specific graph.

Change-Id: I68a3eaed2c1098df1547d1fba34c35bc1d038404
Signed-off-by: Francis Giraldeau <francis.giraldeau@gmail.com>
Reviewed-on: https://git.eclipse.org/r/58682
Reviewed-by: Hudson CI
Reviewed-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
Tested-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
analysis/org.eclipse.tracecompass.analysis.graph.core.tests/src/org/eclipse/tracecompass/analysis/graph/core/tests/graph/TmfGraphTest.java
analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/CycleDetectedException.java [new file with mode: 0644]
analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/Messages.java
analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/TmfGraph.java
analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/TmfVertex.java
analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/messages.properties

index 946714b819b645c41d2ec75ede5c7b42cb9dcf72..9480d7c2c7e03f15a442e139d306eb86b09929f4 100644 (file)
@@ -18,9 +18,12 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.analysis.graph.core.base.CycleDetectedException;
 import org.eclipse.tracecompass.analysis.graph.core.base.IGraphWorker;
 import org.eclipse.tracecompass.analysis.graph.core.base.ITmfGraphVisitor;
 import org.eclipse.tracecompass.analysis.graph.core.base.TmfEdge;
@@ -45,6 +48,7 @@ public class TmfGraphTest {
 
     private static final @NonNull IGraphWorker WORKER1 = new TestGraphWorker(1);
     private static final @NonNull IGraphWorker WORKER2 = new TestGraphWorker(2);
+    private static final @NonNull IGraphWorker WORKER3 = new TestGraphWorker(3);
 
     private final @NonNull TmfGraph fGraph = new TmfGraph();
     private final @NonNull TmfVertex fV0 = new TmfVertex(0);
@@ -406,4 +410,91 @@ public class TmfGraphTest {
         assertEquals(23, stats.getSum().longValue());
     }
 
+    /**
+     * This visitor throws an exception if it visits twice the same vertex
+     *
+     * @author Francis Giraldeau
+     *
+     */
+    private class DuplicateDetectorVisitor implements ITmfGraphVisitor {
+        private final Set<TmfVertex> set = new HashSet<>();
+        @Override
+        public void visitHead(TmfVertex vertex) {
+        }
+        @Override
+        public void visit(TmfEdge edge, boolean horizontal) {
+        }
+        @Override
+        public void visit(TmfVertex vertex) {
+            if (set.contains(vertex)) {
+                throw new RuntimeException("node already visited");
+            }
+            set.add(vertex);
+        }
+    }
+
+    /**
+     * Test that exception is thrown if a node is linked horizontally to itself
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testHorizontalSelfLink() {
+        TmfVertex n0 = new TmfVertex(0);
+        n0.linkHorizontal(n0);
+    }
+
+    /**
+     * Test that exception is thrown if a node is linked vertically to itself.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testVerticalSelfLink() {
+        TmfVertex n0 = new TmfVertex(0);
+        n0.linkVertical(n0);
+    }
+
+    /**
+     * Test that the visitor detect a cycle in horizontal links. A cycle may
+     * exists only between vertices with equal timetstamps.
+     */
+    @Test(expected = CycleDetectedException.class)
+    public void testHorizontalCycle() {
+        TmfVertex n0 = new TmfVertex(0);
+        TmfVertex n1 = new TmfVertex(0);
+        n0.linkHorizontal(n1);
+        n1.linkHorizontal(n0);
+        TmfGraph graph = new TmfGraph();
+        graph.add(WORKER1, n0);
+        graph.add(WORKER1, n1);
+        graph.scanLineTraverse(n0, new DuplicateDetectorVisitor());
+    }
+
+    /**
+     * Test that scanLineTraverse terminates with the following graph:
+     *
+     * <pre>
+     *          ^
+     *          |
+     *    +----->
+     *    ^
+     *    |
+     *    +
+     *
+     * </pre>
+     */
+    @Test
+    public void testScanLineTerminates() {
+        TmfVertex n10 = new TmfVertex(0);
+        TmfVertex n20 = new TmfVertex(0);
+        TmfVertex n21 = new TmfVertex(1);
+        TmfVertex n30 = new TmfVertex(1);
+        TmfGraph graph = new TmfGraph();
+        n10.linkVertical(n20);
+        n20.linkHorizontal(n21);
+        n21.linkVertical(n30);
+        graph.add(WORKER1, n10);
+        graph.add(WORKER2, n20);
+        graph.add(WORKER2, n21);
+        graph.add(WORKER3, n30);
+        graph.scanLineTraverse(n20, new DuplicateDetectorVisitor());
+    }
+
 }
diff --git a/analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/CycleDetectedException.java b/analysis/org.eclipse.tracecompass.analysis.graph.core/src/org/eclipse/tracecompass/analysis/graph/core/base/CycleDetectedException.java
new file mode 100644 (file)
index 0000000..faadd49
--- /dev/null
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2015 École Polytechnique de Montréal
+ *
+ * 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.eclipse.tracecompass.analysis.graph.core.base;
+
+/**
+ * This exception indicates that a cycle was detected while traversing the graph.
+ *
+ * @author Francis Giraldeau
+ *
+ */
+public class CycleDetectedException extends RuntimeException {
+
+    /**
+     * Serial version
+     */
+    private static final long serialVersionUID = 8906101447850670255L;
+
+}
index 57ba37458386a49894d73efa3355168a8f0b96a6..a1e5465d9e6f2f0d153fbb22984fbc51f4f17f8f 100644 (file)
@@ -26,6 +26,8 @@ public class Messages extends NLS {
     public static @Nullable String TmfGraph_FromNotInGraph;
 
     public static @Nullable String TmfVertex_ArgumentTimestampLower;
+
+    public static @Nullable String TmfVertex_CannotLinkToSelf;
     static {
         // initialize resource bundle
         NLS.initializeMessages(BUNDLE_NAME, Messages.class);
index 763e6d0926e708dedbdc8f8f36dbda74d0c96a62..715859b6904aa2920ffc97e1e43ddfa75cb0d433 100644 (file)
@@ -243,6 +243,9 @@ public class TmfGraph {
         TmfEdge edge = headNode.getEdge(EdgeDirection.INCOMING_HORIZONTAL_EDGE);
         while (edge != null) {
             headNode = edge.getVertexFrom();
+            if (headNode == vertex) {
+                throw new CycleDetectedException();
+            }
             edge = headNode.getEdge(EdgeDirection.INCOMING_HORIZONTAL_EDGE);
         }
         return headNode;
@@ -343,8 +346,10 @@ public class TmfGraph {
             // process one line
             TmfVertex n = getHead(curr);
             visitor.visitHead(n);
-            while (n != null) {
+            while (true) {
                 visitor.visit(n);
+                visited.add(n);
+
                 // Only visit links up-right, guarantee to visit once only
                 TmfEdge edge = n.getEdge(EdgeDirection.OUTGOING_VERTICAL_EDGE);
                 if (edge != null) {
@@ -360,9 +365,9 @@ public class TmfGraph {
                     visitor.visit(edge, true);
                     n = edge.getVertexTo();
                 } else {
-                    n = null;
+                    // end of the horizontal list
+                    break;
                 }
-                visited.add(n);
             }
         }
     }
index 220390c394c60f7be37de2d6721378571f13ffff..1a9aaaa01ad1c86ab2eca6bd5b5d43c1c8c309a6 100644 (file)
@@ -169,6 +169,7 @@ public class TmfVertex implements Comparable<TmfVertex> {
      */
     public TmfEdge linkHorizontal(TmfVertex to) {
         checkTimestamps(to);
+        checkNotSelf(to);
         return linkHorizontalRaw(to);
     }
 
@@ -188,6 +189,7 @@ public class TmfVertex implements Comparable<TmfVertex> {
      */
     public TmfEdge linkVertical(TmfVertex to) {
         checkTimestamps(to);
+        checkNotSelf(to);
         return linkVerticalRaw(to);
     }
 
@@ -205,6 +207,13 @@ public class TmfVertex implements Comparable<TmfVertex> {
         }
     }
 
+    private void checkNotSelf(TmfVertex to) {
+        if (this == to) {
+            throw new IllegalArgumentException(Messages.TmfVertex_CannotLinkToSelf);
+        }
+    }
+
+
     /**
      * Get an edge to or from this vertex in the appropriate direction
      *
index f41dab891d767bd3093f2ad8c96543137f16a28c..a6fa8cb6c2fa65a703606b65c6fe544617853018 100644 (file)
@@ -11,4 +11,5 @@
 ###############################################################################
 
 TmfGraph_FromNotInGraph=The 'from' vertex is not in the graph
-TmfVertex_ArgumentTimestampLower=Next node timestamps must be greater or equal to current timestamps
\ No newline at end of file
+TmfVertex_ArgumentTimestampLower=Next node timestamps must be greater or equal to current timestamps
+TmfVertex_CannotLinkToSelf=Cannot link to self
This page took 0.053593 seconds and 5 git commands to generate.