cuelang.org/go@v0.13.0/internal/core/toposort/scc_test.go (about) 1 // Copyright 2024 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package toposort_test 16 17 import ( 18 "slices" 19 "testing" 20 21 "cuelang.org/go/internal/core/adt" 22 "cuelang.org/go/internal/core/runtime" 23 "cuelang.org/go/internal/core/toposort" 24 ) 25 26 func TestStronglyConnectedComponents(t *testing.T) { 27 type TestCase struct { 28 name string 29 inputs [][]string 30 expected [][]string 31 } 32 33 a, b, c, d, e, f, g := "a", "b", "c", "d", "e", "f", "g" 34 35 testCases := []TestCase{ 36 { 37 name: "one", 38 inputs: [][]string{{a}}, 39 expected: [][]string{{a}}, 40 }, 41 { 42 name: "independent", 43 inputs: [][]string{{a}, {b}, {c}}, 44 expected: [][]string{{a}, {b}, {c}}, 45 }, 46 { 47 name: "simple chain two", 48 inputs: [][]string{{c, b}, {d, a}}, 49 expected: [][]string{{a}, {b}, {c}, {d}}, 50 }, 51 { 52 name: "simple chain three", 53 inputs: [][]string{{c, b}, {d, a}, {f, e}}, 54 expected: [][]string{{a}, {b}, {c}, {d}, {e}, {f}}, 55 }, 56 { 57 name: "smallest cycle", 58 inputs: [][]string{{g, f}, {f, g}}, 59 expected: [][]string{{f, g}}, 60 }, 61 { 62 name: "smallest cycle with prefix", 63 inputs: [][]string{{a, b, g, f}, {f, g}}, 64 expected: [][]string{{a}, {b}, {f, g}}, 65 }, 66 { 67 name: "smallest cycle with suffix", 68 inputs: [][]string{{g, f, a, b}, {f, g}}, 69 expected: [][]string{{a}, {b}, {f, g}}, 70 }, 71 { 72 name: "smallest cycle with prefices", 73 inputs: [][]string{{a, b, g, f}, {c, d, f, g}}, 74 expected: [][]string{{a}, {b}, {c}, {d}, {f, g}}, 75 }, 76 { 77 name: "smallest cycle with suffices", 78 inputs: [][]string{{g, f, a, b}, {f, g, c, d}}, 79 expected: [][]string{{a}, {b}, {c}, {d}, {f, g}}, 80 }, 81 { 82 name: "smallest cycle with prefices and sufficies", 83 inputs: [][]string{{a, g, f}, {b, f}, {g, c}, {f, g, d}}, 84 expected: [][]string{{a}, {b}, {c}, {d}, {f, g}}, 85 }, 86 { 87 name: "nested cycles", 88 inputs: [][]string{{b, c}, {e, c, b, d}, {d, f, a, e}, {a, f}}, 89 expected: [][]string{{a, b, c, d, e, f}}, 90 }, 91 { 92 name: "cycles through common node", 93 inputs: [][]string{{a, b, c}, {c, a}, {f, b, g}, {g, f}}, 94 expected: [][]string{{a, b, c, f, g}}, 95 }, 96 { 97 name: "split", 98 inputs: [][]string{{a, b, c}, {a, d, e}, {c, b}, {e, d}}, 99 expected: [][]string{{a}, {b, c}, {d, e}}, 100 }, 101 } 102 103 index := runtime.New() 104 105 for _, tc := range testCases { 106 t.Run(tc.name, func(t *testing.T) { 107 testAllPermutations(t, index, tc.inputs, 108 func(t *testing.T, perm [][]adt.Feature, graph *toposort.Graph) { 109 components := graph.StronglyConnectedComponents() 110 111 componentsNames := make([][]string, len(components)) 112 for i, component := range components { 113 fs := component.Nodes.Features() 114 names := make([]string, len(fs)) 115 for j, f := range fs { 116 names[j] = f.StringValue(index) 117 } 118 slices.Sort(names) 119 componentsNames[i] = names 120 } 121 slices.SortFunc(componentsNames, slices.Compare) 122 123 if !slices.EqualFunc(componentsNames, tc.expected, slices.Equal) { 124 t.Fatalf(` 125 For permutation: %v 126 Expected: %v 127 Got: %v`, 128 permutationNames(index, perm), 129 tc.expected, componentsNames) 130 } 131 132 seen := make(map[*toposort.StronglyConnectedComponent]struct{}) 133 // by definition, the graph of components (the 134 // "condensation graph") must be a DAG. I.e. no cycles the 135 // components are already sorted in a topological ordering 136 for _, component := range components { 137 if _, found := seen[component]; found { 138 t.Fatalf(` 139 For permutation: %v 140 List of components contains the same component twice: %v`, 141 permutationNames(index, perm), component) 142 } 143 seen[component] = struct{}{} 144 for _, next := range component.Outgoing { 145 if _, found := seen[next]; found { 146 t.Fatalf(` 147 For permutation: %v 148 Either the list of components is not topologically sorted, or the condensation graph is not a DAG! 149 Component: %v 150 Outgoing: %v`, 151 permutationNames(index, perm), 152 component.Nodes, next.Nodes) 153 } 154 } 155 } 156 }) 157 }) 158 } 159 }