github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing.go (about) 1 package resource 2 3 import ( 4 "bytes" 5 "errors" 6 "flag" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "os" 12 "path/filepath" 13 "reflect" 14 "regexp" 15 "strconv" 16 "strings" 17 "syscall" 18 "testing" 19 20 "github.com/davecgh/go-spew/spew" 21 "github.com/hashicorp/errwrap" 22 "github.com/hashicorp/go-multierror" 23 "github.com/hashicorp/logutils" 24 "github.com/hashicorp/terraform-plugin-sdk/acctest" 25 "github.com/hashicorp/terraform-plugin-sdk/helper/logging" 26 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 27 "github.com/hashicorp/terraform-plugin-sdk/internal/command/format" 28 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 29 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configload" 30 "github.com/hashicorp/terraform-plugin-sdk/internal/initwd" 31 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 32 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 33 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 34 "github.com/hashicorp/terraform-plugin-sdk/terraform" 35 "github.com/mitchellh/colorstring" 36 ) 37 38 // flagSweep is a flag available when running tests on the command line. It 39 // contains a comma seperated list of regions to for the sweeper functions to 40 // run in. This flag bypasses the normal Test path and instead runs functions designed to 41 // clean up any leaked resources a testing environment could have created. It is 42 // a best effort attempt, and relies on Provider authors to implement "Sweeper" 43 // methods for resources. 44 45 // Adding Sweeper methods with AddTestSweepers will 46 // construct a list of sweeper funcs to be called here. We iterate through 47 // regions provided by the sweep flag, and for each region we iterate through the 48 // tests, and exit on any errors. At time of writing, sweepers are ran 49 // sequentially, however they can list dependencies to be ran first. We track 50 // the sweepers that have been ran, so as to not run a sweeper twice for a given 51 // region. 52 // 53 // WARNING: 54 // Sweepers are designed to be destructive. You should not use the -sweep flag 55 // in any environment that is not strictly a test environment. Resources will be 56 // destroyed. 57 58 var flagSweep = flag.String("sweep", "", "List of Regions to run available Sweepers") 59 var flagSweepAllowFailures = flag.Bool("sweep-allow-failures", false, "Enable to allow Sweeper Tests to continue after failures") 60 var flagSweepRun = flag.String("sweep-run", "", "Comma seperated list of Sweeper Tests to run") 61 var sweeperFuncs map[string]*Sweeper 62 63 // type SweeperFunc is a signature for a function that acts as a sweeper. It 64 // accepts a string for the region that the sweeper is to be ran in. This 65 // function must be able to construct a valid client for that region. 66 type SweeperFunc func(r string) error 67 68 type Sweeper struct { 69 // Name for sweeper. Must be unique to be ran by the Sweeper Runner 70 Name string 71 72 // Dependencies list the const names of other Sweeper functions that must be ran 73 // prior to running this Sweeper. This is an ordered list that will be invoked 74 // recursively at the helper/resource level 75 Dependencies []string 76 77 // Sweeper function that when invoked sweeps the Provider of specific 78 // resources 79 F SweeperFunc 80 } 81 82 func init() { 83 sweeperFuncs = make(map[string]*Sweeper) 84 } 85 86 // AddTestSweepers function adds a given name and Sweeper configuration 87 // pair to the internal sweeperFuncs map. Invoke this function to register a 88 // resource sweeper to be available for running when the -sweep flag is used 89 // with `go test`. Sweeper names must be unique to help ensure a given sweeper 90 // is only ran once per run. 91 func AddTestSweepers(name string, s *Sweeper) { 92 if _, ok := sweeperFuncs[name]; ok { 93 log.Fatalf("[ERR] Error adding (%s) to sweeperFuncs: function already exists in map", name) 94 } 95 96 sweeperFuncs[name] = s 97 } 98 99 func TestMain(m *testing.M) { 100 flag.Parse() 101 if *flagSweep != "" { 102 // parse flagSweep contents for regions to run 103 regions := strings.Split(*flagSweep, ",") 104 105 // get filtered list of sweepers to run based on sweep-run flag 106 sweepers := filterSweepers(*flagSweepRun, sweeperFuncs) 107 108 if _, err := runSweepers(regions, sweepers, *flagSweepAllowFailures); err != nil { 109 os.Exit(1) 110 } 111 } else { 112 exitCode := m.Run() 113 114 if acctest.TestHelper != nil { 115 err := acctest.TestHelper.Close() 116 if err != nil { 117 log.Printf("Error cleaning up temporary test files: %s", err) 118 } 119 } 120 os.Exit(exitCode) 121 } 122 } 123 124 func runSweepers(regions []string, sweepers map[string]*Sweeper, allowFailures bool) (map[string]map[string]error, error) { 125 var sweeperErrorFound bool 126 sweeperRunList := make(map[string]map[string]error) 127 128 for _, region := range regions { 129 region = strings.TrimSpace(region) 130 131 var regionSweeperErrorFound bool 132 regionSweeperRunList := make(map[string]error) 133 134 log.Printf("[DEBUG] Running Sweepers for region (%s):\n", region) 135 for _, sweeper := range sweepers { 136 if err := runSweeperWithRegion(region, sweeper, sweepers, regionSweeperRunList, allowFailures); err != nil { 137 if allowFailures { 138 continue 139 } 140 141 sweeperRunList[region] = regionSweeperRunList 142 return sweeperRunList, fmt.Errorf("sweeper (%s) for region (%s) failed: %s", sweeper.Name, region, err) 143 } 144 } 145 146 log.Printf("Sweeper Tests ran successfully:\n") 147 for sweeper, sweeperErr := range regionSweeperRunList { 148 if sweeperErr == nil { 149 fmt.Printf("\t- %s\n", sweeper) 150 } else { 151 regionSweeperErrorFound = true 152 } 153 } 154 155 if regionSweeperErrorFound { 156 sweeperErrorFound = true 157 log.Printf("Sweeper Tests ran unsuccessfully:\n") 158 for sweeper, sweeperErr := range regionSweeperRunList { 159 if sweeperErr != nil { 160 fmt.Printf("\t- %s: %s\n", sweeper, sweeperErr) 161 } 162 } 163 } 164 165 sweeperRunList[region] = regionSweeperRunList 166 } 167 168 if sweeperErrorFound { 169 return sweeperRunList, errors.New("at least one sweeper failed") 170 } 171 172 return sweeperRunList, nil 173 } 174 175 // filterSweepers takes a comma seperated string listing the names of sweepers 176 // to be ran, and returns a filtered set from the list of all of sweepers to 177 // run based on the names given. 178 func filterSweepers(f string, source map[string]*Sweeper) map[string]*Sweeper { 179 filterSlice := strings.Split(strings.ToLower(f), ",") 180 if len(filterSlice) == 1 && filterSlice[0] == "" { 181 // if the filter slice is a single element of "" then no sweeper list was 182 // given, so just return the full list 183 return source 184 } 185 186 sweepers := make(map[string]*Sweeper) 187 for name := range source { 188 for _, s := range filterSlice { 189 if strings.Contains(strings.ToLower(name), s) { 190 for foundName, foundSweeper := range filterSweeperWithDependencies(name, source) { 191 sweepers[foundName] = foundSweeper 192 } 193 } 194 } 195 } 196 return sweepers 197 } 198 199 // filterSweeperWithDependencies recursively returns sweeper and all dependencies. 200 // Since filterSweepers performs fuzzy matching, this function is used 201 // to perform exact sweeper and dependency lookup. 202 func filterSweeperWithDependencies(name string, source map[string]*Sweeper) map[string]*Sweeper { 203 result := make(map[string]*Sweeper) 204 205 currentSweeper, ok := source[name] 206 if !ok { 207 log.Printf("[WARN] Sweeper has dependency (%s), but that sweeper was not found", name) 208 return result 209 } 210 211 result[name] = currentSweeper 212 213 for _, dependency := range currentSweeper.Dependencies { 214 for foundName, foundSweeper := range filterSweeperWithDependencies(dependency, source) { 215 result[foundName] = foundSweeper 216 } 217 } 218 219 return result 220 } 221 222 // runSweeperWithRegion recieves a sweeper and a region, and recursively calls 223 // itself with that region for every dependency found for that sweeper. If there 224 // are no dependencies, invoke the contained sweeper fun with the region, and 225 // add the success/fail status to the sweeperRunList. 226 func runSweeperWithRegion(region string, s *Sweeper, sweepers map[string]*Sweeper, sweeperRunList map[string]error, allowFailures bool) error { 227 for _, dep := range s.Dependencies { 228 if depSweeper, ok := sweepers[dep]; ok { 229 log.Printf("[DEBUG] Sweeper (%s) has dependency (%s), running..", s.Name, dep) 230 err := runSweeperWithRegion(region, depSweeper, sweepers, sweeperRunList, allowFailures) 231 232 if err != nil { 233 if allowFailures { 234 log.Printf("[ERROR] Error running Sweeper (%s) in region (%s): %s", depSweeper.Name, region, err) 235 continue 236 } 237 238 return err 239 } 240 } else { 241 log.Printf("[WARN] Sweeper (%s) has dependency (%s), but that sweeper was not found", s.Name, dep) 242 } 243 } 244 245 if _, ok := sweeperRunList[s.Name]; ok { 246 log.Printf("[DEBUG] Sweeper (%s) already ran in region (%s)", s.Name, region) 247 return nil 248 } 249 250 log.Printf("[DEBUG] Running Sweeper (%s) in region (%s)", s.Name, region) 251 252 runE := s.F(region) 253 254 sweeperRunList[s.Name] = runE 255 256 if runE != nil { 257 log.Printf("[ERROR] Error running Sweeper (%s) in region (%s): %s", s.Name, region, runE) 258 } 259 260 return runE 261 } 262 263 const TestEnvVar = "TF_ACC" 264 const TestDisableBinaryTestingFlagEnvVar = "TF_DISABLE_BINARY_TESTING" 265 266 // TestProvider can be implemented by any ResourceProvider to provide custom 267 // reset functionality at the start of an acceptance test. 268 // The helper/schema Provider implements this interface. 269 type TestProvider interface { 270 TestReset() error 271 } 272 273 // TestCheckFunc is the callback type used with acceptance tests to check 274 // the state of a resource. The state passed in is the latest state known, 275 // or in the case of being after a destroy, it is the last known state when 276 // it was created. 277 type TestCheckFunc func(*terraform.State) error 278 279 // ImportStateCheckFunc is the check function for ImportState tests 280 type ImportStateCheckFunc func([]*terraform.InstanceState) error 281 282 // ImportStateIdFunc is an ID generation function to help with complex ID 283 // generation for ImportState tests. 284 type ImportStateIdFunc func(*terraform.State) (string, error) 285 286 // TestCase is a single acceptance test case used to test the apply/destroy 287 // lifecycle of a resource in a specific configuration. 288 // 289 // When the destroy plan is executed, the config from the last TestStep 290 // is used to plan it. 291 type TestCase struct { 292 // IsUnitTest allows a test to run regardless of the TF_ACC 293 // environment variable. This should be used with care - only for 294 // fast tests on local resources (e.g. remote state with a local 295 // backend) but can be used to increase confidence in correct 296 // operation of Terraform without waiting for a full acctest run. 297 IsUnitTest bool 298 299 // PreCheck, if non-nil, will be called before any test steps are 300 // executed. It will only be executed in the case that the steps 301 // would run, so it can be used for some validation before running 302 // acceptance tests, such as verifying that keys are setup. 303 PreCheck func() 304 305 // Providers is the ResourceProvider that will be under test. 306 // 307 // Alternately, ProviderFactories can be specified for the providers 308 // that are valid. This takes priority over Providers. 309 // 310 // The end effect of each is the same: specifying the providers that 311 // are used within the tests. 312 Providers map[string]terraform.ResourceProvider 313 ProviderFactories map[string]terraform.ResourceProviderFactory 314 315 // ExternalProviders are providers the TestCase relies on that should 316 // be downloaded from the registry during init. This is only really 317 // necessary to set if you're using import, as providers in your config 318 // will be automatically retrieved during init. Import doesn't always 319 // use a config, however, so we allow manually specifying them here to 320 // be downloaded for import tests. 321 // 322 // ExternalProviders will only be used when using binary acceptance 323 // testing in reattach mode. 324 ExternalProviders map[string]ExternalProvider 325 326 // PreventPostDestroyRefresh can be set to true for cases where data sources 327 // are tested alongside real resources 328 PreventPostDestroyRefresh bool 329 330 // CheckDestroy is called after the resource is finally destroyed 331 // to allow the tester to test that the resource is truly gone. 332 CheckDestroy TestCheckFunc 333 334 // Steps are the apply sequences done within the context of the 335 // same state. Each step can have its own check to verify correctness. 336 Steps []TestStep 337 338 // The settings below control the "ID-only refresh test." This is 339 // an enabled-by-default test that tests that a refresh can be 340 // refreshed with only an ID to result in the same attributes. 341 // This validates completeness of Refresh. 342 // 343 // IDRefreshName is the name of the resource to check. This will 344 // default to the first non-nil primary resource in the state. 345 // 346 // IDRefreshIgnore is a list of configuration keys that will be ignored. 347 IDRefreshName string 348 IDRefreshIgnore []string 349 350 // DisableBinaryDriver forces this test case to run using the legacy test 351 // driver, even if the binary test driver has been enabled. 352 // 353 // Deprecated: This property will be removed in version 2.0.0 of the SDK. 354 DisableBinaryDriver bool 355 } 356 357 // ExternalProvider holds information about third-party providers that should 358 // be downloaded by Terraform as part of running the test step. 359 type ExternalProvider struct { 360 VersionConstraint string // the version constraint for the provider 361 Source string // the provider source 362 } 363 364 // TestStep is a single apply sequence of a test, done within the 365 // context of a state. 366 // 367 // Multiple TestSteps can be sequenced in a Test to allow testing 368 // potentially complex update logic. In general, simply create/destroy 369 // tests will only need one step. 370 type TestStep struct { 371 // ResourceName should be set to the name of the resource 372 // that is being tested. Example: "aws_instance.foo". Various test 373 // modes use this to auto-detect state information. 374 // 375 // This is only required if the test mode settings below say it is 376 // for the mode you're using. 377 ResourceName string 378 379 // PreConfig is called before the Config is applied to perform any per-step 380 // setup that needs to happen. This is called regardless of "test mode" 381 // below. 382 PreConfig func() 383 384 // Taint is a list of resource addresses to taint prior to the execution of 385 // the step. Be sure to only include this at a step where the referenced 386 // address will be present in state, as it will fail the test if the resource 387 // is missing. 388 // 389 // This option is ignored on ImportState tests, and currently only works for 390 // resources in the root module path. 391 Taint []string 392 393 //--------------------------------------------------------------- 394 // Test modes. One of the following groups of settings must be 395 // set to determine what the test step will do. Ideally we would've 396 // used Go interfaces here but there are now hundreds of tests we don't 397 // want to re-type so instead we just determine which step logic 398 // to run based on what settings below are set. 399 //--------------------------------------------------------------- 400 401 //--------------------------------------------------------------- 402 // Plan, Apply testing 403 //--------------------------------------------------------------- 404 405 // Config a string of the configuration to give to Terraform. If this 406 // is set, then the TestCase will execute this step with the same logic 407 // as a `terraform apply`. 408 Config string 409 410 // Check is called after the Config is applied. Use this step to 411 // make your own API calls to check the status of things, and to 412 // inspect the format of the ResourceState itself. 413 // 414 // If an error is returned, the test will fail. In this case, a 415 // destroy plan will still be attempted. 416 // 417 // If this is nil, no check is done on this step. 418 Check TestCheckFunc 419 420 // Destroy will create a destroy plan if set to true. 421 Destroy bool 422 423 // ExpectNonEmptyPlan can be set to true for specific types of tests that are 424 // looking to verify that a diff occurs 425 ExpectNonEmptyPlan bool 426 427 // ExpectError allows the construction of test cases that we expect to fail 428 // with an error. The specified regexp must match against the error for the 429 // test to pass. 430 ExpectError *regexp.Regexp 431 432 // PlanOnly can be set to only run `plan` with this configuration, and not 433 // actually apply it. This is useful for ensuring config changes result in 434 // no-op plans 435 PlanOnly bool 436 437 // PreventDiskCleanup can be set to true for testing terraform modules which 438 // require access to disk at runtime. Note that this will leave files in the 439 // temp folder 440 PreventDiskCleanup bool 441 442 // PreventPostDestroyRefresh can be set to true for cases where data sources 443 // are tested alongside real resources 444 PreventPostDestroyRefresh bool 445 446 // SkipFunc is called before applying config, but after PreConfig 447 // This is useful for defining test steps with platform-dependent checks 448 SkipFunc func() (bool, error) 449 450 //--------------------------------------------------------------- 451 // ImportState testing 452 //--------------------------------------------------------------- 453 454 // ImportState, if true, will test the functionality of ImportState 455 // by importing the resource with ResourceName (must be set) and the 456 // ID of that resource. 457 ImportState bool 458 459 // ImportStateId is the ID to perform an ImportState operation with. 460 // This is optional. If it isn't set, then the resource ID is automatically 461 // determined by inspecting the state for ResourceName's ID. 462 ImportStateId string 463 464 // ImportStateIdPrefix is the prefix added in front of ImportStateId. 465 // This can be useful in complex import cases, where more than one 466 // attribute needs to be passed on as the Import ID. Mainly in cases 467 // where the ID is not known, and a known prefix needs to be added to 468 // the unset ImportStateId field. 469 ImportStateIdPrefix string 470 471 // ImportStateIdFunc is a function that can be used to dynamically generate 472 // the ID for the ImportState tests. It is sent the state, which can be 473 // checked to derive the attributes necessary and generate the string in the 474 // desired format. 475 ImportStateIdFunc ImportStateIdFunc 476 477 // ImportStateCheck checks the results of ImportState. It should be 478 // used to verify that the resulting value of ImportState has the 479 // proper resources, IDs, and attributes. 480 ImportStateCheck ImportStateCheckFunc 481 482 // ImportStateVerify, if true, will also check that the state values 483 // that are finally put into the state after import match for all the 484 // IDs returned by the Import. Note that this checks for strict equality 485 // and does not respect DiffSuppressFunc or CustomizeDiff. 486 // 487 // ImportStateVerifyIgnore is a list of prefixes of fields that should 488 // not be verified to be equal. These can be set to ephemeral fields or 489 // fields that can't be refreshed and don't matter. 490 ImportStateVerify bool 491 ImportStateVerifyIgnore []string 492 493 // provider s is used internally to maintain a reference to the 494 // underlying providers during the tests 495 providers map[string]terraform.ResourceProvider 496 } 497 498 // Set to a file mask in sprintf format where %s is test name 499 const EnvLogPathMask = "TF_LOG_PATH_MASK" 500 501 func LogOutput(t TestT) (logOutput io.Writer, err error) { 502 logOutput = ioutil.Discard 503 504 logLevel := logging.LogLevel() 505 if logLevel == "" { 506 return 507 } 508 509 logOutput = os.Stderr 510 511 if logPath := os.Getenv(logging.EnvLogFile); logPath != "" { 512 var err error 513 logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) 514 if err != nil { 515 return nil, err 516 } 517 } 518 519 if logPathMask := os.Getenv(EnvLogPathMask); logPathMask != "" { 520 // Escape special characters which may appear if we have subtests 521 testName := strings.Replace(t.Name(), "/", "__", -1) 522 523 logPath := fmt.Sprintf(logPathMask, testName) 524 var err error 525 logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) 526 if err != nil { 527 return nil, err 528 } 529 } 530 531 // This was the default since the beginning 532 logOutput = &logutils.LevelFilter{ 533 Levels: logging.ValidLevels, 534 MinLevel: logutils.LogLevel(logLevel), 535 Writer: logOutput, 536 } 537 538 return 539 } 540 541 // ParallelTest performs an acceptance test on a resource, allowing concurrency 542 // with other ParallelTest. 543 // 544 // Tests will fail if they do not properly handle conditions to allow multiple 545 // tests to occur against the same resource or service (e.g. random naming). 546 // All other requirements of the Test function also apply to this function. 547 func ParallelTest(t TestT, c TestCase) { 548 t.Parallel() 549 Test(t, c) 550 } 551 552 // Test performs an acceptance test on a resource. 553 // 554 // Tests are not run unless an environmental variable "TF_ACC" is 555 // set to some non-empty value. This is to avoid test cases surprising 556 // a user by creating real resources. 557 // 558 // Tests will fail unless the verbose flag (`go test -v`, or explicitly 559 // the "-test.v" flag) is set. Because some acceptance tests take quite 560 // long, we require the verbose flag so users are able to see progress 561 // output. 562 func Test(t TestT, c TestCase) { 563 // We only run acceptance tests if an env var is set because they're 564 // slow and generally require some outside configuration. You can opt out 565 // of this with OverrideEnvVar on individual TestCases. 566 if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { 567 t.Skip(fmt.Sprintf( 568 "Acceptance tests skipped unless env '%s' set", 569 TestEnvVar)) 570 return 571 } 572 if v := os.Getenv(TestDisableBinaryTestingFlagEnvVar); v != "" { 573 b, err := strconv.ParseBool(v) 574 if err != nil { 575 t.Error(fmt.Errorf("Error parsing EnvVar %q value %q: %s", TestDisableBinaryTestingFlagEnvVar, v, err)) 576 } 577 578 c.DisableBinaryDriver = b 579 } 580 581 logWriter, err := LogOutput(t) 582 if err != nil { 583 t.Error(fmt.Errorf("error setting up logging: %s", err)) 584 } 585 log.SetOutput(logWriter) 586 587 // We require verbose mode so that the user knows what is going on. 588 if !testTesting && !testing.Verbose() && !c.IsUnitTest { 589 t.Fatal("Acceptance tests must be run with the -v flag on tests") 590 } 591 592 // get instances of all providers, so we can use the individual 593 // resources to shim the state during the tests. 594 providers := make(map[string]terraform.ResourceProvider) 595 for name, pf := range testProviderFactories(c) { 596 p, err := pf() 597 if err != nil { 598 t.Fatal(err) 599 } 600 providers[name] = p 601 } 602 603 if acctest.TestHelper != nil && c.DisableBinaryDriver == false { 604 // auto-configure all providers 605 for _, p := range providers { 606 err = p.Configure(terraform.NewResourceConfigRaw(nil)) 607 if err != nil { 608 t.Fatal(err) 609 } 610 } 611 612 // Run the PreCheck if we have it. 613 // This is done after the auto-configure to allow providers 614 // to override the default auto-configure parameters. 615 if c.PreCheck != nil { 616 c.PreCheck() 617 } 618 619 // inject providers for ImportStateVerify 620 RunNewTest(t.(*testing.T), c, providers) 621 return 622 } else { 623 // run the PreCheck if we have it 624 if c.PreCheck != nil { 625 c.PreCheck() 626 } 627 } 628 629 providerResolver, err := testProviderResolver(c) 630 if err != nil { 631 t.Fatal(err) 632 } 633 634 opts := terraform.ContextOpts{ProviderResolver: providerResolver} 635 636 // A single state variable to track the lifecycle, starting with no state 637 var state *terraform.State 638 639 // Go through each step and run it 640 var idRefreshCheck *terraform.ResourceState 641 idRefresh := c.IDRefreshName != "" 642 errored := false 643 for i, step := range c.Steps { 644 // insert the providers into the step so we can get the resources for 645 // shimming the state 646 step.providers = providers 647 648 var err error 649 log.Printf("[DEBUG] Test: Executing step %d", i) 650 651 if step.SkipFunc != nil { 652 skip, err := step.SkipFunc() 653 if err != nil { 654 t.Fatal(err) 655 } 656 if skip { 657 log.Printf("[WARN] Skipping step %d", i) 658 continue 659 } 660 } 661 662 if step.Config == "" && !step.ImportState { 663 err = fmt.Errorf( 664 "unknown test mode for step. Please see TestStep docs\n\n%#v", 665 step) 666 } else { 667 if step.ImportState { 668 if step.Config == "" { 669 step.Config, err = testProviderConfig(c) 670 if err != nil { 671 t.Fatal("Error setting config for providers: " + err.Error()) 672 } 673 } 674 675 // Can optionally set step.Config in addition to 676 // step.ImportState, to provide config for the import. 677 state, err = testStepImportState(opts, state, step) 678 } else { 679 state, err = testStepConfig(opts, state, step) 680 } 681 } 682 683 // If we expected an error, but did not get one, fail 684 if err == nil && step.ExpectError != nil { 685 errored = true 686 t.Error(fmt.Sprintf( 687 "Step %d, no error received, but expected a match to:\n\n%s\n\n", 688 i, step.ExpectError)) 689 break 690 } 691 692 // If there was an error, exit 693 if err != nil { 694 // Perhaps we expected an error? Check if it matches 695 if step.ExpectError != nil { 696 if !step.ExpectError.MatchString(err.Error()) { 697 errored = true 698 t.Error(fmt.Sprintf( 699 "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", 700 i, err, step.ExpectError)) 701 break 702 } 703 } else { 704 errored = true 705 t.Error(fmt.Sprintf("Step %d error: %s", i, detailedErrorMessage(err))) 706 break 707 } 708 } 709 710 // If we've never checked an id-only refresh and our state isn't 711 // empty, find the first resource and test it. 712 if idRefresh && idRefreshCheck == nil && !state.Empty() { 713 // Find the first non-nil resource in the state 714 for _, m := range state.Modules { 715 if len(m.Resources) > 0 { 716 if v, ok := m.Resources[c.IDRefreshName]; ok { 717 idRefreshCheck = v 718 } 719 720 break 721 } 722 } 723 724 // If we have an instance to check for refreshes, do it 725 // immediately. We do it in the middle of another test 726 // because it shouldn't affect the overall state (refresh 727 // is read-only semantically) and we want to fail early if 728 // this fails. If refresh isn't read-only, then this will have 729 // caught a different bug. 730 if idRefreshCheck != nil { 731 log.Printf( 732 "[WARN] Test: Running ID-only refresh check on %s", 733 idRefreshCheck.Primary.ID) 734 if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { 735 log.Printf("[ERROR] Test: ID-only test failed: %s", err) 736 t.Error(fmt.Sprintf( 737 "[ERROR] Test: ID-only test failed: %s", err)) 738 break 739 } 740 } 741 } 742 } 743 744 // If we never checked an id-only refresh, it is a failure. 745 if idRefresh { 746 if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { 747 t.Error("ID-only refresh check never ran.") 748 } 749 } 750 751 // If we have a state, then run the destroy 752 if state != nil { 753 lastStep := c.Steps[len(c.Steps)-1] 754 destroyStep := TestStep{ 755 Config: lastStep.Config, 756 Check: c.CheckDestroy, 757 Destroy: true, 758 PreventDiskCleanup: lastStep.PreventDiskCleanup, 759 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 760 providers: providers, 761 } 762 763 log.Printf("[WARN] Test: Executing destroy step") 764 state, err := testStep(opts, state, destroyStep) 765 if err != nil { 766 t.Error(fmt.Sprintf( 767 "Error destroying resource! WARNING: Dangling resources\n"+ 768 "may exist. The full state and error is shown below.\n\n"+ 769 "Error: %s\n\nState: %s", 770 err, 771 state)) 772 } 773 } else { 774 log.Printf("[WARN] Skipping destroy test since there is no state.") 775 } 776 } 777 778 // testProviderConfig takes the list of Providers in a TestCase and returns a 779 // config with only empty provider blocks. This is useful for Import, where no 780 // config is provided, but the providers must be defined. 781 func testProviderConfig(c TestCase) (string, error) { 782 var lines []string 783 var requiredProviders []string 784 for p := range c.Providers { 785 lines = append(lines, fmt.Sprintf("provider %q {}\n", p)) 786 } 787 for p, v := range c.ExternalProviders { 788 if _, ok := c.Providers[p]; ok { 789 return "", fmt.Errorf("Provider %q set in both Providers and ExternalProviders for TestCase. Must be set in only one.", p) 790 } 791 if _, ok := c.ProviderFactories[p]; ok { 792 return "", fmt.Errorf("Provider %q set in both ProviderFactories and ExternalProviders for TestCase. Must be set in only one.", p) 793 } 794 lines = append(lines, fmt.Sprintf("provider %q {}\n", p)) 795 var providerBlock string 796 if v.VersionConstraint != "" { 797 providerBlock = fmt.Sprintf("%s\nversion = %q", providerBlock, v.VersionConstraint) 798 } 799 if v.Source != "" { 800 providerBlock = fmt.Sprintf("%s\nsource = %q", providerBlock, v.Source) 801 } 802 if providerBlock != "" { 803 providerBlock = fmt.Sprintf("%s = {%s\n}\n", p, providerBlock) 804 } 805 requiredProviders = append(requiredProviders, providerBlock) 806 } 807 808 if len(requiredProviders) > 0 { 809 lines = append([]string{fmt.Sprintf("terraform {\nrequired_providers {\n%s}\n}\n\n", strings.Join(requiredProviders, ""))}, lines...) 810 } 811 812 return strings.Join(lines, ""), nil 813 } 814 815 // testProviderFactories combines the fixed Providers and 816 // ResourceProviderFactory functions into a single map of 817 // ResourceProviderFactory functions. 818 func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory { 819 ctxProviders := make(map[string]terraform.ResourceProviderFactory) 820 for k, pf := range c.ProviderFactories { 821 ctxProviders[k] = pf 822 } 823 824 // add any fixed providers 825 for k, p := range c.Providers { 826 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 827 } 828 return ctxProviders 829 } 830 831 // testProviderResolver is a helper to build a ResourceProviderResolver 832 // with pre instantiated ResourceProviders, so that we can reset them for the 833 // test, while only calling the factory function once. 834 // Any errors are stored so that they can be returned by the factory in 835 // terraform to match non-test behavior. 836 func testProviderResolver(c TestCase) (providers.Resolver, error) { 837 ctxProviders := testProviderFactories(c) 838 839 // wrap the old provider factories in the test grpc server so they can be 840 // called from terraform. 841 newProviders := make(map[string]providers.Factory) 842 843 for k, pf := range ctxProviders { 844 factory := pf // must copy to ensure each closure sees its own value 845 newProviders[k] = func() (providers.Interface, error) { 846 p, err := factory() 847 if err != nil { 848 return nil, err 849 } 850 851 // The provider is wrapped in a GRPCTestProvider so that it can be 852 // passed back to terraform core as a providers.Interface, rather 853 // than the legacy ResourceProvider. 854 return GRPCTestProvider(p), nil 855 } 856 } 857 858 return providers.ResolverFixed(newProviders), nil 859 } 860 861 // UnitTest is a helper to force the acceptance testing harness to run in the 862 // normal unit test suite. This should only be used for resource that don't 863 // have any external dependencies. 864 func UnitTest(t TestT, c TestCase) { 865 c.IsUnitTest = true 866 Test(t, c) 867 } 868 869 func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { 870 // TODO: We guard by this right now so master doesn't explode. We 871 // need to remove this eventually to make this part of the normal tests. 872 if os.Getenv("TF_ACC_IDONLY") == "" { 873 return nil 874 } 875 876 addr := addrs.Resource{ 877 Mode: addrs.ManagedResourceMode, 878 Type: r.Type, 879 Name: "foo", 880 }.Instance(addrs.NoKey) 881 absAddr := addr.Absolute(addrs.RootModuleInstance) 882 883 // Build the state. The state is just the resource with an ID. There 884 // are no attributes. We only set what is needed to perform a refresh. 885 state := states.NewState() 886 state.RootModule().SetResourceInstanceCurrent( 887 addr, 888 &states.ResourceInstanceObjectSrc{ 889 AttrsFlat: r.Primary.Attributes, 890 Status: states.ObjectReady, 891 }, 892 addrs.ProviderConfig{Type: "placeholder"}.Absolute(addrs.RootModuleInstance), 893 ) 894 895 // Create the config module. We use the full config because Refresh 896 // doesn't have access to it and we may need things like provider 897 // configurations. The initial implementation of id-only checks used 898 // an empty config module, but that caused the aforementioned problems. 899 cfg, err := testConfig(opts, step) 900 if err != nil { 901 return err 902 } 903 904 // Initialize the context 905 opts.Config = cfg 906 opts.State = state 907 ctx, ctxDiags := terraform.NewContext(&opts) 908 if ctxDiags.HasErrors() { 909 return ctxDiags.Err() 910 } 911 if diags := ctx.Validate(); len(diags) > 0 { 912 if diags.HasErrors() { 913 return errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) 914 } 915 916 log.Printf("[WARN] Config warnings:\n%s", diags.Err().Error()) 917 } 918 919 // Refresh! 920 state, refreshDiags := ctx.Refresh() 921 if refreshDiags.HasErrors() { 922 return refreshDiags.Err() 923 } 924 925 // Verify attribute equivalence. 926 actualR := state.ResourceInstance(absAddr) 927 if actualR == nil { 928 return fmt.Errorf("Resource gone!") 929 } 930 if actualR.Current == nil { 931 return fmt.Errorf("Resource has no primary instance") 932 } 933 actual := actualR.Current.AttrsFlat 934 expected := r.Primary.Attributes 935 // Remove fields we're ignoring 936 for _, v := range c.IDRefreshIgnore { 937 for k := range actual { 938 if strings.HasPrefix(k, v) { 939 delete(actual, k) 940 } 941 } 942 for k := range expected { 943 if strings.HasPrefix(k, v) { 944 delete(expected, k) 945 } 946 } 947 } 948 949 if !reflect.DeepEqual(actual, expected) { 950 // Determine only the different attributes 951 for k, v := range expected { 952 if av, ok := actual[k]; ok && v == av { 953 delete(expected, k) 954 delete(actual, k) 955 } 956 } 957 958 spewConf := spew.NewDefaultConfig() 959 spewConf.SortKeys = true 960 return fmt.Errorf( 961 "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 962 "\n\n%s\n\n%s", 963 spewConf.Sdump(actual), spewConf.Sdump(expected)) 964 } 965 966 return nil 967 } 968 969 func testConfig(opts terraform.ContextOpts, step TestStep) (*configs.Config, error) { 970 if step.PreConfig != nil { 971 step.PreConfig() 972 } 973 974 cfgPath, err := ioutil.TempDir("", "tf-test") 975 if err != nil { 976 return nil, fmt.Errorf("Error creating temporary directory for config: %s", err) 977 } 978 979 if step.PreventDiskCleanup { 980 log.Printf("[INFO] Skipping defer os.RemoveAll call") 981 } else { 982 defer os.RemoveAll(cfgPath) 983 } 984 985 // Write the main configuration file 986 err = ioutil.WriteFile(filepath.Join(cfgPath, "main.tf"), []byte(step.Config), os.ModePerm) 987 if err != nil { 988 return nil, fmt.Errorf("Error creating temporary file for config: %s", err) 989 } 990 991 // Create directory for our child modules, if any. 992 modulesDir := filepath.Join(cfgPath, ".modules") 993 err = os.Mkdir(modulesDir, os.ModePerm) 994 if err != nil { 995 return nil, fmt.Errorf("Error creating child modules directory: %s", err) 996 } 997 998 inst := initwd.NewModuleInstaller(modulesDir, nil) 999 _, installDiags := inst.InstallModules(cfgPath, true, initwd.ModuleInstallHooksImpl{}) 1000 if installDiags.HasErrors() { 1001 return nil, installDiags.Err() 1002 } 1003 1004 loader, err := configload.NewLoader(&configload.Config{ 1005 ModulesDir: modulesDir, 1006 }) 1007 if err != nil { 1008 return nil, fmt.Errorf("failed to create config loader: %s", err) 1009 } 1010 1011 config, configDiags := loader.LoadConfig(cfgPath) 1012 if configDiags.HasErrors() { 1013 return nil, configDiags 1014 } 1015 1016 return config, nil 1017 } 1018 1019 func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 1020 if c.ResourceName == "" { 1021 return nil, fmt.Errorf("ResourceName must be set in TestStep") 1022 } 1023 1024 for _, m := range state.Modules { 1025 if len(m.Resources) > 0 { 1026 if v, ok := m.Resources[c.ResourceName]; ok { 1027 return v, nil 1028 } 1029 } 1030 } 1031 1032 return nil, fmt.Errorf( 1033 "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) 1034 } 1035 1036 // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into 1037 // a single TestCheckFunc. 1038 // 1039 // As a user testing their provider, this lets you decompose your checks 1040 // into smaller pieces more easily. 1041 func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 1042 return func(s *terraform.State) error { 1043 for i, f := range fs { 1044 if err := f(s); err != nil { 1045 return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) 1046 } 1047 } 1048 1049 return nil 1050 } 1051 } 1052 1053 // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into 1054 // a single TestCheckFunc. 1055 // 1056 // As a user testing their provider, this lets you decompose your checks 1057 // into smaller pieces more easily. 1058 // 1059 // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the 1060 // TestCheckFuncs and aggregates failures. 1061 func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 1062 return func(s *terraform.State) error { 1063 var result *multierror.Error 1064 1065 for i, f := range fs { 1066 if err := f(s); err != nil { 1067 result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) 1068 } 1069 } 1070 1071 return result.ErrorOrNil() 1072 } 1073 } 1074 1075 // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value 1076 // exists in state for the given name/key combination. It is useful when 1077 // testing that computed values were set, when it is not possible to 1078 // know ahead of time what the values will be. 1079 func TestCheckResourceAttrSet(name, key string) TestCheckFunc { 1080 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1081 is, err := primaryInstanceState(s, name) 1082 if err != nil { 1083 return err 1084 } 1085 1086 return testCheckResourceAttrSet(is, name, key) 1087 }) 1088 } 1089 1090 // TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with 1091 // support for non-root modules 1092 func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { 1093 mpt := addrs.Module(mp).UnkeyedInstanceShim() 1094 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1095 is, err := modulePathPrimaryInstanceState(s, mpt, name) 1096 if err != nil { 1097 return err 1098 } 1099 1100 return testCheckResourceAttrSet(is, name, key) 1101 }) 1102 } 1103 1104 func testCheckResourceAttrSet(is *terraform.InstanceState, name string, key string) error { 1105 if val, ok := is.Attributes[key]; !ok || val == "" { 1106 return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) 1107 } 1108 1109 return nil 1110 } 1111 1112 // TestCheckResourceAttr is a TestCheckFunc which validates 1113 // the value in state for the given name/key combination. 1114 func TestCheckResourceAttr(name, key, value string) TestCheckFunc { 1115 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1116 is, err := primaryInstanceState(s, name) 1117 if err != nil { 1118 return err 1119 } 1120 1121 return testCheckResourceAttr(is, name, key, value) 1122 }) 1123 } 1124 1125 // TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with 1126 // support for non-root modules 1127 func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { 1128 mpt := addrs.Module(mp).UnkeyedInstanceShim() 1129 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1130 is, err := modulePathPrimaryInstanceState(s, mpt, name) 1131 if err != nil { 1132 return err 1133 } 1134 1135 return testCheckResourceAttr(is, name, key, value) 1136 }) 1137 } 1138 1139 func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { 1140 // Empty containers may be elided from the state. 1141 // If the intent here is to check for an empty container, allow the key to 1142 // also be non-existent. 1143 emptyCheck := false 1144 if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { 1145 emptyCheck = true 1146 } 1147 1148 if v, ok := is.Attributes[key]; !ok || v != value { 1149 if emptyCheck && !ok { 1150 return nil 1151 } 1152 1153 if !ok { 1154 return fmt.Errorf("%s: Attribute '%s' not found", name, key) 1155 } 1156 1157 return fmt.Errorf( 1158 "%s: Attribute '%s' expected %#v, got %#v", 1159 name, 1160 key, 1161 value, 1162 v) 1163 } 1164 return nil 1165 } 1166 1167 // TestCheckNoResourceAttr is a TestCheckFunc which ensures that 1168 // NO value exists in state for the given name/key combination. 1169 func TestCheckNoResourceAttr(name, key string) TestCheckFunc { 1170 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1171 is, err := primaryInstanceState(s, name) 1172 if err != nil { 1173 return err 1174 } 1175 1176 return testCheckNoResourceAttr(is, name, key) 1177 }) 1178 } 1179 1180 // TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with 1181 // support for non-root modules 1182 func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { 1183 mpt := addrs.Module(mp).UnkeyedInstanceShim() 1184 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1185 is, err := modulePathPrimaryInstanceState(s, mpt, name) 1186 if err != nil { 1187 return err 1188 } 1189 1190 return testCheckNoResourceAttr(is, name, key) 1191 }) 1192 } 1193 1194 func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { 1195 // Empty containers may sometimes be included in the state. 1196 // If the intent here is to check for an empty container, allow the value to 1197 // also be "0". 1198 emptyCheck := false 1199 if strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%") { 1200 emptyCheck = true 1201 } 1202 1203 val, exists := is.Attributes[key] 1204 if emptyCheck && val == "0" { 1205 return nil 1206 } 1207 1208 if exists { 1209 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) 1210 } 1211 1212 return nil 1213 } 1214 1215 // TestMatchResourceAttr is a TestCheckFunc which checks that the value 1216 // in state for the given name/key combination matches the given regex. 1217 func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { 1218 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1219 is, err := primaryInstanceState(s, name) 1220 if err != nil { 1221 return err 1222 } 1223 1224 return testMatchResourceAttr(is, name, key, r) 1225 }) 1226 } 1227 1228 // TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with 1229 // support for non-root modules 1230 func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { 1231 mpt := addrs.Module(mp).UnkeyedInstanceShim() 1232 return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { 1233 is, err := modulePathPrimaryInstanceState(s, mpt, name) 1234 if err != nil { 1235 return err 1236 } 1237 1238 return testMatchResourceAttr(is, name, key, r) 1239 }) 1240 } 1241 1242 func testMatchResourceAttr(is *terraform.InstanceState, name string, key string, r *regexp.Regexp) error { 1243 if !r.MatchString(is.Attributes[key]) { 1244 return fmt.Errorf( 1245 "%s: Attribute '%s' didn't match %q, got %#v", 1246 name, 1247 key, 1248 r.String(), 1249 is.Attributes[key]) 1250 } 1251 1252 return nil 1253 } 1254 1255 // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the 1256 // value is a pointer so that it can be updated while the test is running. 1257 // It will only be dereferenced at the point this step is run. 1258 func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { 1259 return func(s *terraform.State) error { 1260 return TestCheckResourceAttr(name, key, *value)(s) 1261 } 1262 } 1263 1264 // TestCheckModuleResourceAttrPtr - as per TestCheckResourceAttrPtr but with 1265 // support for non-root modules 1266 func TestCheckModuleResourceAttrPtr(mp []string, name string, key string, value *string) TestCheckFunc { 1267 return func(s *terraform.State) error { 1268 return TestCheckModuleResourceAttr(mp, name, key, *value)(s) 1269 } 1270 } 1271 1272 // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values 1273 // in state for a pair of name/key combinations are equal. 1274 func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { 1275 return checkIfIndexesIntoTypeSetPair(keyFirst, keySecond, func(s *terraform.State) error { 1276 isFirst, err := primaryInstanceState(s, nameFirst) 1277 if err != nil { 1278 return err 1279 } 1280 1281 isSecond, err := primaryInstanceState(s, nameSecond) 1282 if err != nil { 1283 return err 1284 } 1285 1286 return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) 1287 }) 1288 } 1289 1290 // TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with 1291 // support for non-root modules 1292 func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { 1293 mptFirst := addrs.Module(mpFirst).UnkeyedInstanceShim() 1294 mptSecond := addrs.Module(mpSecond).UnkeyedInstanceShim() 1295 return checkIfIndexesIntoTypeSetPair(keyFirst, keySecond, func(s *terraform.State) error { 1296 isFirst, err := modulePathPrimaryInstanceState(s, mptFirst, nameFirst) 1297 if err != nil { 1298 return err 1299 } 1300 1301 isSecond, err := modulePathPrimaryInstanceState(s, mptSecond, nameSecond) 1302 if err != nil { 1303 return err 1304 } 1305 1306 return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) 1307 }) 1308 } 1309 1310 func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { 1311 vFirst, okFirst := isFirst.Attributes[keyFirst] 1312 vSecond, okSecond := isSecond.Attributes[keySecond] 1313 1314 // Container count values of 0 should not be relied upon, and not reliably 1315 // maintained by helper/schema. For the purpose of tests, consider unset and 1316 // 0 to be equal. 1317 if len(keyFirst) > 2 && len(keySecond) > 2 && keyFirst[len(keyFirst)-2:] == keySecond[len(keySecond)-2:] && 1318 (strings.HasSuffix(keyFirst, ".#") || strings.HasSuffix(keyFirst, ".%")) { 1319 // they have the same suffix, and it is a collection count key. 1320 if vFirst == "0" || vFirst == "" { 1321 okFirst = false 1322 } 1323 if vSecond == "0" || vSecond == "" { 1324 okSecond = false 1325 } 1326 } 1327 1328 if okFirst != okSecond { 1329 if !okFirst { 1330 return fmt.Errorf("%s: Attribute %q not set, but %q is set in %s as %q", nameFirst, keyFirst, keySecond, nameSecond, vSecond) 1331 } 1332 return fmt.Errorf("%s: Attribute %q is %q, but %q is not set in %s", nameFirst, keyFirst, vFirst, keySecond, nameSecond) 1333 } 1334 if !(okFirst || okSecond) { 1335 // If they both don't exist then they are equally unset, so that's okay. 1336 return nil 1337 } 1338 1339 if vFirst != vSecond { 1340 return fmt.Errorf( 1341 "%s: Attribute '%s' expected %#v, got %#v", 1342 nameFirst, 1343 keyFirst, 1344 vSecond, 1345 vFirst) 1346 } 1347 1348 return nil 1349 } 1350 1351 // TestCheckOutput checks an output in the Terraform configuration 1352 func TestCheckOutput(name, value string) TestCheckFunc { 1353 return func(s *terraform.State) error { 1354 ms := s.RootModule() 1355 rs, ok := ms.Outputs[name] 1356 if !ok { 1357 return fmt.Errorf("Not found: %s", name) 1358 } 1359 1360 if rs.Value != value { 1361 return fmt.Errorf( 1362 "Output '%s': expected %#v, got %#v", 1363 name, 1364 value, 1365 rs) 1366 } 1367 1368 return nil 1369 } 1370 } 1371 1372 func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { 1373 return func(s *terraform.State) error { 1374 ms := s.RootModule() 1375 rs, ok := ms.Outputs[name] 1376 if !ok { 1377 return fmt.Errorf("Not found: %s", name) 1378 } 1379 1380 if !r.MatchString(rs.Value.(string)) { 1381 return fmt.Errorf( 1382 "Output '%s': %#v didn't match %q", 1383 name, 1384 rs, 1385 r.String()) 1386 } 1387 1388 return nil 1389 } 1390 } 1391 1392 // TestT is the interface used to handle the test lifecycle of a test. 1393 // 1394 // Users should just use a *testing.T object, which implements this. 1395 type TestT interface { 1396 Error(args ...interface{}) 1397 Fatal(args ...interface{}) 1398 Skip(args ...interface{}) 1399 Name() string 1400 Parallel() 1401 } 1402 1403 // This is set to true by unit tests to alter some behavior 1404 var testTesting = false 1405 1406 // modulePrimaryInstanceState returns the instance state for the given resource 1407 // name in a ModuleState 1408 func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, name string) (*terraform.InstanceState, error) { 1409 rs, ok := ms.Resources[name] 1410 if !ok { 1411 return nil, fmt.Errorf("Not found: %s in %s", name, ms.Path) 1412 } 1413 1414 is := rs.Primary 1415 if is == nil { 1416 return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path) 1417 } 1418 1419 return is, nil 1420 } 1421 1422 // modulePathPrimaryInstanceState returns the primary instance state for the 1423 // given resource name in a given module path. 1424 func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, name string) (*terraform.InstanceState, error) { 1425 ms := s.ModuleByPath(mp) 1426 if ms == nil { 1427 return nil, fmt.Errorf("No module found at: %s", mp) 1428 } 1429 1430 return modulePrimaryInstanceState(s, ms, name) 1431 } 1432 1433 // primaryInstanceState returns the primary instance state for the given 1434 // resource name in the root module. 1435 func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { 1436 ms := s.RootModule() 1437 return modulePrimaryInstanceState(s, ms, name) 1438 } 1439 1440 // operationError is a specialized implementation of error used to describe 1441 // failures during one of the several operations performed for a particular 1442 // test case. 1443 type operationError struct { 1444 OpName string 1445 Diags tfdiags.Diagnostics 1446 } 1447 1448 func newOperationError(opName string, diags tfdiags.Diagnostics) error { 1449 return operationError{opName, diags} 1450 } 1451 1452 // Error returns a terse error string containing just the basic diagnostic 1453 // messages, for situations where normal Go error behavior is appropriate. 1454 func (err operationError) Error() string { 1455 return fmt.Sprintf("errors during %s: %s", err.OpName, err.Diags.Err().Error()) 1456 } 1457 1458 // ErrorDetail is like Error except it includes verbosely-rendered diagnostics 1459 // similar to what would come from a normal Terraform run, which include 1460 // additional context not included in Error(). 1461 func (err operationError) ErrorDetail() string { 1462 var buf bytes.Buffer 1463 fmt.Fprintf(&buf, "errors during %s:", err.OpName) 1464 clr := &colorstring.Colorize{Disable: true, Colors: colorstring.DefaultColors} 1465 for _, diag := range err.Diags { 1466 diagStr := format.Diagnostic(diag, nil, clr, 78) 1467 buf.WriteByte('\n') 1468 buf.WriteString(diagStr) 1469 } 1470 return buf.String() 1471 } 1472 1473 // detailedErrorMessage is a helper for calling ErrorDetail on an error if 1474 // it is an operationError or just taking Error otherwise. 1475 func detailedErrorMessage(err error) string { 1476 switch tErr := err.(type) { 1477 case operationError: 1478 return tErr.ErrorDetail() 1479 default: 1480 return err.Error() 1481 } 1482 } 1483 1484 // indexesIntoTypeSet is a heuristic to try and identify if a flatmap style 1485 // string address uses a precalculated TypeSet hash, which are integers and 1486 // typically are large and obviously not a list index 1487 func indexesIntoTypeSet(key string) bool { 1488 for _, part := range strings.Split(key, ".") { 1489 if i, err := strconv.Atoi(part); err == nil && i > 100 { 1490 return true 1491 } 1492 } 1493 return false 1494 } 1495 1496 func checkIfIndexesIntoTypeSet(key string, f TestCheckFunc) TestCheckFunc { 1497 return func(s *terraform.State) error { 1498 err := f(s) 1499 if err != nil && s.IsBinaryDrivenTest && indexesIntoTypeSet(key) { 1500 return fmt.Errorf("Error in test check: %s\nTest check address %q likely indexes into TypeSet\nThis is not possible in V1 of the SDK while using the binary driver\nPlease disable the driver for this TestCase with DisableBinaryDriver: true", err, key) 1501 } 1502 return err 1503 } 1504 } 1505 1506 func checkIfIndexesIntoTypeSetPair(keyFirst, keySecond string, f TestCheckFunc) TestCheckFunc { 1507 return func(s *terraform.State) error { 1508 err := f(s) 1509 if err != nil && s.IsBinaryDrivenTest && (indexesIntoTypeSet(keyFirst) || indexesIntoTypeSet(keySecond)) { 1510 return fmt.Errorf("Error in test check: %s\nTest check address %q or %q likely indexes into TypeSet\nThis is not possible in V1 of the SDK while using the binary driver\nPlease disable the driver for this TestCase with DisableBinaryDriver: true", err, keyFirst, keySecond) 1511 } 1512 return err 1513 } 1514 }