github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/depgraph/graph_test.go (about) 1 package depgraph 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strings" 8 "sync" 9 "testing" 10 ) 11 12 // ParseNouns is used to parse a string in the format of: 13 // a -> b ; edge name 14 // b -> c 15 // Into a series of nouns and dependencies 16 func ParseNouns(s string) map[string]*Noun { 17 lines := strings.Split(s, "\n") 18 nodes := make(map[string]*Noun) 19 for _, line := range lines { 20 var edgeName string 21 if idx := strings.Index(line, ";"); idx >= 0 { 22 edgeName = strings.Trim(line[idx+1:], " \t\r\n") 23 line = line[:idx] 24 } 25 parts := strings.SplitN(line, "->", 2) 26 if len(parts) != 2 { 27 continue 28 } 29 head_name := strings.Trim(parts[0], " \t\r\n") 30 tail_name := strings.Trim(parts[1], " \t\r\n") 31 head := nodes[head_name] 32 if head == nil { 33 head = &Noun{Name: head_name} 34 nodes[head_name] = head 35 } 36 tail := nodes[tail_name] 37 if tail == nil { 38 tail = &Noun{Name: tail_name} 39 nodes[tail_name] = tail 40 } 41 edge := &Dependency{ 42 Name: edgeName, 43 Source: head, 44 Target: tail, 45 } 46 head.Deps = append(head.Deps, edge) 47 } 48 return nodes 49 } 50 51 func NounMapToList(m map[string]*Noun) []*Noun { 52 list := make([]*Noun, 0, len(m)) 53 for _, n := range m { 54 list = append(list, n) 55 } 56 return list 57 } 58 59 func TestGraph_Noun(t *testing.T) { 60 nodes := ParseNouns(`a -> b 61 a -> c 62 b -> d 63 b -> e 64 c -> d 65 c -> e`) 66 67 g := &Graph{ 68 Name: "Test", 69 Nouns: NounMapToList(nodes), 70 } 71 72 n := g.Noun("a") 73 if n == nil { 74 t.Fatal("should not be nil") 75 } 76 if n.Name != "a" { 77 t.Fatalf("bad: %#v", n) 78 } 79 } 80 81 func TestGraph_String(t *testing.T) { 82 nodes := ParseNouns(`a -> b 83 a -> c 84 b -> d 85 b -> e 86 c -> d 87 c -> e`) 88 89 g := &Graph{ 90 Name: "Test", 91 Nouns: NounMapToList(nodes), 92 Root: nodes["a"], 93 } 94 actual := g.String() 95 96 expected := ` 97 root: a 98 a 99 a -> b 100 a -> c 101 b 102 b -> d 103 b -> e 104 c 105 c -> d 106 c -> e 107 d 108 e 109 ` 110 111 actual = strings.TrimSpace(actual) 112 expected = strings.TrimSpace(expected) 113 if actual != expected { 114 t.Fatalf("bad:\n%s\n!=\n%s", actual, expected) 115 } 116 } 117 118 func TestGraph_Validate(t *testing.T) { 119 nodes := ParseNouns(`a -> b 120 a -> c 121 b -> d 122 b -> e 123 c -> d 124 c -> e`) 125 list := NounMapToList(nodes) 126 127 g := &Graph{Name: "Test", Nouns: list} 128 if err := g.Validate(); err != nil { 129 t.Fatalf("err: %v", err) 130 } 131 } 132 133 func TestGraph_Validate_Cycle(t *testing.T) { 134 nodes := ParseNouns(`a -> b 135 a -> c 136 b -> d 137 d -> b`) 138 list := NounMapToList(nodes) 139 140 g := &Graph{Name: "Test", Nouns: list} 141 err := g.Validate() 142 if err == nil { 143 t.Fatalf("expected err") 144 } 145 146 vErr, ok := err.(*ValidateError) 147 if !ok { 148 t.Fatalf("expected validate error") 149 } 150 151 if len(vErr.Cycles) != 1 { 152 t.Fatalf("expected cycles") 153 } 154 155 cycle := vErr.Cycles[0] 156 cycleNodes := make([]string, len(cycle)) 157 for i, c := range cycle { 158 cycleNodes[i] = c.Name 159 } 160 sort.Strings(cycleNodes) 161 162 if cycleNodes[0] != "b" { 163 t.Fatalf("bad: %v", cycle) 164 } 165 if cycleNodes[1] != "d" { 166 t.Fatalf("bad: %v", cycle) 167 } 168 } 169 170 func TestGraph_Validate_MultiRoot(t *testing.T) { 171 nodes := ParseNouns(`a -> b 172 c -> d`) 173 list := NounMapToList(nodes) 174 175 g := &Graph{Name: "Test", Nouns: list} 176 err := g.Validate() 177 if err == nil { 178 t.Fatalf("expected err") 179 } 180 181 vErr, ok := err.(*ValidateError) 182 if !ok { 183 t.Fatalf("expected validate error") 184 } 185 186 if !vErr.MissingRoot { 187 t.Fatalf("expected missing root") 188 } 189 } 190 191 func TestGraph_Validate_NoRoot(t *testing.T) { 192 nodes := ParseNouns(`a -> b 193 b -> a`) 194 list := NounMapToList(nodes) 195 196 g := &Graph{Name: "Test", Nouns: list} 197 err := g.Validate() 198 if err == nil { 199 t.Fatalf("expected err") 200 } 201 202 vErr, ok := err.(*ValidateError) 203 if !ok { 204 t.Fatalf("expected validate error") 205 } 206 207 if !vErr.MissingRoot { 208 t.Fatalf("expected missing root") 209 } 210 } 211 212 func TestGraph_Validate_Unreachable(t *testing.T) { 213 nodes := ParseNouns(`a -> b 214 a -> c 215 b -> d 216 x -> x`) 217 list := NounMapToList(nodes) 218 219 g := &Graph{Name: "Test", Nouns: list} 220 err := g.Validate() 221 if err == nil { 222 t.Fatalf("expected err") 223 } 224 225 vErr, ok := err.(*ValidateError) 226 if !ok { 227 t.Fatalf("expected validate error") 228 } 229 230 if len(vErr.Unreachable) != 1 { 231 t.Fatalf("expected unreachable") 232 } 233 234 if vErr.Unreachable[0].Name != "x" { 235 t.Fatalf("bad: %v", vErr.Unreachable[0]) 236 } 237 } 238 239 type VersionMeta int 240 type VersionConstraint struct { 241 Min int 242 Max int 243 } 244 245 func (v *VersionConstraint) Satisfied(head, tail *Noun) (bool, error) { 246 vers := int(tail.Meta.(VersionMeta)) 247 if vers < v.Min { 248 return false, fmt.Errorf("version %d below minimum %d", 249 vers, v.Min) 250 } else if vers > v.Max { 251 return false, fmt.Errorf("version %d above maximum %d", 252 vers, v.Max) 253 } 254 return true, nil 255 } 256 257 func (v *VersionConstraint) String() string { 258 return "version" 259 } 260 261 func TestGraph_ConstraintViolation(t *testing.T) { 262 nodes := ParseNouns(`a -> b 263 a -> c 264 b -> d 265 b -> e 266 c -> d 267 c -> e`) 268 list := NounMapToList(nodes) 269 270 // Add a version constraint 271 vers := &VersionConstraint{1, 3} 272 273 // Introduce some constraints 274 depB := nodes["a"].Deps[0] 275 depB.Constraints = []Constraint{vers} 276 depC := nodes["a"].Deps[1] 277 depC.Constraints = []Constraint{vers} 278 279 // Add some versions 280 nodes["b"].Meta = VersionMeta(0) 281 nodes["c"].Meta = VersionMeta(4) 282 283 g := &Graph{Name: "Test", Nouns: list} 284 err := g.Validate() 285 if err != nil { 286 t.Fatalf("err: %v", err) 287 } 288 289 err = g.CheckConstraints() 290 if err == nil { 291 t.Fatalf("Expected err") 292 } 293 294 cErr, ok := err.(*ConstraintError) 295 if !ok { 296 t.Fatalf("expected constraint error") 297 } 298 299 if len(cErr.Violations) != 2 { 300 t.Fatalf("expected 2 violations: %v", cErr) 301 } 302 303 if cErr.Violations[0].Error() != "Constraint version between a and b violated: version 0 below minimum 1" { 304 t.Fatalf("err: %v", cErr.Violations[0]) 305 } 306 307 if cErr.Violations[1].Error() != "Constraint version between a and c violated: version 4 above maximum 3" { 308 t.Fatalf("err: %v", cErr.Violations[1]) 309 } 310 } 311 312 func TestGraph_Constraint_NoViolation(t *testing.T) { 313 nodes := ParseNouns(`a -> b 314 a -> c 315 b -> d 316 b -> e 317 c -> d 318 c -> e`) 319 list := NounMapToList(nodes) 320 321 // Add a version constraint 322 vers := &VersionConstraint{1, 3} 323 324 // Introduce some constraints 325 depB := nodes["a"].Deps[0] 326 depB.Constraints = []Constraint{vers} 327 depC := nodes["a"].Deps[1] 328 depC.Constraints = []Constraint{vers} 329 330 // Add some versions 331 nodes["b"].Meta = VersionMeta(2) 332 nodes["c"].Meta = VersionMeta(3) 333 334 g := &Graph{Name: "Test", Nouns: list} 335 err := g.Validate() 336 if err != nil { 337 t.Fatalf("err: %v", err) 338 } 339 340 err = g.CheckConstraints() 341 if err != nil { 342 t.Fatalf("err: %v", err) 343 } 344 } 345 346 func TestGraphWalk(t *testing.T) { 347 nodes := ParseNouns(`a -> b 348 a -> c 349 b -> d 350 b -> e 351 c -> d 352 c -> e`) 353 list := NounMapToList(nodes) 354 g := &Graph{Name: "Test", Nouns: list} 355 if err := g.Validate(); err != nil { 356 t.Fatalf("err: %s", err) 357 } 358 359 var namesLock sync.Mutex 360 names := make([]string, 0, 0) 361 err := g.Walk(func(n *Noun) error { 362 namesLock.Lock() 363 defer namesLock.Unlock() 364 names = append(names, n.Name) 365 return nil 366 }) 367 if err != nil { 368 t.Fatalf("err: %s", err) 369 } 370 371 expected := [][]string{ 372 {"e", "d", "c", "b", "a"}, 373 {"e", "d", "b", "c", "a"}, 374 {"d", "e", "c", "b", "a"}, 375 {"d", "e", "b", "c", "a"}, 376 } 377 found := false 378 for _, expect := range expected { 379 if reflect.DeepEqual(expect, names) { 380 found = true 381 break 382 } 383 } 384 if !found { 385 t.Fatalf("bad: %#v", names) 386 } 387 } 388 389 func TestGraphWalk_error(t *testing.T) { 390 nodes := ParseNouns(`a -> b 391 b -> c 392 a -> d 393 a -> e 394 e -> f 395 f -> g 396 g -> h`) 397 list := NounMapToList(nodes) 398 g := &Graph{Name: "Test", Nouns: list} 399 if err := g.Validate(); err != nil { 400 t.Fatalf("err: %s", err) 401 } 402 403 // We repeat this a lot because sometimes timing causes 404 // a false positive. 405 for i := 0; i < 100; i++ { 406 var lock sync.Mutex 407 var walked []string 408 err := g.Walk(func(n *Noun) error { 409 lock.Lock() 410 defer lock.Unlock() 411 412 walked = append(walked, n.Name) 413 414 if n.Name == "b" { 415 return fmt.Errorf("foo") 416 } 417 418 return nil 419 }) 420 if err == nil { 421 t.Fatal("should error") 422 } 423 424 sort.Strings(walked) 425 426 expected := []string{"b", "c", "d", "e", "f", "g", "h"} 427 if !reflect.DeepEqual(walked, expected) { 428 t.Fatalf("bad: %#v", walked) 429 } 430 } 431 } 432 433 func TestGraph_DependsOn(t *testing.T) { 434 nodes := ParseNouns(`a -> b 435 a -> c 436 b -> d 437 b -> e 438 c -> d 439 c -> e`) 440 441 g := &Graph{ 442 Name: "Test", 443 Nouns: NounMapToList(nodes), 444 } 445 446 dNoun := g.Noun("d") 447 incoming := g.DependsOn(dNoun) 448 449 if len(incoming) != 2 { 450 t.Fatalf("bad: %#v", incoming) 451 } 452 453 var hasB, hasC bool 454 for _, in := range incoming { 455 switch in.Name { 456 case "b": 457 hasB = true 458 case "c": 459 hasC = true 460 default: 461 t.Fatalf("Bad: %#v", in) 462 } 463 } 464 if !hasB || !hasC { 465 t.Fatalf("missing incoming edge") 466 } 467 }