| Index: mojom/mojom_parser/utils/wellfounded_graphs_test.go
|
| diff --git a/mojom/mojom_parser/utils/wellfounded_graphs_test.go b/mojom/mojom_parser/utils/wellfounded_graphs_test.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..958fa9a5882c7f2897b8f85b12c92a98a60508da
|
| --- /dev/null
|
| +++ b/mojom/mojom_parser/utils/wellfounded_graphs_test.go
|
| @@ -0,0 +1,1349 @@
|
| +package utils
|
| +
|
| +import (
|
| + "bytes"
|
| + "fmt"
|
| + "strings"
|
| + "testing"
|
| +)
|
| +
|
| +////////////////////////////////////////////////////
|
| +// SimpleNode type
|
| +////////////////////////////////////////////////////
|
| +
|
| +// SimpleNode is a simple implementation of the |Node| interface used
|
| +// for testing.
|
| +type SimpleNode struct {
|
| + // The value of this node
|
| + value int
|
| +
|
| + // Is this node a square node?
|
| + isSquare bool
|
| +
|
| + // The list of out-edges. The elements will be *SimpleNodes.
|
| + edges []Node
|
| +
|
| + // Cache the fact that |SetKnownWellFounded| has been invoked.
|
| + knownWellFounded bool
|
| +}
|
| +
|
| +func (node *SimpleNode) DebugName() string {
|
| + return fmt.Sprintf("Node%d", node.value)
|
| +}
|
| +
|
| +func (node *SimpleNode) OutEdges() []Node {
|
| + return node.edges
|
| +}
|
| +
|
| +func (node *SimpleNode) IsSquare() bool {
|
| + return node.isSquare
|
| +}
|
| +
|
| +func (node *SimpleNode) KnownWellFounded() bool {
|
| + return node.knownWellFounded
|
| +}
|
| +
|
| +func (node *SimpleNode) SetKnownWellFounded() {
|
| + if node.knownWellFounded {
|
| + panic(fmt.Sprintf("SetKnownWellFounded invoked twice on %s", node.DebugName()))
|
| + }
|
| + node.knownWellFounded = true
|
| +}
|
| +
|
| +//Creates a new SimpleNode.
|
| +func NewNode(value, numEdges int, isSquare bool) *SimpleNode {
|
| + node := new(SimpleNode)
|
| + node.value = value
|
| + node.edges = make([]Node, numEdges)
|
| + node.isSquare = isSquare
|
| + return node
|
| +}
|
| +
|
| +////////////////////////////////////////////////////
|
| +// Test Utilities
|
| +////////////////////////////////////////////////////
|
| +
|
| +// Builds a test graph containing only circle nodes.
|
| +// The connection matrix should have the following form:
|
| +// connectionMatrix[i][j] = k indicates that there is an edge in the graph
|
| +// from Node i to Node K. k must be less than len(connectionMatrix).
|
| +func buildCircleTestGraph(connectionMatrix [][]int) (nodes []*SimpleNode) {
|
| + return buildTestGraph(connectionMatrix, nil)
|
| +}
|
| +
|
| +// Builds a test graph. The connection matrix should have the following form:
|
| +// connectionMatrix[i][j] = k indicates that there is an edge in the graph
|
| +// from Node i to Node K. k must be less than len(connectionMatrix).
|
| +// If |squares| is not nil then it must contain the list of indices of the nodes that are squares.
|
| +func buildTestGraph(connectionMatrix [][]int, squares []int) (nodes []*SimpleNode) {
|
| + squareMap := make(map[int]bool)
|
| + if squares != nil {
|
| + for _, n := range squares {
|
| + squareMap[n] = true
|
| + }
|
| + }
|
| + nodes = make([]*SimpleNode, len(connectionMatrix))
|
| + for index, connectionList := range connectionMatrix {
|
| + _, isSquare := squareMap[index]
|
| + nodes[index] = NewNode(index, len(connectionList), isSquare)
|
| + }
|
| + for index, connectionList := range connectionMatrix {
|
| + for i, target := range connectionList {
|
| + nodes[index].edges[i] = nodes[target]
|
| + }
|
| + }
|
| + return
|
| +}
|
| +
|
| +// expectedDebugData is used to store an expectation for the value of the |debugData| populated
|
| +// by the ethod |checkWellFounded|. This data captures the result of phase 1 of the algorithm.
|
| +type expectedDebugData struct {
|
| + initialPendingSet []string
|
| + initialFoundationSet []string
|
| +}
|
| +
|
| +func compareDebugData(expected *expectedDebugData, actual *debugData) error {
|
| + if expected == nil {
|
| + panic("expected cannot be nil")
|
| + return nil
|
| + }
|
| + if err := compareNodeSlice(expected.initialPendingSet, actual.initialPendingSet, "initialPendingSet"); err != nil {
|
| + return err
|
| + }
|
| + return compareNodeSlice(expected.initialFoundationSet, actual.initialFoundationSet, "initialFoundationSet")
|
| +}
|
| +
|
| +func compareNodeSlice(expected []string, actual []Node, name string) error {
|
| + expectedMap := make(map[string]bool)
|
| + actualMap := make(map[string]bool)
|
| + for _, n := range expected {
|
| + expectedMap[n] = true
|
| + }
|
| + for _, n := range actual {
|
| + actualMap[n.DebugName()] = true
|
| + }
|
| + for _, n := range expected {
|
| + if _, ok := actualMap[n]; !ok {
|
| + return fmt.Errorf("%s: missing %s, actual: %s", name, n, NodeSliceToString(actual))
|
| + }
|
| + }
|
| + for _, n := range actual {
|
| + if _, ok := expectedMap[n.DebugName()]; !ok {
|
| + return fmt.Errorf("%s: unexpected: %s, expected: %v, actual: %s", name, n.DebugName(), expected, NodeSliceToString(actual))
|
| + }
|
| + }
|
| + return nil
|
| +}
|
| +
|
| +func NodeSliceToString(nodeSlice []Node) string {
|
| + var buffer bytes.Buffer
|
| + fmt.Fprintf(&buffer, "[")
|
| + first := true
|
| + for _, n := range nodeSlice {
|
| + if !first {
|
| + fmt.Fprintf(&buffer, ", ")
|
| + }
|
| + fmt.Fprintf(&buffer, "%s", n.DebugName())
|
| + first = false
|
| + }
|
| + fmt.Fprintln(&buffer, "]")
|
| + return buffer.String()
|
| +}
|
| +
|
| +////////////////////////////////////////////////////
|
| +// Tests
|
| +////////////////////////////////////////////////////
|
| +
|
| +type WellFoundedGraphTestCase struct {
|
| + // The connection matrix describing the digraph for this case.
|
| + connectionMatrix [][]int
|
| +
|
| + // If this is non-nil it should be the list of indices of the square nodes.
|
| + squares []int
|
| +
|
| + // The expected result when searching from Node0
|
| + expectedFirstA, expectedLastA, expectedPathA string
|
| +
|
| + // The expected result when searching from Node1
|
| + expectedFirstB, expectedLastB, expectedPathB string
|
| +
|
| + expectedDebugDataA, expectedDebugDataB expectedDebugData
|
| +}
|
| +
|
| +// TestWellFoundedIsWellFoundedCirclesOnly tests the function |checkWellFounded|
|
| +// on graphs that are well-founded and that contain only circles. For
|
| +// graphs that contain only circles being well-founded is equivalent to
|
| +// being acyclic.
|
| +func TestWellFoundedIsWellFoundedCirclesOnly(t *testing.T) {
|
| + cases := []WellFoundedGraphTestCase{
|
| +
|
| + // Case: Single node, no self-loop
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {},
|
| + },
|
| + },
|
| +
|
| + // Case: Linear chain of length 4
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0 -> 1
|
| + {2}, // 1 -> 2
|
| + {3}, // 2 -> 3
|
| + {}, // 3 -> ||
|
| + },
|
| + },
|
| +
|
| + // Case: Diamond
|
| + //
|
| + // 0
|
| + // 1 2
|
| + // 3
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3}, // 1
|
| + {3}, // 2
|
| + {}, // 3
|
| + },
|
| + },
|
| +
|
| + // Case: Complex, double diamond with a tail
|
| + //
|
| + // 0
|
| + // 1 2
|
| + // 6 3 4
|
| + // 5
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3, 6}, // 1
|
| + {3, 4}, // 2
|
| + {5}, // 3
|
| + {5}, // 4
|
| + {}, // 5
|
| + {}, // 6
|
| + },
|
| + },
|
| +
|
| + // Case: 0 and 1 are independent roots
|
| + //
|
| + // 0 1
|
| + // 7 2 |
|
| + // 6 3 4
|
| + // 5
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {7, 2}, // 0
|
| + {4}, // 1
|
| + {3, 4}, // 2
|
| + {5}, // 3
|
| + {5}, // 4
|
| + {}, // 5
|
| + {}, // 6
|
| + {3, 6}, // 7
|
| + },
|
| + },
|
| + }
|
| +
|
| + for i, c := range cases {
|
| + graph := buildCircleTestGraph(c.connectionMatrix)
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[0], &dbgData)
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dA: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + if err := compareDebugData(&c.expectedDebugDataA, &dbgData); err != nil {
|
| + t.Fatalf("Case %dA: %s", i, err.Error())
|
| + }
|
| + if len(graph) > 1 {
|
| + dbgData := debugData{}
|
| + cycleDescription = checkWellFounded(graph[1], &dbgData)
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dB: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + if err := compareDebugData(&c.expectedDebugDataB, &dbgData); err != nil {
|
| + t.Fatalf("Case %dB: %s", i, err.Error())
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +// TestWellFoundedNotWellFoundedCirclesOnly tests the function |checkWellFounded|
|
| +// on graphs that are not well-founded and that contain only circles. For
|
| +// graphs that contain only circles being well-founded is equivalent to
|
| +// being acyclic.
|
| +func TestWellFoundedNotWellFoundedCirclesOnly(t *testing.T) {
|
| +
|
| + cases := []WellFoundedGraphTestCase{
|
| +
|
| + // Case: Single node with self-loop
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {0},
|
| + },
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node0",
|
| + expectedPathA: "Node0",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop of length 4
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0 -> 1
|
| + {2}, // 1 -> 2
|
| + {3}, // 2 -> 3
|
| + {0}, // 3 -> 0
|
| + },
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node0, Node1, Node2, Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node3, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + },
|
| + },
|
| +
|
| + // Case: Diamond with a loop-back from bottom to top
|
| + //
|
| + // 0 <--
|
| + // 1 2 |
|
| + // 3 ---|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3}, // 1
|
| + {3}, // 2
|
| + {0}, // 3
|
| + },
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node0, Node1, Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node3, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + },
|
| + },
|
| +
|
| + // Case: Complex, double diamond with a tail and a bridge and
|
| + // a loop back to the top
|
| + //
|
| + // 0
|
| + // 1 2 <----
|
| + // 6 3 4 |
|
| + // 5 ------|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3, 6}, // 1
|
| + {3, 4}, // 2
|
| + {5}, // 3
|
| + {5}, // 4
|
| + {2}, // 5
|
| + {}, // 6
|
| + },
|
| + expectedFirstA: "Node3",
|
| + expectedLastA: "Node2",
|
| + expectedPathA: "Node3, Node5, Node2",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5"},
|
| + },
|
| +
|
| + expectedFirstB: "Node3",
|
| + expectedLastB: "Node2",
|
| + expectedPathB: "Node3, Node5, Node2",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node2", "Node3", "Node4", "Node5"},
|
| + },
|
| + },
|
| +
|
| + // Case: More complex
|
| + //
|
| + //
|
| + // 0
|
| + // 1 2 3 <------
|
| + // 4 5 |
|
| + // 6 8 7 |
|
| + // 11 9 -------|
|
| + // 12 10
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2, 3}, // 0
|
| + {4}, // 1
|
| + {4, 5}, // 2
|
| + {5}, // 3
|
| + {6}, // 4
|
| + {6, 7, 8}, // 5
|
| + {11}, // 6
|
| + {}, // 7
|
| + {9}, // 8
|
| + {10, 3}, // 9
|
| + {}, // 10
|
| + {12}, // 11
|
| + {}, // 12
|
| + },
|
| + expectedFirstA: "Node5",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node5, Node8, Node9, Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node2", "Node3", "Node5", "Node8", "Node9"},
|
| + },
|
| + },
|
| +
|
| + // Case: No loop from 0 but loop from 1
|
| + //
|
| + //
|
| + // 1
|
| + // 0 2 3 <----
|
| + // 4 5 |
|
| + // 6 8 7 |
|
| + // 11 9 ------|
|
| + // 12 10
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {4}, // 0
|
| + {0, 2, 3}, // 1
|
| + {4, 5}, // 2
|
| + {5}, // 3
|
| + {6}, // 4
|
| + {6, 7, 8}, // 5
|
| + {11}, // 6
|
| + {}, // 7
|
| + {9}, // 8
|
| + {10, 3}, // 9
|
| + {}, // 10
|
| + {12}, // 11
|
| + {}, // 12
|
| + },
|
| + expectedFirstB: "Node5",
|
| + expectedLastB: "Node3",
|
| + expectedPathB: "Node5, Node8, Node9, Node3",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node2", "Node3", "Node5", "Node8", "Node9"},
|
| + },
|
| + },
|
| + }
|
| +
|
| + for i, c := range cases {
|
| + graph := buildCircleTestGraph(c.connectionMatrix)
|
| + // Test from root=Node0
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[0], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataA, &dbgData); err != nil {
|
| + t.Fatalf("Case %dA: %s", i, err.Error())
|
| + }
|
| + if len(c.expectedPathA) == 0 {
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dA: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + } else {
|
| + if cycleDescription == nil {
|
| + t.Fatalf("Case %d: Expected a cycle.", i)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("first:%s", c.expectedFirstA)) {
|
| + t.Fatalf("Case %d: got=%s expectedFirst=%q", i, cycleDescription.String(), c.expectedFirstA)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("last:%s", c.expectedLastA)) {
|
| + t.Fatalf("Case %d: got=%s expectedLast=%q", i, cycleDescription.String(), c.expectedLastA)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("{%s}", c.expectedPathA)) {
|
| + t.Fatalf("Case %d: got=%s expectedPath=%q", i, cycleDescription.String(), c.expectedPathA)
|
| + }
|
| + }
|
| +
|
| + // Test from root=Node1
|
| + if len(graph) > 1 {
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[1], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataB, &dbgData); err != nil {
|
| + t.Fatalf("Case %dB: %s", i, err.Error())
|
| + }
|
| + if len(c.expectedPathB) == 0 {
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dB: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + } else {
|
| + if cycleDescription == nil {
|
| + t.Fatalf("Case %dB: Expected a cycle.", i)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("first:%s", c.expectedFirstB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedFirst=%q", i, cycleDescription.String(), c.expectedFirstB)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("last:%s", c.expectedLastB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedLast=%q", i, cycleDescription.String(), c.expectedLastB)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("{%s}", c.expectedPathB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedPath=%q", i, cycleDescription.String(), c.expectedPathB)
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +// TestWellFoundedIsWellFounded tests the function |checkWellFounded| on graphs
|
| +// that contain both circles and squares and are well-founded.
|
| +func TestWellFoundedIsWellFounded(t *testing.T) {
|
| + cases := []WellFoundedGraphTestCase{
|
| +
|
| + // Case: Single square node, no self-loop
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {},
|
| + },
|
| + squares: []int{0},
|
| + },
|
| +
|
| + // Case: Loop through right branch but not through left branch.
|
| + //
|
| + // [0] <--|
|
| + // 1 2--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {}, // 1
|
| + {0}, // 2
|
| + },
|
| + squares: []int{0},
|
| + },
|
| +
|
| + // Case: Loop through left branch but not through rigt branch.
|
| + //
|
| + // [0] <--|
|
| + // 2 1--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {0}, // 1
|
| + {}, // 2
|
| + },
|
| + squares: []int{0},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node1"},
|
| + initialFoundationSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop through right branch below the root
|
| + //
|
| + // 0 <---|
|
| + // [1] |
|
| + // 2 3--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {}, // 2
|
| + {0}, // 3
|
| + },
|
| + squares: []int{1},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node3"},
|
| + initialFoundationSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop through left branch below the root
|
| + //
|
| + // 0 <---|
|
| + // [1] |
|
| + // 3 2--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {0}, // 2
|
| + {}, // 3
|
| + },
|
| + squares: []int{1},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node2"},
|
| + initialFoundationSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case: We know 0 is well-founded before we see the loop from 4 to 0
|
| + //
|
| + //
|
| + // 1 <-- [0] <--|
|
| + // [2] |
|
| + // 3 4--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {}, // 1
|
| + {3, 4}, // 2
|
| + {}, // 3
|
| + {0}, // 4
|
| + },
|
| + squares: []int{0, 2},
|
| + },
|
| +
|
| + // Case: Well-founded becuase of 8
|
| + // 0
|
| + // 1
|
| + // 2 <--
|
| + // 3 |
|
| + // 8 <-- [4] |
|
| + // 5 |
|
| + // 6---|
|
| + // 7
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2}, // 1
|
| + {3}, // 2
|
| + {4}, // 3
|
| + {5, 8}, // 4
|
| + {6}, // 5
|
| + {2, 7}, // 6
|
| + {}, // 7
|
| + {}, // 8
|
| + },
|
| + squares: []int{4},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node5", "Node6"},
|
| + initialFoundationSet: []string{"Node2"},
|
| + },
|
| + },
|
| +
|
| + // Case: Double loops through right branches
|
| + //
|
| + // 0 <---|
|
| + // [1]<---|-----
|
| + // 2 3--| |
|
| + // [4] |
|
| + // 5 6 7 --|
|
| + // 8 9 10
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {}, // 2
|
| + {0, 4}, // 3
|
| + {5, 6, 7}, // 4
|
| + {8}, // 5
|
| + {9}, // 6
|
| + {1, 10}, // 7
|
| + {}, // 8
|
| + {}, // 9
|
| + {}, // 10
|
| + },
|
| + squares: []int{1, 4},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node3"},
|
| + initialFoundationSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case: Double loops through left branches
|
| + //
|
| + // 0 <---|
|
| + // [1]<---|-----
|
| + // 3 2--| |
|
| + // [4] |
|
| + // 7 6 5 --|
|
| + // 8 9 10
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {0, 4}, // 2
|
| + {}, // 3
|
| + {5, 6, 7}, // 4
|
| + {1, 10}, // 5
|
| + {9}, // 6
|
| + {8}, // 7
|
| + {}, // 8
|
| + {}, // 9
|
| + {}, // 10
|
| + },
|
| + squares: []int{1, 4},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node2", "Node5"},
|
| + initialFoundationSet: []string{"Node0", "Node1"},
|
| + },
|
| + },
|
| +
|
| + // Case: Crossing up and down on right
|
| + //
|
| + // 0 -----|
|
| + // 1 |
|
| + // [2] <---|-----
|
| + // 3 4 <-| |
|
| + // 5 |
|
| + // 6 |
|
| + // 7 -------|
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 4}, // 0
|
| + {2}, // 1
|
| + {3, 4}, // 2
|
| + {}, // 3
|
| + {5}, // 4
|
| + {6}, // 5
|
| + {7}, // 6
|
| + {2}, // 7
|
| + },
|
| + squares: []int{2},
|
| + },
|
| +
|
| + // Case: Crossing up and down on left
|
| + //
|
| + // 0 -----|
|
| + // 1 |
|
| + // [2] <---|-----
|
| + // 4 3 <-| |
|
| + // 5 |
|
| + // 6 |
|
| + // 7 -------|
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 3}, // 0
|
| + {2}, // 1
|
| + {3, 4}, // 2
|
| + {5}, // 3
|
| + {}, // 4
|
| + {6}, // 5
|
| + {7}, // 6
|
| + {2}, // 7
|
| + },
|
| + squares: []int{2},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node3", "Node5", "Node6", "Node7"},
|
| + initialFoundationSet: []string{"Node2"},
|
| + },
|
| + },
|
| +
|
| + // Case:
|
| + //
|
| + // [0] <------|
|
| + // |---->1 2 |
|
| + // |<---[3]-------------|
|
| + //
|
| + //
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3}, // 1
|
| + {}, // 2
|
| + {0, 1}, // 3
|
| + },
|
| + squares: []int{0, 3},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node3"},
|
| + initialFoundationSet: []string{"Node0"},
|
| + },
|
| + },
|
| +
|
| + // Case:
|
| + //
|
| + // |-----> 0 <------\
|
| + // | [1] --> [2]---|--> 4
|
| + // | ^ \
|
| + // |----|-------3
|
| + //
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {2}, // 1
|
| + {0, 3, 4}, // 2
|
| + {0, 1}, // 3
|
| + {}, // 4
|
| + },
|
| + squares: []int{1, 2},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node3"},
|
| + initialFoundationSet: []string{"Node0", "Node1"},
|
| + },
|
| + },
|
| +
|
| + // Case:
|
| + //
|
| + // |-->[1]
|
| + // [2]< -| 0
|
| + // ^ |
|
| + // |----|
|
| + //
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {}, // 0
|
| + {0, 2}, // 1
|
| + {1, 2}, // 2
|
| + },
|
| + squares: []int{1, 2},
|
| + },
|
| +
|
| + // Case: Double loops through left branches
|
| + //
|
| + // 0 <---|
|
| + // [1]<---|-----
|
| + // 3 2--| |
|
| + // [4] |
|
| + // 7 6 5 --|
|
| + // 8 9 10
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {0, 4}, // 2
|
| + {}, // 3
|
| + {5, 6, 7}, // 4
|
| + {1, 10}, // 5
|
| + {9}, // 6
|
| + {8}, // 7
|
| + {}, // 8
|
| + {}, // 9
|
| + {}, // 10
|
| + },
|
| + squares: []int{1, 4},
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node2", "Node5"},
|
| + initialFoundationSet: []string{"Node0", "Node1"},
|
| + },
|
| + },
|
| + }
|
| +
|
| + for i, c := range cases {
|
| + graph := buildTestGraph(c.connectionMatrix, c.squares)
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[0], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataA, &dbgData); err != nil {
|
| + t.Fatalf("Case %dA: %s", i, err.Error())
|
| + }
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dA: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + if len(graph) > 1 {
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[1], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataB, &dbgData); err != nil {
|
| + t.Fatalf("Case %dB: %s", i, err.Error())
|
| + }
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dB: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +// TestWellFoundedNotWellFounded tests the function |checkWellFounded| on graphs
|
| +// that contain both circles and squares and are not well-founded.
|
| +func TestWellFoundedNotWellFounded(t *testing.T) {
|
| +
|
| + cases := []WellFoundedGraphTestCase{
|
| +
|
| + // Case: Single square node with self-loop
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {0},
|
| + },
|
| +
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node0",
|
| + expectedPathA: "Node0",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop of length 4 with 1 square at the root
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0 -> 1
|
| + {2}, // 1 -> 2
|
| + {3}, // 2 -> 3
|
| + {0}, // 3 -> 0
|
| + },
|
| +
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node0, Node1, Node2, Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node3, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop of length 4 with 2 squares
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0 -> 1
|
| + {2}, // 1 -> 2
|
| + {3}, // 2 -> 3
|
| + {0}, // 3 -> 0
|
| + },
|
| +
|
| + squares: []int{1, 2},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node0, Node1, Node2, Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node3, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop through left and right branches
|
| + //
|
| + // |--> [0] <--|
|
| + // |--1 2--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {0}, // 1
|
| + {0}, // 2
|
| + },
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node1",
|
| + expectedPathA: "Node0, Node1",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Loop through left and right branches below the root
|
| + //
|
| + // |----> 0 <---|
|
| + // | [1] |
|
| + // |-- 2 3--|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2, 3}, // 1
|
| + {0}, // 2
|
| + {0}, // 3
|
| + },
|
| + squares: []int{1},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node2",
|
| + expectedPathA: "Node0, Node1, Node2",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Self loop at one node below a square
|
| + //
|
| + // [0]
|
| + // 1 2--|
|
| + // ^ |
|
| + // | |
|
| + // ----
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {}, // 1
|
| + {2}, // 2
|
| + },
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node2",
|
| + expectedLastA: "Node2",
|
| + expectedPathA: "Node2",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node2"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Self loop at one node below a square, other side
|
| + //
|
| + // [0]
|
| + // 2 1--|
|
| + // ^ |
|
| + // | |
|
| + // ----
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {1}, // 1
|
| + {}, // 2
|
| + },
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node1",
|
| + expectedLastA: "Node1",
|
| + expectedPathA: "Node1",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node1"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node1",
|
| + expectedPathB: "Node1",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Self loop off of one branch of a square.
|
| + //
|
| + // [0]
|
| + // 1 2
|
| + // 3 -|
|
| + // ^ |
|
| + // |---
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3}, // 1
|
| + {}, // 2
|
| + {3}, // 3
|
| + },
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node3",
|
| + expectedLastA: "Node3",
|
| + expectedPathA: "Node3",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node3",
|
| + expectedLastB: "Node3",
|
| + expectedPathB: "Node3",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node3"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Hard loop off of one branch of a square.
|
| + //
|
| + // [0]
|
| + // --> 1 2
|
| + // | 3
|
| + // |---4
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1, 2}, // 0
|
| + {3}, // 1
|
| + {}, // 2
|
| + {4}, // 3
|
| + {1}, // 4
|
| + },
|
| + squares: []int{0},
|
| +
|
| + expectedFirstA: "Node1",
|
| + expectedLastA: "Node4",
|
| + expectedPathA: "Node1, Node3, Node4",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node3", "Node4"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node4",
|
| + expectedPathB: "Node1, Node3, Node4",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node3", "Node4"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Graph below 0 is well-founded and 1 is well-founded but not the graph below 1.
|
| + //
|
| + // [1]
|
| + // 2 -| 0
|
| + // ^ |
|
| + // |--|
|
| + //
|
| + //
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {}, // 0
|
| + {0, 2}, // 1
|
| + {2}, // 2
|
| + },
|
| + squares: []int{1},
|
| +
|
| + expectedFirstB: "Node2",
|
| + expectedLastB: "Node2",
|
| + expectedPathB: "Node2",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node2"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Different loops through both branches sharing nodes.
|
| + //
|
| + // 0 <------|
|
| + // 1 |
|
| + // [2] |
|
| + // 3 4 |
|
| + // \ 5 |
|
| + // \----> 6 |
|
| + // 7 --|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2}, // 1
|
| + {3, 4}, // 2
|
| + {6}, // 3
|
| + {5}, // 4
|
| + {6}, // 5
|
| + {7}, // 6
|
| + {0}, // 7
|
| + },
|
| + squares: []int{2},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node7",
|
| + expectedPathA: "Node0, Node1, Node2, Node3, Node6, Node7",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6", "Node7"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node3, Node6, Node7, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6", "Node7"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Different loops through both branches sharing nodes--switch sides.
|
| + //
|
| + // 0 <------|
|
| + // 1 |
|
| + // [2] |
|
| + // 4 3 |
|
| + // \ 5 |
|
| + // \----> 6 |
|
| + // 7 --|
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2}, // 1
|
| + {3, 4}, // 2
|
| + {5}, // 3
|
| + {6}, // 4
|
| + {6}, // 5
|
| + {7}, // 6
|
| + {0}, // 7
|
| + },
|
| + squares: []int{2},
|
| +
|
| + expectedFirstA: "Node0",
|
| + expectedLastA: "Node7",
|
| + expectedPathA: "Node0, Node1, Node2, Node3, Node5, Node6, Node7",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6", "Node7"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node1",
|
| + expectedLastB: "Node0",
|
| + expectedPathB: "Node1, Node2, Node3, Node5, Node6, Node7, Node0",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6", "Node7"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Not Well-founded becuase of 8 becuase 4 is a circle
|
| + // 0
|
| + // 1
|
| + // 2 <--
|
| + // 3 |
|
| + // 8 <-- 4 |
|
| + // 5 |
|
| + // 6---|
|
| + // 7
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2}, // 1
|
| + {3}, // 2
|
| + {4}, // 3
|
| + {5, 8}, // 4
|
| + {6}, // 5
|
| + {2, 7}, // 6
|
| + {}, // 7
|
| + {}, // 8
|
| + },
|
| + squares: []int{},
|
| +
|
| + expectedFirstA: "Node2",
|
| + expectedLastA: "Node6",
|
| + expectedPathA: "Node2, Node3, Node4, Node5, Node6",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node2",
|
| + expectedLastB: "Node6",
|
| + expectedPathB: "Node2, Node3, Node4, Node5, Node6",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node2", "Node3", "Node4", "Node5", "Node6"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| +
|
| + // Case: Not Well-founded becuase of 8 becuase there is no 8
|
| + // 0
|
| + // 1
|
| + // 2 <--
|
| + // 3 |
|
| + // [4] |
|
| + // 5 |
|
| + // 6---|
|
| + // 7
|
| + {
|
| + connectionMatrix: [][]int{
|
| + {1}, // 0
|
| + {2}, // 1
|
| + {3}, // 2
|
| + {4}, // 3
|
| + {5}, // 4
|
| + {6}, // 5
|
| + {2, 7}, // 6
|
| + {}, // 7
|
| + },
|
| + squares: []int{4},
|
| +
|
| + expectedFirstA: "Node2",
|
| + expectedLastA: "Node6",
|
| + expectedPathA: "Node2, Node3, Node4, Node5, Node6",
|
| + expectedDebugDataA: expectedDebugData{
|
| + initialPendingSet: []string{"Node0", "Node1", "Node2", "Node3", "Node4", "Node5", "Node6"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| +
|
| + expectedFirstB: "Node2",
|
| + expectedLastB: "Node6",
|
| + expectedPathB: "Node2, Node3, Node4, Node5, Node6",
|
| + expectedDebugDataB: expectedDebugData{
|
| + initialPendingSet: []string{"Node1", "Node2", "Node3", "Node4", "Node5", "Node6"},
|
| + initialFoundationSet: []string{},
|
| + },
|
| + },
|
| + }
|
| +
|
| + for i, c := range cases {
|
| + graph := buildTestGraph(c.connectionMatrix, c.squares)
|
| + // Test from root=Node0
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[0], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataA, &dbgData); err != nil {
|
| + t.Fatalf("Case %dA: %s", i, err.Error())
|
| + }
|
| + if len(c.expectedPathA) == 0 {
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dA: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + } else {
|
| + if cycleDescription == nil {
|
| + t.Fatalf("Case %d: Expected a cycle.", i)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("first:%s", c.expectedFirstA)) {
|
| + t.Fatalf("Case %d: got=%s expectedFirst=%q", i, cycleDescription.String(), c.expectedFirstA)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("last:%s", c.expectedLastA)) {
|
| + t.Fatalf("Case %d: got=%s expectedLast=%q", i, cycleDescription.String(), c.expectedLastA)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("{%s}", c.expectedPathA)) {
|
| + t.Fatalf("Case %d: got=%s expectedPath=%q", i, cycleDescription.String(), c.expectedPathA)
|
| + }
|
| + }
|
| +
|
| + // Test from root=Node1
|
| + if len(graph) > 1 {
|
| + dbgData := debugData{}
|
| + cycleDescription := checkWellFounded(graph[1], &dbgData)
|
| + if err := compareDebugData(&c.expectedDebugDataB, &dbgData); err != nil {
|
| + t.Fatalf("Case %dB: %s", i, err.Error())
|
| + }
|
| + if len(c.expectedPathB) == 0 {
|
| + if cycleDescription != nil {
|
| + t.Fatalf("Case %dB: Detected a cycle: %s", i, cycleDescription)
|
| + }
|
| + } else {
|
| + if cycleDescription == nil {
|
| + t.Fatalf("Case %dB: Expected a cycle.", i)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("first:%s", c.expectedFirstB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedFirst=%q", i, cycleDescription.String(), c.expectedFirstB)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("last:%s", c.expectedLastB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedLast=%q", i, cycleDescription.String(), c.expectedLastB)
|
| + }
|
| + if !strings.Contains(cycleDescription.String(), fmt.Sprintf("{%s}", c.expectedPathB)) {
|
| + t.Fatalf("Case %dB: got=%s expectedPath=%q", i, cycleDescription.String(), c.expectedPathB)
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|