github.com/serbaut/terraform@v0.6.12-0.20160607213102-ac2d195cc560/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 // In a DESTROY/CREATE scenario, the plan diff will be run against the 529 // state of the old instance, while the apply diff will be run against an 530 // empty state (because the state is cleared when the destroy runs.) 531 // For complex attributes, this can result in keys that seem to disappear 532 // between the two diffs, when in reality everything is working just fine. 533 // 534 // Same() needs to take into account this scenario by analyzing NewRemoved 535 // and treating as "Same" a diff that does indeed have that key removed. 536 { 537 &InstanceDiff{ 538 Attributes: map[string]*ResourceAttrDiff{ 539 "somemap.oldkey": &ResourceAttrDiff{ 540 Old: "long ago", 541 New: "", 542 NewRemoved: true, 543 }, 544 "somemap.newkey": &ResourceAttrDiff{ 545 Old: "", 546 New: "brave new world", 547 }, 548 }, 549 }, 550 &InstanceDiff{ 551 Attributes: map[string]*ResourceAttrDiff{ 552 "somemap.newkey": &ResourceAttrDiff{ 553 Old: "", 554 New: "brave new world", 555 }, 556 }, 557 }, 558 true, 559 "", 560 }, 561 562 // Another thing that can occur in DESTROY/CREATE scenarios is that list 563 // values that are going to zero have diffs that show up at plan time but 564 // are gone at apply time. The NewRemoved handling catches the fields and 565 // treats them as OK, but it also needs to treat the .# field itself as 566 // okay to be present in the old diff but not in the new one. 567 { 568 &InstanceDiff{ 569 Attributes: map[string]*ResourceAttrDiff{ 570 "reqnew": &ResourceAttrDiff{ 571 Old: "old", 572 New: "new", 573 RequiresNew: true, 574 }, 575 "somemap.#": &ResourceAttrDiff{ 576 Old: "1", 577 New: "0", 578 }, 579 "somemap.oldkey": &ResourceAttrDiff{ 580 Old: "long ago", 581 New: "", 582 NewRemoved: true, 583 }, 584 }, 585 }, 586 &InstanceDiff{ 587 Attributes: map[string]*ResourceAttrDiff{ 588 "reqnew": &ResourceAttrDiff{ 589 Old: "", 590 New: "new", 591 RequiresNew: true, 592 }, 593 }, 594 }, 595 true, 596 "", 597 }, 598 } 599 600 for i, tc := range cases { 601 same, reason := tc.One.Same(tc.Two) 602 if same != tc.Same { 603 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 604 i, tc.Same, same, reason, tc.One, tc.Two) 605 } 606 if reason != tc.Reason { 607 t.Fatalf( 608 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 609 } 610 } 611 } 612 613 const moduleDiffStrBasic = ` 614 CREATE: nodeA 615 bar: "foo" => "<computed>" 616 foo: "foo" => "bar" 617 longfoo: "foo" => "bar" (forces new resource) 618 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 619 `