Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 1 | package toposort |
| 2 | |
| 3 | import ( |
| 4 | "reflect" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | func toStringSlice(input []interface{}) (output []string) { |
| 9 | output = make([]string, len(input)) |
| 10 | for ix, ival := range input { |
| 11 | output[ix] = ival.(string) |
| 12 | } |
| 13 | return |
| 14 | } |
| 15 | |
| 16 | func toStringCycles(input [][]interface{}) (output [][]string) { |
| 17 | output = make([][]string, len(input)) |
| 18 | for ix, islice := range input { |
| 19 | output[ix] = toStringSlice(islice) |
| 20 | } |
| 21 | return |
| 22 | } |
| 23 | |
| 24 | type orderChecker struct { |
| 25 | t *testing.T |
| 26 | original []string |
| 27 | orderMap map[string]int |
| 28 | } |
| 29 | |
| 30 | func makeOrderChecker(t *testing.T, slice []interface{}) orderChecker { |
| 31 | result := orderChecker{t, toStringSlice(slice), make(map[string]int)} |
| 32 | for ix, val := range result.original { |
| 33 | result.orderMap[val] = ix |
| 34 | } |
| 35 | return result |
| 36 | } |
| 37 | |
| 38 | func (oc *orderChecker) findValue(val string) int { |
| 39 | if index, ok := oc.orderMap[val]; ok { |
| 40 | return index |
| 41 | } |
| 42 | oc.t.Errorf("Couldn't find val %v in slice %v", val, oc.original) |
| 43 | return -1 |
| 44 | } |
| 45 | |
| 46 | func (oc *orderChecker) expectOrder(before, after string) { |
| 47 | if oc.findValue(before) >= oc.findValue(after) { |
| 48 | oc.t.Errorf("Expected %v before %v, slice %v", before, after, oc.original) |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | // Since sort is deterministic we can expect a particular total order, in |
| 53 | // addition to the partial order checks. |
| 54 | func (oc *orderChecker) expectTotalOrder(expect ...string) { |
| 55 | if !reflect.DeepEqual(oc.original, expect) { |
| 56 | oc.t.Errorf("Expected order %v, actual %v", expect, oc.original) |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | func expectCycles(t *testing.T, actual [][]interface{}, expect [][]string) { |
| 61 | actualStr := toStringCycles(actual) |
| 62 | if !reflect.DeepEqual(actualStr, expect) { |
| 63 | t.Errorf("Expected cycles %v, actual %v", expect, actualStr) |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | func TestSortDag(t *testing.T) { |
| 68 | // This is the graph: |
| 69 | // ,-->B |
| 70 | // | |
| 71 | // A-->C---->D |
| 72 | // | \ |
| 73 | // | `-->E--. |
| 74 | // `-------------`-->F |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 75 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 76 | sorter.AddEdge("A", "B") |
| 77 | sorter.AddEdge("A", "C") |
| 78 | sorter.AddEdge("A", "F") |
| 79 | sorter.AddEdge("C", "D") |
| 80 | sorter.AddEdge("C", "E") |
| 81 | sorter.AddEdge("E", "F") |
| 82 | sorted, cycles := sorter.Sort() |
| 83 | oc := makeOrderChecker(t, sorted) |
| 84 | oc.expectOrder("B", "A") |
| 85 | oc.expectOrder("C", "A") |
| 86 | oc.expectOrder("D", "A") |
| 87 | oc.expectOrder("E", "A") |
| 88 | oc.expectOrder("F", "A") |
| 89 | oc.expectOrder("D", "C") |
| 90 | oc.expectOrder("E", "C") |
| 91 | oc.expectOrder("F", "C") |
| 92 | oc.expectOrder("F", "E") |
| 93 | oc.expectTotalOrder("B", "D", "F", "E", "C", "A") |
| 94 | expectCycles(t, cycles, [][]string{}) |
| 95 | } |
| 96 | |
| 97 | func TestSortSelfCycle(t *testing.T) { |
| 98 | // This is the graph: |
| 99 | // ,---. |
| 100 | // | | |
| 101 | // A<--' |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 102 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 103 | sorter.AddEdge("A", "A") |
| 104 | sorted, cycles := sorter.Sort() |
| 105 | oc := makeOrderChecker(t, sorted) |
| 106 | oc.expectTotalOrder("A") |
| 107 | expectCycles(t, cycles, [][]string{{"A", "A"}}) |
| 108 | } |
| 109 | |
| 110 | func TestSortCycle(t *testing.T) { |
| 111 | // This is the graph: |
| 112 | // ,-->B-->C |
| 113 | // | | |
| 114 | // A<------' |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 115 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 116 | sorter.AddEdge("A", "B") |
| 117 | sorter.AddEdge("B", "C") |
| 118 | sorter.AddEdge("C", "A") |
| 119 | sorted, cycles := sorter.Sort() |
| 120 | oc := makeOrderChecker(t, sorted) |
| 121 | oc.expectTotalOrder("C", "B", "A") |
| 122 | expectCycles(t, cycles, [][]string{{"A", "C", "B", "A"}}) |
| 123 | } |
| 124 | |
| 125 | func TestSortContainsCycle1(t *testing.T) { |
| 126 | // This is the graph: |
| 127 | // ,-->B |
| 128 | // | ,-----. |
| 129 | // | v | |
| 130 | // A-->C---->D |
| 131 | // | \ |
| 132 | // | `-->E--. |
| 133 | // `-------------`-->F |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 134 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 135 | sorter.AddEdge("A", "B") |
| 136 | sorter.AddEdge("A", "C") |
| 137 | sorter.AddEdge("A", "F") |
| 138 | sorter.AddEdge("C", "D") |
| 139 | sorter.AddEdge("C", "E") |
| 140 | sorter.AddEdge("D", "C") // creates the cycle |
| 141 | sorter.AddEdge("E", "F") |
| 142 | sorted, cycles := sorter.Sort() |
| 143 | oc := makeOrderChecker(t, sorted) |
| 144 | oc.expectOrder("B", "A") |
| 145 | oc.expectOrder("C", "A") |
| 146 | oc.expectOrder("D", "A") |
| 147 | oc.expectOrder("E", "A") |
| 148 | oc.expectOrder("F", "A") |
| 149 | // The difference with the dag is C, D may be in either order. |
| 150 | oc.expectOrder("E", "C") |
| 151 | oc.expectOrder("F", "C") |
| 152 | oc.expectOrder("F", "E") |
| 153 | oc.expectTotalOrder("B", "D", "F", "E", "C", "A") |
| 154 | expectCycles(t, cycles, [][]string{{"C", "D", "C"}}) |
| 155 | } |
| 156 | |
| 157 | func TestSortContainsCycle2(t *testing.T) { |
| 158 | // This is the graph: |
| 159 | // ,-->B |
| 160 | // | ,-------------. |
| 161 | // | v | |
| 162 | // A-->C---->D | |
| 163 | // | \ | |
| 164 | // | `-->E--. | |
| 165 | // `-------------`-->F |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 166 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 167 | sorter.AddEdge("A", "B") |
| 168 | sorter.AddEdge("A", "C") |
| 169 | sorter.AddEdge("A", "F") |
| 170 | sorter.AddEdge("C", "D") |
| 171 | sorter.AddEdge("C", "E") |
| 172 | sorter.AddEdge("E", "F") |
| 173 | sorter.AddEdge("F", "C") // creates the cycle |
| 174 | sorted, cycles := sorter.Sort() |
| 175 | oc := makeOrderChecker(t, sorted) |
| 176 | oc.expectOrder("B", "A") |
| 177 | oc.expectOrder("C", "A") |
| 178 | oc.expectOrder("D", "A") |
| 179 | oc.expectOrder("E", "A") |
| 180 | oc.expectOrder("F", "A") |
| 181 | oc.expectOrder("D", "C") |
| 182 | // The difference with the dag is C, E, F may be in any order. |
| 183 | oc.expectTotalOrder("B", "D", "F", "E", "C", "A") |
| 184 | expectCycles(t, cycles, [][]string{{"C", "F", "E", "C"}}) |
| 185 | } |
| 186 | |
| 187 | func TestSortMultiCycles(t *testing.T) { |
| 188 | // This is the graph: |
| 189 | // ,-->B |
| 190 | // | ,------------. |
| 191 | // | v | |
| 192 | // .--A-->C---->D | |
| 193 | // | ^ \ | |
| 194 | // | | `-->E--. | |
| 195 | // | | | | | |
| 196 | // | `---------' | | |
| 197 | // `---------------`-->F |
Todd Wang | c2cba88 | 2014-10-14 10:59:56 -0700 | [diff] [blame] | 198 | var sorter Sorter |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 199 | sorter.AddEdge("A", "B") |
| 200 | sorter.AddEdge("A", "C") |
| 201 | sorter.AddEdge("A", "F") |
| 202 | sorter.AddEdge("C", "D") |
| 203 | sorter.AddEdge("C", "E") |
| 204 | sorter.AddEdge("E", "A") // creates a cycle |
| 205 | sorter.AddEdge("E", "F") |
| 206 | sorter.AddEdge("F", "C") // creates a cycle |
| 207 | sorted, cycles := sorter.Sort() |
| 208 | oc := makeOrderChecker(t, sorted) |
| 209 | oc.expectOrder("B", "A") |
| 210 | oc.expectOrder("D", "A") |
| 211 | oc.expectOrder("F", "A") |
| 212 | oc.expectOrder("D", "C") |
| 213 | oc.expectTotalOrder("B", "D", "F", "E", "C", "A") |
| 214 | expectCycles(t, cycles, [][]string{{"A", "E", "C", "A"}, {"C", "F", "E", "C"}}) |
| 215 | } |