github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/graph/dag_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package graph 21 22 import ( 23 "strings" 24 "testing" 25 ) 26 27 func TestAddVertex(t *testing.T) { 28 dag := NewDAG() 29 added := dag.AddVertex(nil) 30 if added { 31 t.Error("should return false if add nil vertex") 32 } 33 v := 6 34 added = dag.AddVertex(v) 35 if !added { 36 t.Error("should return true if add none nil vertex") 37 } 38 if len(dag.Vertices()) != 1 { 39 t.Error("vertex not added") 40 } 41 if dag.Vertices()[0] != v { 42 t.Error("vertex not added") 43 } 44 } 45 46 func TestRemoveVertex(t *testing.T) { 47 dag := NewDAG() 48 removed := dag.RemoveVertex(nil) 49 if !removed { 50 t.Error("should return true if removing nil vertex") 51 } 52 for i := 0; i < 4; i++ { 53 dag.AddVertex(i) 54 } 55 dag.Connect(0, 1) 56 dag.Connect(1, 2) 57 dag.Connect(1, 3) 58 if len(dag.vertices) != 4 { 59 t.Error("unexpected vertices", len(dag.vertices)) 60 } 61 if len(dag.edges) != 3 { 62 t.Error("unexpected edges", len(dag.edges)) 63 } 64 for i := 3; i >= 0; i-- { 65 dag.RemoveVertex(i) 66 } 67 if len(dag.vertices) != 0 { 68 t.Error("unexpected vertices", len(dag.vertices)) 69 } 70 if len(dag.edges) != 0 { 71 t.Error("unexpected edges", len(dag.edges)) 72 } 73 } 74 75 func TestAddNRemoveEdge(t *testing.T) { 76 dag := NewDAG() 77 added := dag.AddEdge(RealEdge(nil, nil)) 78 if added { 79 t.Error("should return false if nil edge added") 80 } 81 v1, v2 := 3, 5 82 e1 := RealEdge(v1, v2) 83 e2 := RealEdge(v1, v2) 84 added = dag.AddEdge(e1) 85 if !added { 86 t.Errorf("edge %v should be added", e1) 87 } 88 added = dag.AddEdge(e2) 89 if !added { 90 t.Errorf("edge %v should be added", e2) 91 } 92 if len(dag.edges) != 1 { 93 t.Error("edge add failed") 94 } 95 if dag.edges[e1] != e1 { 96 t.Error("edge add failed") 97 } 98 99 removed := dag.RemoveEdge(e2) 100 if !removed { 101 t.Errorf("remove edge %v failed", e2) 102 } 103 if len(dag.edges) != 0 { 104 t.Errorf("remove edge %v failed", e2) 105 } 106 } 107 108 func TestXConnect(t *testing.T) { 109 dag := NewDAG() 110 v1, v2 := 3, 5 111 connected := dag.Connect(nil, v2) 112 if connected { 113 t.Error("connect nil vertex should return false") 114 } 115 connected = dag.Connect(v1, v2) 116 if !connected { 117 t.Errorf("connect %v to %v failed", v1, v2) 118 } 119 connected = dag.Connect(v1, v2) 120 if !connected { 121 t.Errorf("connect %v to %v failed", v1, v2) 122 } 123 if len(dag.edges) != 1 { 124 t.Error("connect failed") 125 } 126 for edge := range dag.edges { 127 if edge.From() != v1 || edge.To() != v2 { 128 t.Errorf("edge in dag: %v, edge need: %v", edge, RealEdge(v1, v2)) 129 } 130 } 131 132 v3 := 7 133 connected = dag.AddConnect(v1, nil) 134 if connected { 135 t.Error("AddConnect nil vertex should return false") 136 } 137 connected = dag.AddConnect(v1, v3) 138 if !connected { 139 t.Errorf("AddConnect %v to %v should succeed", v1, v3) 140 } 141 v4 := 9 142 connected = dag.AddConnectRoot(v4) 143 if connected { 144 t.Errorf("AddConnectRoot to %v with nil root should failed", v4) 145 } 146 dag.AddVertex(v1) 147 connected = dag.AddConnectRoot(v4) 148 if !connected { 149 t.Errorf("AddConnectRoot to %v should succeed", v4) 150 } 151 } 152 153 func TestWalkTopoOrder(t *testing.T) { 154 dag := newTestDAG() 155 156 expected := []int{4, 5, 1, 10, 12, 11, 9, 6, 0, 3, 2, 7, 8} 157 walkOrder := make([]int, 0, len(expected)) 158 159 walkFunc := func(v Vertex) error { 160 walkOrder = append(walkOrder, v.(int)) 161 return nil 162 } 163 if err := dag.WalkReverseTopoOrder(walkFunc, nil); err != nil { 164 t.Error(err) 165 } 166 for i := range expected { 167 if walkOrder[i] != expected[i] { 168 t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder) 169 } 170 } 171 172 expected = []int{8, 7, 2, 3, 0, 6, 9, 11, 12, 10, 1, 5, 4} 173 walkOrder = make([]int, 0, len(expected)) 174 if err := dag.WalkTopoOrder(walkFunc, nil); err != nil { 175 t.Error(err) 176 } 177 for i := range expected { 178 if walkOrder[i] != expected[i] { 179 t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder) 180 } 181 } 182 } 183 184 func TestWalkBFS(t *testing.T) { 185 dag := newTestDAG() 186 187 expected := []int{8, 7, 2, 6, 0, 3, 4, 9, 1, 5, 10, 11, 12} 188 walkOrder := make([]int, 0, len(expected)) 189 190 walkFunc := func(v Vertex) error { 191 walkOrder = append(walkOrder, v.(int)) 192 return nil 193 } 194 if err := dag.bfs(walkFunc, less); err != nil { 195 t.Error(err) 196 } 197 for i := range expected { 198 if walkOrder[i] != expected[i] { 199 t.Errorf("unexpected order, index %d\n expected: %v\nactual: %v\n", i, expected, walkOrder) 200 } 201 } 202 } 203 204 func TestValidate(t *testing.T) { 205 dag := NewDAG() 206 err := dag.validate() 207 if err == nil { 208 t.Error("nil root not found") 209 } 210 if !strings.Contains(err.Error(), "no single Root found") { 211 t.Error("nil root not found") 212 } 213 for i := 0; i < 4; i++ { 214 dag.AddVertex(i) 215 } 216 dag.Connect(0, 1) 217 dag.Connect(1, 2) 218 dag.Connect(2, 3) 219 dag.Connect(3, 1) 220 err = dag.validate() 221 if err == nil { 222 t.Error("cycle not found") 223 } 224 if err.Error() != "cycle found" { 225 t.Error("error not as expected") 226 } 227 dag.Connect(1, 1) 228 err = dag.validate() 229 if err == nil { 230 t.Error("self-cycle not found") 231 } 232 if err.Error() != "self-cycle found: 1" { 233 t.Error("error not as expected") 234 } 235 } 236 237 func TestEquals(t *testing.T) { 238 d1 := newTestDAG() 239 equal := d1.Equals(nil, less) 240 if equal { 241 t.Error("should return false if nil other") 242 } 243 d2 := NewDAG() 244 equal = d1.Equals(d2, nil) 245 if equal { 246 t.Error("should return false if nil less func") 247 } 248 for i := 0; i < 13; i++ { 249 d2.AddVertex(12 - i) 250 } 251 252 // add edges in reverse order 253 d2.Connect(1, 5) 254 d2.Connect(10, 1) 255 d2.Connect(12, 10) 256 d2.Connect(3, 0) 257 d2.Connect(7, 2) 258 d2.Connect(7, 6) 259 d2.Connect(6, 9) 260 d2.Connect(6, 4) 261 d2.Connect(0, 5) 262 d2.Connect(5, 4) 263 d2.Connect(8, 7) 264 d2.Connect(3, 5) 265 d2.Connect(9, 11) 266 d2.Connect(9, 10) 267 d2.Connect(9, 12) 268 d2.Connect(11, 12) 269 d2.Connect(2, 0) 270 d2.Connect(0, 1) 271 d2.Connect(0, 6) 272 d2.Connect(2, 3) 273 274 if !d1.Equals(d2, less) { 275 t.Error("equals test failed") 276 } 277 278 d1 = NewDAG() 279 d2 = NewDAG() 280 281 d1.AddVertex(0) 282 d1.AddVertex(1) 283 d1.AddVertex(2) 284 d1.AddVertex(3) 285 d1.AddVertex(4) 286 d2.AddVertex(0) 287 d2.AddVertex(2) 288 d2.AddVertex(3) 289 d2.AddVertex(1) 290 d2.AddVertex(4) 291 292 d1.Connect(0, 1) 293 d1.Connect(0, 2) 294 d1.Connect(0, 3) 295 d1.Connect(0, 4) 296 d2.Connect(0, 2) 297 d2.Connect(0, 3) 298 d2.Connect(0, 4) 299 d2.Connect(0, 1) 300 301 if !d1.Equals(d2, less) { 302 t.Error("equals test failed") 303 } 304 } 305 306 func TestMerge(t *testing.T) { 307 dag1 := NewDAG() 308 dag2 := NewDAG() 309 v1, v2, v3, v4, v5, v6 := 1, 2, 3, 4, 5, 6 310 dag1.AddVertex(v1) 311 dag1.AddVertex(v2) 312 dag1.Connect(v1, v2) 313 dag2.AddVertex(v3) 314 dag2.AddVertex(v4) 315 dag2.AddVertex(v5) 316 dag2.AddVertex(v6) 317 dag2.Connect(v2, v3) 318 dag2.Connect(v2, v4) 319 dag2.Connect(v3, v5) 320 dag2.Connect(v4, v5) 321 dag2.Connect(v6, v4) 322 dag2.Connect(v6, v5) 323 324 dagExpected := NewDAG() 325 dagExpected.AddVertex(v1) 326 dagExpected.AddVertex(v2) 327 dagExpected.AddVertex(v3) 328 dagExpected.AddVertex(v4) 329 dagExpected.AddVertex(v5) 330 dagExpected.AddVertex(v6) 331 dagExpected.Connect(v1, v2) 332 dagExpected.Connect(v1, v6) 333 dagExpected.Connect(v2, v3) 334 dagExpected.Connect(v2, v4) 335 dagExpected.Connect(v3, v5) 336 dagExpected.Connect(v4, v5) 337 dagExpected.Connect(v6, v4) 338 dagExpected.Connect(v6, v5) 339 340 dag1.Merge(dag2) 341 if !dag1.Equals(dagExpected, less) { 342 t.Errorf("dag merge error, expected: %v, actual: %v", dagExpected, dag1) 343 } 344 } 345 346 func TestString(t *testing.T) { 347 dag := newTestDAG() 348 str := dag.String() 349 expectedOrder := []string{"|", "4", "5", "1", "10", "12", "11", "9", "6", "0", "3", "2", "7", "8"} 350 expectedStr := strings.Join(expectedOrder, "->") 351 if str != expectedStr { 352 t.Errorf("dag string error, expected: %s, actual: %s", expectedStr, str) 353 } 354 } 355 356 func less(v1, v2 Vertex) bool { 357 val1, _ := v1.(int) 358 val2, _ := v2.(int) 359 return val1 < val2 360 } 361 362 func newTestDAG() *DAG { 363 dag := NewDAG() 364 for i := 0; i < 13; i++ { 365 dag.AddVertex(i) 366 } 367 dag.Connect(2, 3) 368 dag.Connect(0, 6) 369 dag.Connect(0, 1) 370 dag.Connect(2, 0) 371 dag.Connect(11, 12) 372 dag.Connect(9, 12) 373 dag.Connect(9, 10) 374 dag.Connect(9, 11) 375 dag.Connect(3, 5) 376 dag.Connect(8, 7) 377 dag.Connect(5, 4) 378 dag.Connect(0, 5) 379 dag.Connect(6, 4) 380 dag.Connect(6, 9) 381 dag.Connect(7, 6) 382 dag.Connect(7, 2) 383 dag.Connect(3, 0) 384 dag.Connect(12, 10) 385 dag.Connect(10, 1) 386 dag.Connect(1, 5) 387 return dag 388 }