github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/dag/dag_test.go (about) 1 package dag 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "reflect" 8 "strconv" 9 "strings" 10 "sync" 11 "testing" 12 13 "github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags" 14 15 _ "github.com/iaas-resource-provision/iaas-rpc/internal/logging" 16 ) 17 18 func TestMain(m *testing.M) { 19 flag.Parse() 20 os.Exit(m.Run()) 21 } 22 23 func TestAcyclicGraphRoot(t *testing.T) { 24 var g AcyclicGraph 25 g.Add(1) 26 g.Add(2) 27 g.Add(3) 28 g.Connect(BasicEdge(3, 2)) 29 g.Connect(BasicEdge(3, 1)) 30 31 if root, err := g.Root(); err != nil { 32 t.Fatalf("err: %s", err) 33 } else if root != 3 { 34 t.Fatalf("bad: %#v", root) 35 } 36 } 37 38 func TestAcyclicGraphRoot_cycle(t *testing.T) { 39 var g AcyclicGraph 40 g.Add(1) 41 g.Add(2) 42 g.Add(3) 43 g.Connect(BasicEdge(1, 2)) 44 g.Connect(BasicEdge(2, 3)) 45 g.Connect(BasicEdge(3, 1)) 46 47 if _, err := g.Root(); err == nil { 48 t.Fatal("should error") 49 } 50 } 51 52 func TestAcyclicGraphRoot_multiple(t *testing.T) { 53 var g AcyclicGraph 54 g.Add(1) 55 g.Add(2) 56 g.Add(3) 57 g.Connect(BasicEdge(3, 2)) 58 59 if _, err := g.Root(); err == nil { 60 t.Fatal("should error") 61 } 62 } 63 64 func TestAyclicGraphTransReduction(t *testing.T) { 65 var g AcyclicGraph 66 g.Add(1) 67 g.Add(2) 68 g.Add(3) 69 g.Connect(BasicEdge(1, 2)) 70 g.Connect(BasicEdge(1, 3)) 71 g.Connect(BasicEdge(2, 3)) 72 g.TransitiveReduction() 73 74 actual := strings.TrimSpace(g.String()) 75 expected := strings.TrimSpace(testGraphTransReductionStr) 76 if actual != expected { 77 t.Fatalf("bad: %s", actual) 78 } 79 } 80 81 func TestAyclicGraphTransReduction_more(t *testing.T) { 82 var g AcyclicGraph 83 g.Add(1) 84 g.Add(2) 85 g.Add(3) 86 g.Add(4) 87 g.Connect(BasicEdge(1, 2)) 88 g.Connect(BasicEdge(1, 3)) 89 g.Connect(BasicEdge(1, 4)) 90 g.Connect(BasicEdge(2, 3)) 91 g.Connect(BasicEdge(2, 4)) 92 g.Connect(BasicEdge(3, 4)) 93 g.TransitiveReduction() 94 95 actual := strings.TrimSpace(g.String()) 96 expected := strings.TrimSpace(testGraphTransReductionMoreStr) 97 if actual != expected { 98 t.Fatalf("bad: %s", actual) 99 } 100 } 101 102 // use this to simulate slow sort operations 103 type counter struct { 104 Name string 105 Calls int64 106 } 107 108 func (s *counter) String() string { 109 s.Calls++ 110 return s.Name 111 } 112 113 // Make sure we can reduce a sizable, fully-connected graph. 114 func TestAyclicGraphTransReduction_fullyConnected(t *testing.T) { 115 var g AcyclicGraph 116 117 const nodeCount = 200 118 nodes := make([]*counter, nodeCount) 119 for i := 0; i < nodeCount; i++ { 120 nodes[i] = &counter{Name: strconv.Itoa(i)} 121 } 122 123 // Add them all to the graph 124 for _, n := range nodes { 125 g.Add(n) 126 } 127 128 // connect them all 129 for i := range nodes { 130 for j := range nodes { 131 if i == j { 132 continue 133 } 134 g.Connect(BasicEdge(nodes[i], nodes[j])) 135 } 136 } 137 138 g.TransitiveReduction() 139 140 vertexNameCalls := int64(0) 141 for _, n := range nodes { 142 vertexNameCalls += n.Calls 143 } 144 145 switch { 146 case vertexNameCalls > 2*nodeCount: 147 // Make calling it more the 2x per node fatal. 148 // If we were sorting this would give us roughly ln(n)(n^3) calls, or 149 // >59000000 calls for 200 vertices. 150 t.Fatalf("VertexName called %d times", vertexNameCalls) 151 case vertexNameCalls > 0: 152 // we don't expect any calls, but a change here isn't necessarily fatal 153 t.Logf("WARNING: VertexName called %d times", vertexNameCalls) 154 } 155 } 156 157 func TestAcyclicGraphValidate(t *testing.T) { 158 var g AcyclicGraph 159 g.Add(1) 160 g.Add(2) 161 g.Add(3) 162 g.Connect(BasicEdge(3, 2)) 163 g.Connect(BasicEdge(3, 1)) 164 165 if err := g.Validate(); err != nil { 166 t.Fatalf("err: %s", err) 167 } 168 } 169 170 func TestAcyclicGraphValidate_cycle(t *testing.T) { 171 var g AcyclicGraph 172 g.Add(1) 173 g.Add(2) 174 g.Add(3) 175 g.Connect(BasicEdge(3, 2)) 176 g.Connect(BasicEdge(3, 1)) 177 g.Connect(BasicEdge(1, 2)) 178 g.Connect(BasicEdge(2, 1)) 179 180 if err := g.Validate(); err == nil { 181 t.Fatal("should error") 182 } 183 } 184 185 func TestAcyclicGraphValidate_cycleSelf(t *testing.T) { 186 var g AcyclicGraph 187 g.Add(1) 188 g.Add(2) 189 g.Connect(BasicEdge(1, 1)) 190 191 if err := g.Validate(); err == nil { 192 t.Fatal("should error") 193 } 194 } 195 196 func TestAcyclicGraphAncestors(t *testing.T) { 197 var g AcyclicGraph 198 g.Add(1) 199 g.Add(2) 200 g.Add(3) 201 g.Add(4) 202 g.Add(5) 203 g.Connect(BasicEdge(0, 1)) 204 g.Connect(BasicEdge(1, 2)) 205 g.Connect(BasicEdge(2, 3)) 206 g.Connect(BasicEdge(3, 4)) 207 g.Connect(BasicEdge(4, 5)) 208 209 actual, err := g.Ancestors(2) 210 if err != nil { 211 t.Fatalf("err: %#v", err) 212 } 213 214 expected := []Vertex{3, 4, 5} 215 216 if actual.Len() != len(expected) { 217 t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 218 } 219 220 for _, e := range expected { 221 if !actual.Include(e) { 222 t.Fatalf("expected: %#v to include: %#v", expected, actual) 223 } 224 } 225 } 226 227 func TestAcyclicGraphDescendents(t *testing.T) { 228 var g AcyclicGraph 229 g.Add(1) 230 g.Add(2) 231 g.Add(3) 232 g.Add(4) 233 g.Add(5) 234 g.Connect(BasicEdge(0, 1)) 235 g.Connect(BasicEdge(1, 2)) 236 g.Connect(BasicEdge(2, 3)) 237 g.Connect(BasicEdge(3, 4)) 238 g.Connect(BasicEdge(4, 5)) 239 240 actual, err := g.Descendents(2) 241 if err != nil { 242 t.Fatalf("err: %#v", err) 243 } 244 245 expected := []Vertex{0, 1} 246 247 if actual.Len() != len(expected) { 248 t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) 249 } 250 251 for _, e := range expected { 252 if !actual.Include(e) { 253 t.Fatalf("expected: %#v to include: %#v", expected, actual) 254 } 255 } 256 } 257 258 func TestAcyclicGraphWalk(t *testing.T) { 259 var g AcyclicGraph 260 g.Add(1) 261 g.Add(2) 262 g.Add(3) 263 g.Connect(BasicEdge(3, 2)) 264 g.Connect(BasicEdge(3, 1)) 265 266 var visits []Vertex 267 var lock sync.Mutex 268 err := g.Walk(func(v Vertex) tfdiags.Diagnostics { 269 lock.Lock() 270 defer lock.Unlock() 271 visits = append(visits, v) 272 return nil 273 }) 274 if err != nil { 275 t.Fatalf("err: %s", err) 276 } 277 278 expected := [][]Vertex{ 279 {1, 2, 3}, 280 {2, 1, 3}, 281 } 282 for _, e := range expected { 283 if reflect.DeepEqual(visits, e) { 284 return 285 } 286 } 287 288 t.Fatalf("bad: %#v", visits) 289 } 290 291 func TestAcyclicGraphWalk_error(t *testing.T) { 292 var g AcyclicGraph 293 g.Add(1) 294 g.Add(2) 295 g.Add(3) 296 g.Add(4) 297 g.Connect(BasicEdge(4, 3)) 298 g.Connect(BasicEdge(3, 2)) 299 g.Connect(BasicEdge(2, 1)) 300 301 var visits []Vertex 302 var lock sync.Mutex 303 err := g.Walk(func(v Vertex) tfdiags.Diagnostics { 304 lock.Lock() 305 defer lock.Unlock() 306 307 var diags tfdiags.Diagnostics 308 309 if v == 2 { 310 diags = diags.Append(fmt.Errorf("error")) 311 return diags 312 } 313 314 visits = append(visits, v) 315 return diags 316 }) 317 if err == nil { 318 t.Fatal("should error") 319 } 320 321 expected := []Vertex{1} 322 if !reflect.DeepEqual(visits, expected) { 323 t.Errorf("wrong visits\ngot: %#v\nwant: %#v", visits, expected) 324 } 325 326 } 327 328 func BenchmarkDAG(b *testing.B) { 329 for i := 0; i < b.N; i++ { 330 count := 150 331 b.StopTimer() 332 g := &AcyclicGraph{} 333 334 // create 4 layers of fully connected nodes 335 // layer A 336 for i := 0; i < count; i++ { 337 g.Add(fmt.Sprintf("A%d", i)) 338 } 339 340 // layer B 341 for i := 0; i < count; i++ { 342 B := fmt.Sprintf("B%d", i) 343 g.Add(B) 344 for j := 0; j < count; j++ { 345 g.Connect(BasicEdge(B, fmt.Sprintf("A%d", j))) 346 } 347 } 348 349 // layer C 350 for i := 0; i < count; i++ { 351 c := fmt.Sprintf("C%d", i) 352 g.Add(c) 353 for j := 0; j < count; j++ { 354 // connect them to previous layers so we have something that requires reduction 355 g.Connect(BasicEdge(c, fmt.Sprintf("A%d", j))) 356 g.Connect(BasicEdge(c, fmt.Sprintf("B%d", j))) 357 } 358 } 359 360 // layer D 361 for i := 0; i < count; i++ { 362 d := fmt.Sprintf("D%d", i) 363 g.Add(d) 364 for j := 0; j < count; j++ { 365 g.Connect(BasicEdge(d, fmt.Sprintf("A%d", j))) 366 g.Connect(BasicEdge(d, fmt.Sprintf("B%d", j))) 367 g.Connect(BasicEdge(d, fmt.Sprintf("C%d", j))) 368 } 369 } 370 371 b.StartTimer() 372 // Find dependencies for every node 373 for _, v := range g.Vertices() { 374 _, err := g.Ancestors(v) 375 if err != nil { 376 b.Fatal(err) 377 } 378 } 379 380 // reduce the final graph 381 g.TransitiveReduction() 382 } 383 } 384 385 func TestAcyclicGraph_ReverseDepthFirstWalk_WithRemoval(t *testing.T) { 386 var g AcyclicGraph 387 g.Add(1) 388 g.Add(2) 389 g.Add(3) 390 g.Connect(BasicEdge(3, 2)) 391 g.Connect(BasicEdge(2, 1)) 392 393 var visits []Vertex 394 var lock sync.Mutex 395 err := g.SortedReverseDepthFirstWalk([]Vertex{1}, func(v Vertex, d int) error { 396 lock.Lock() 397 defer lock.Unlock() 398 visits = append(visits, v) 399 g.Remove(v) 400 return nil 401 }) 402 if err != nil { 403 t.Fatalf("err: %s", err) 404 } 405 406 expected := []Vertex{1, 2, 3} 407 if !reflect.DeepEqual(visits, expected) { 408 t.Fatalf("expected: %#v, got: %#v", expected, visits) 409 } 410 } 411 412 const testGraphTransReductionStr = ` 413 1 414 2 415 2 416 3 417 3 418 ` 419 420 const testGraphTransReductionMoreStr = ` 421 1 422 2 423 2 424 3 425 3 426 4 427 4 428 `