github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/terraform/diff_test.go (about) 1 package terraform 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 ) 8 9 func TestDiffEmpty(t *testing.T) { 10 diff := new(Diff) 11 if !diff.Empty() { 12 t.Fatal("should be empty") 13 } 14 15 mod := diff.AddModule(rootModulePath) 16 mod.Resources["nodeA"] = &InstanceDiff{ 17 Attributes: map[string]*ResourceAttrDiff{ 18 "foo": &ResourceAttrDiff{ 19 Old: "foo", 20 New: "bar", 21 }, 22 }, 23 } 24 25 if diff.Empty() { 26 t.Fatal("should not be empty") 27 } 28 } 29 30 func TestModuleDiff_ChangeType(t *testing.T) { 31 cases := []struct { 32 Diff *ModuleDiff 33 Result DiffChangeType 34 }{ 35 { 36 &ModuleDiff{}, 37 DiffNone, 38 }, 39 { 40 &ModuleDiff{ 41 Resources: map[string]*InstanceDiff{ 42 "foo": &InstanceDiff{Destroy: true}, 43 }, 44 }, 45 DiffDestroy, 46 }, 47 { 48 &ModuleDiff{ 49 Resources: map[string]*InstanceDiff{ 50 "foo": &InstanceDiff{ 51 Attributes: map[string]*ResourceAttrDiff{ 52 "foo": &ResourceAttrDiff{ 53 Old: "", 54 New: "bar", 55 }, 56 }, 57 }, 58 }, 59 }, 60 DiffUpdate, 61 }, 62 { 63 &ModuleDiff{ 64 Resources: map[string]*InstanceDiff{ 65 "foo": &InstanceDiff{ 66 Attributes: map[string]*ResourceAttrDiff{ 67 "foo": &ResourceAttrDiff{ 68 Old: "", 69 New: "bar", 70 RequiresNew: true, 71 }, 72 }, 73 }, 74 }, 75 }, 76 DiffCreate, 77 }, 78 { 79 &ModuleDiff{ 80 Resources: map[string]*InstanceDiff{ 81 "foo": &InstanceDiff{ 82 Destroy: true, 83 Attributes: map[string]*ResourceAttrDiff{ 84 "foo": &ResourceAttrDiff{ 85 Old: "", 86 New: "bar", 87 RequiresNew: true, 88 }, 89 }, 90 }, 91 }, 92 }, 93 DiffUpdate, 94 }, 95 } 96 97 for i, tc := range cases { 98 actual := tc.Diff.ChangeType() 99 if actual != tc.Result { 100 t.Fatalf("%d: %#v", i, actual) 101 } 102 } 103 } 104 105 func TestModuleDiff_Empty(t *testing.T) { 106 diff := new(ModuleDiff) 107 if !diff.Empty() { 108 t.Fatal("should be empty") 109 } 110 111 diff.Resources = map[string]*InstanceDiff{ 112 "nodeA": &InstanceDiff{}, 113 } 114 115 if !diff.Empty() { 116 t.Fatal("should be empty") 117 } 118 119 diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ 120 "foo": &ResourceAttrDiff{ 121 Old: "foo", 122 New: "bar", 123 }, 124 } 125 126 if diff.Empty() { 127 t.Fatal("should not be empty") 128 } 129 130 diff.Resources["nodeA"].Attributes = nil 131 diff.Resources["nodeA"].Destroy = true 132 133 if diff.Empty() { 134 t.Fatal("should not be empty") 135 } 136 } 137 138 func TestModuleDiff_String(t *testing.T) { 139 diff := &ModuleDiff{ 140 Resources: map[string]*InstanceDiff{ 141 "nodeA": &InstanceDiff{ 142 Attributes: map[string]*ResourceAttrDiff{ 143 "foo": &ResourceAttrDiff{ 144 Old: "foo", 145 New: "bar", 146 }, 147 "bar": &ResourceAttrDiff{ 148 Old: "foo", 149 NewComputed: true, 150 }, 151 "longfoo": &ResourceAttrDiff{ 152 Old: "foo", 153 New: "bar", 154 RequiresNew: true, 155 }, 156 "secretfoo": &ResourceAttrDiff{ 157 Old: "foo", 158 New: "bar", 159 Sensitive: true, 160 }, 161 }, 162 }, 163 }, 164 } 165 166 actual := strings.TrimSpace(diff.String()) 167 expected := strings.TrimSpace(moduleDiffStrBasic) 168 if actual != expected { 169 t.Fatalf("bad:\n%s", actual) 170 } 171 } 172 173 func TestInstanceDiff_ChangeType(t *testing.T) { 174 cases := []struct { 175 Diff *InstanceDiff 176 Result DiffChangeType 177 }{ 178 { 179 &InstanceDiff{}, 180 DiffNone, 181 }, 182 { 183 &InstanceDiff{Destroy: true}, 184 DiffDestroy, 185 }, 186 { 187 &InstanceDiff{ 188 Attributes: map[string]*ResourceAttrDiff{ 189 "foo": &ResourceAttrDiff{ 190 Old: "", 191 New: "bar", 192 }, 193 }, 194 }, 195 DiffUpdate, 196 }, 197 { 198 &InstanceDiff{ 199 Attributes: map[string]*ResourceAttrDiff{ 200 "foo": &ResourceAttrDiff{ 201 Old: "", 202 New: "bar", 203 RequiresNew: true, 204 }, 205 }, 206 }, 207 DiffCreate, 208 }, 209 { 210 &InstanceDiff{ 211 Destroy: true, 212 Attributes: map[string]*ResourceAttrDiff{ 213 "foo": &ResourceAttrDiff{ 214 Old: "", 215 New: "bar", 216 RequiresNew: true, 217 }, 218 }, 219 }, 220 DiffDestroyCreate, 221 }, 222 { 223 &InstanceDiff{ 224 DestroyTainted: true, 225 Attributes: map[string]*ResourceAttrDiff{ 226 "foo": &ResourceAttrDiff{ 227 Old: "", 228 New: "bar", 229 RequiresNew: true, 230 }, 231 }, 232 }, 233 DiffDestroyCreate, 234 }, 235 } 236 237 for i, tc := range cases { 238 actual := tc.Diff.ChangeType() 239 if actual != tc.Result { 240 t.Fatalf("%d: %#v", i, actual) 241 } 242 } 243 } 244 245 func TestInstanceDiff_Empty(t *testing.T) { 246 var rd *InstanceDiff 247 248 if !rd.Empty() { 249 t.Fatal("should be empty") 250 } 251 252 rd = new(InstanceDiff) 253 254 if !rd.Empty() { 255 t.Fatal("should be empty") 256 } 257 258 rd = &InstanceDiff{Destroy: true} 259 260 if rd.Empty() { 261 t.Fatal("should not be empty") 262 } 263 264 rd = &InstanceDiff{ 265 Attributes: map[string]*ResourceAttrDiff{ 266 "foo": &ResourceAttrDiff{ 267 New: "bar", 268 }, 269 }, 270 } 271 272 if rd.Empty() { 273 t.Fatal("should not be empty") 274 } 275 } 276 277 func TestModuleDiff_Instances(t *testing.T) { 278 yesDiff := &InstanceDiff{Destroy: true} 279 noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} 280 281 cases := []struct { 282 Diff *ModuleDiff 283 Id string 284 Result []*InstanceDiff 285 }{ 286 { 287 &ModuleDiff{ 288 Resources: map[string]*InstanceDiff{ 289 "foo": yesDiff, 290 "bar": noDiff, 291 }, 292 }, 293 "foo", 294 []*InstanceDiff{ 295 yesDiff, 296 }, 297 }, 298 299 { 300 &ModuleDiff{ 301 Resources: map[string]*InstanceDiff{ 302 "foo": yesDiff, 303 "foo.0": yesDiff, 304 "bar": noDiff, 305 }, 306 }, 307 "foo", 308 []*InstanceDiff{ 309 yesDiff, 310 yesDiff, 311 }, 312 }, 313 314 { 315 &ModuleDiff{ 316 Resources: map[string]*InstanceDiff{ 317 "foo": yesDiff, 318 "foo.0": yesDiff, 319 "foo_bar": noDiff, 320 "bar": noDiff, 321 }, 322 }, 323 "foo", 324 []*InstanceDiff{ 325 yesDiff, 326 yesDiff, 327 }, 328 }, 329 } 330 331 for i, tc := range cases { 332 actual := tc.Diff.Instances(tc.Id) 333 if !reflect.DeepEqual(actual, tc.Result) { 334 t.Fatalf("%d: %#v", i, actual) 335 } 336 } 337 } 338 339 func TestInstanceDiff_RequiresNew(t *testing.T) { 340 rd := &InstanceDiff{ 341 Attributes: map[string]*ResourceAttrDiff{ 342 "foo": &ResourceAttrDiff{}, 343 }, 344 } 345 346 if rd.RequiresNew() { 347 t.Fatal("should not require new") 348 } 349 350 rd.Attributes["foo"].RequiresNew = true 351 352 if !rd.RequiresNew() { 353 t.Fatal("should require new") 354 } 355 } 356 357 func TestInstanceDiff_RequiresNew_nil(t *testing.T) { 358 var rd *InstanceDiff 359 360 if rd.RequiresNew() { 361 t.Fatal("should not require new") 362 } 363 } 364 365 func TestInstanceDiffSame(t *testing.T) { 366 cases := []struct { 367 One, Two *InstanceDiff 368 Same bool 369 Reason string 370 }{ 371 { 372 &InstanceDiff{}, 373 &InstanceDiff{}, 374 true, 375 "", 376 }, 377 378 { 379 nil, 380 nil, 381 true, 382 "", 383 }, 384 385 { 386 &InstanceDiff{Destroy: false}, 387 &InstanceDiff{Destroy: true}, 388 false, 389 "diff: Destroy; old: false, new: true", 390 }, 391 392 { 393 &InstanceDiff{Destroy: true}, 394 &InstanceDiff{Destroy: true}, 395 true, 396 "", 397 }, 398 399 { 400 &InstanceDiff{ 401 Attributes: map[string]*ResourceAttrDiff{ 402 "foo": &ResourceAttrDiff{}, 403 }, 404 }, 405 &InstanceDiff{ 406 Attributes: map[string]*ResourceAttrDiff{ 407 "foo": &ResourceAttrDiff{}, 408 }, 409 }, 410 true, 411 "", 412 }, 413 414 { 415 &InstanceDiff{ 416 Attributes: map[string]*ResourceAttrDiff{ 417 "bar": &ResourceAttrDiff{}, 418 }, 419 }, 420 &InstanceDiff{ 421 Attributes: map[string]*ResourceAttrDiff{ 422 "foo": &ResourceAttrDiff{}, 423 }, 424 }, 425 false, 426 "attribute mismatch: bar", 427 }, 428 429 // Extra attributes 430 { 431 &InstanceDiff{ 432 Attributes: map[string]*ResourceAttrDiff{ 433 "foo": &ResourceAttrDiff{}, 434 }, 435 }, 436 &InstanceDiff{ 437 Attributes: map[string]*ResourceAttrDiff{ 438 "foo": &ResourceAttrDiff{}, 439 "bar": &ResourceAttrDiff{}, 440 }, 441 }, 442 false, 443 "extra attributes: bar", 444 }, 445 446 { 447 &InstanceDiff{ 448 Attributes: map[string]*ResourceAttrDiff{ 449 "foo": &ResourceAttrDiff{RequiresNew: true}, 450 }, 451 }, 452 &InstanceDiff{ 453 Attributes: map[string]*ResourceAttrDiff{ 454 "foo": &ResourceAttrDiff{RequiresNew: false}, 455 }, 456 }, 457 false, 458 "diff RequiresNew; old: true, new: false", 459 }, 460 461 { 462 &InstanceDiff{ 463 Attributes: map[string]*ResourceAttrDiff{ 464 "foo.#": &ResourceAttrDiff{NewComputed: true}, 465 }, 466 }, 467 &InstanceDiff{ 468 Attributes: map[string]*ResourceAttrDiff{ 469 "foo.#": &ResourceAttrDiff{ 470 Old: "0", 471 New: "1", 472 }, 473 "foo.0": &ResourceAttrDiff{ 474 Old: "", 475 New: "12", 476 }, 477 }, 478 }, 479 true, 480 "", 481 }, 482 483 { 484 &InstanceDiff{ 485 Attributes: map[string]*ResourceAttrDiff{ 486 "foo.#": &ResourceAttrDiff{ 487 Old: "0", 488 New: "1", 489 }, 490 "foo.~35964334.bar": &ResourceAttrDiff{ 491 Old: "", 492 New: "${var.foo}", 493 }, 494 }, 495 }, 496 &InstanceDiff{ 497 Attributes: map[string]*ResourceAttrDiff{ 498 "foo.#": &ResourceAttrDiff{ 499 Old: "0", 500 New: "1", 501 }, 502 "foo.87654323.bar": &ResourceAttrDiff{ 503 Old: "", 504 New: "12", 505 }, 506 }, 507 }, 508 true, 509 "", 510 }, 511 512 { 513 &InstanceDiff{ 514 Attributes: map[string]*ResourceAttrDiff{ 515 "foo.#": &ResourceAttrDiff{ 516 Old: "0", 517 NewComputed: true, 518 }, 519 }, 520 }, 521 &InstanceDiff{ 522 Attributes: map[string]*ResourceAttrDiff{}, 523 }, 524 true, 525 "", 526 }, 527 528 // Computed sets may not contain all fields in the original diff, and 529 // because multiple entries for the same set can compute to the same 530 // hash before the values are computed or interpolated, the overall 531 // count can change as well. 532 { 533 &InstanceDiff{ 534 Attributes: map[string]*ResourceAttrDiff{ 535 "foo.#": &ResourceAttrDiff{ 536 Old: "0", 537 New: "1", 538 }, 539 "foo.~35964334.bar": &ResourceAttrDiff{ 540 Old: "", 541 New: "${var.foo}", 542 }, 543 }, 544 }, 545 &InstanceDiff{ 546 Attributes: map[string]*ResourceAttrDiff{ 547 "foo.#": &ResourceAttrDiff{ 548 Old: "0", 549 New: "2", 550 }, 551 "foo.87654323.bar": &ResourceAttrDiff{ 552 Old: "", 553 New: "12", 554 }, 555 "foo.87654325.bar": &ResourceAttrDiff{ 556 Old: "", 557 New: "12", 558 }, 559 "foo.87654325.baz": &ResourceAttrDiff{ 560 Old: "", 561 New: "12", 562 }, 563 }, 564 }, 565 true, 566 "", 567 }, 568 569 // Computed values in maps will fail the "Same" check as well 570 { 571 &InstanceDiff{ 572 Attributes: map[string]*ResourceAttrDiff{ 573 "foo.%": &ResourceAttrDiff{ 574 Old: "", 575 New: "", 576 NewComputed: true, 577 }, 578 }, 579 }, 580 &InstanceDiff{ 581 Attributes: map[string]*ResourceAttrDiff{ 582 "foo.%": &ResourceAttrDiff{ 583 Old: "0", 584 New: "1", 585 NewComputed: false, 586 }, 587 "foo.val": &ResourceAttrDiff{ 588 Old: "", 589 New: "something", 590 }, 591 }, 592 }, 593 true, 594 "", 595 }, 596 597 // In a DESTROY/CREATE scenario, the plan diff will be run against the 598 // state of the old instance, while the apply diff will be run against an 599 // empty state (because the state is cleared when the destroy runs.) 600 // For complex attributes, this can result in keys that seem to disappear 601 // between the two diffs, when in reality everything is working just fine. 602 // 603 // Same() needs to take into account this scenario by analyzing NewRemoved 604 // and treating as "Same" a diff that does indeed have that key removed. 605 { 606 &InstanceDiff{ 607 Attributes: map[string]*ResourceAttrDiff{ 608 "somemap.oldkey": &ResourceAttrDiff{ 609 Old: "long ago", 610 New: "", 611 NewRemoved: true, 612 }, 613 "somemap.newkey": &ResourceAttrDiff{ 614 Old: "", 615 New: "brave new world", 616 }, 617 }, 618 }, 619 &InstanceDiff{ 620 Attributes: map[string]*ResourceAttrDiff{ 621 "somemap.newkey": &ResourceAttrDiff{ 622 Old: "", 623 New: "brave new world", 624 }, 625 }, 626 }, 627 true, 628 "", 629 }, 630 631 // Another thing that can occur in DESTROY/CREATE scenarios is that list 632 // values that are going to zero have diffs that show up at plan time but 633 // are gone at apply time. The NewRemoved handling catches the fields and 634 // treats them as OK, but it also needs to treat the .# field itself as 635 // okay to be present in the old diff but not in the new one. 636 { 637 &InstanceDiff{ 638 Attributes: map[string]*ResourceAttrDiff{ 639 "reqnew": &ResourceAttrDiff{ 640 Old: "old", 641 New: "new", 642 RequiresNew: true, 643 }, 644 "somemap.#": &ResourceAttrDiff{ 645 Old: "1", 646 New: "0", 647 }, 648 "somemap.oldkey": &ResourceAttrDiff{ 649 Old: "long ago", 650 New: "", 651 NewRemoved: true, 652 }, 653 }, 654 }, 655 &InstanceDiff{ 656 Attributes: map[string]*ResourceAttrDiff{ 657 "reqnew": &ResourceAttrDiff{ 658 Old: "", 659 New: "new", 660 RequiresNew: true, 661 }, 662 }, 663 }, 664 true, 665 "", 666 }, 667 668 { 669 &InstanceDiff{ 670 Attributes: map[string]*ResourceAttrDiff{ 671 "reqnew": &ResourceAttrDiff{ 672 Old: "old", 673 New: "new", 674 RequiresNew: true, 675 }, 676 "somemap.%": &ResourceAttrDiff{ 677 Old: "1", 678 New: "0", 679 }, 680 "somemap.oldkey": &ResourceAttrDiff{ 681 Old: "long ago", 682 New: "", 683 NewRemoved: true, 684 }, 685 }, 686 }, 687 &InstanceDiff{ 688 Attributes: map[string]*ResourceAttrDiff{ 689 "reqnew": &ResourceAttrDiff{ 690 Old: "", 691 New: "new", 692 RequiresNew: true, 693 }, 694 }, 695 }, 696 true, 697 "", 698 }, 699 } 700 701 for i, tc := range cases { 702 same, reason := tc.One.Same(tc.Two) 703 if same != tc.Same { 704 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 705 i, tc.Same, same, reason, tc.One, tc.Two) 706 } 707 if reason != tc.Reason { 708 t.Fatalf( 709 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 710 } 711 } 712 } 713 714 const moduleDiffStrBasic = ` 715 CREATE: nodeA 716 bar: "foo" => "<computed>" 717 foo: "foo" => "bar" 718 longfoo: "foo" => "bar" (forces new resource) 719 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 720 `