From 3a79a1ca1a465071aefcf452f5161a20d0c71df3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 09:46:21 -0700 Subject: [PATCH 1/9] terraform: add ResourceProviderFactoryFixed --- terraform/resource_provider.go | 8 ++++++++ terraform/resource_provider_test.go | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index 88f0eeac2..39942d8a2 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -69,6 +69,14 @@ type ResourceType struct { // of a resource provider. type ResourceProviderFactory func() (ResourceProvider, error) +// ResourceProviderFactoryFixed is a helper that creates a +// ResourceProviderFactory that just returns some fixed provider. +func ResourceProviderFactoryFixed(p ResourceProvider) ResourceProviderFactory { + return func() (ResourceProvider, error) { + return p, nil + } +} + func ProviderSatisfies(p ResourceProvider, n string) bool { for _, rt := range p.Resources() { if rt.Name == n { diff --git a/terraform/resource_provider_test.go b/terraform/resource_provider_test.go index f2952c192..a1b7b40e5 100644 --- a/terraform/resource_provider_test.go +++ b/terraform/resource_provider_test.go @@ -175,3 +175,15 @@ func TestResourceConfig_IsSet_nil(t *testing.T) { t.Fatal("bad") } } + +func TestResourceProviderFactoryFixed(t *testing.T) { + p := new(MockResourceProvider) + var f ResourceProviderFactory = ResourceProviderFactoryFixed(p) + actual, err := f() + if err != nil { + t.Fatalf("err: %s", err) + } + if actual != p { + t.Fatal("should be identical") + } +} From e0fbd48afd9c09fdec9d8f957b3d054424750f3f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 10:20:21 -0700 Subject: [PATCH 2/9] helper/resource: Acceptance test framework --- helper/resource/testing.go | 205 ++++++++++++++++++++++++++++++++ helper/resource/testing_test.go | 163 +++++++++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 helper/resource/testing.go create mode 100644 helper/resource/testing_test.go diff --git a/helper/resource/testing.go b/helper/resource/testing.go new file mode 100644 index 000000000..84a287e68 --- /dev/null +++ b/helper/resource/testing.go @@ -0,0 +1,205 @@ +package resource + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" +) + +const TestEnvVar = "TF_ACC" + +// TestCheckFunc is the callback type used with acceptance tests to check +// the state of a resource. The state passed in is the latest state known, +// or in the case of being after a destroy, it is the last known state when +// it was created. +type TestCheckFunc func(*terraform.State) error + +// TestCase is a single acceptance test case used to test the apply/destroy +// lifecycle of a resource in a specific configuration. +type TestCase struct { + // Provider is the ResourceProvider that will be under test. + Providers map[string]terraform.ResourceProvider + + // CheckDestroy is called after the resource is finally destroyed + // to allow the tester to test that the resource is truly gone. + CheckDestroy TestCheckFunc + + // Steps are the apply sequences done within the context of the + // same state. Each step can have its own check to verify correctness. + Steps []TestStep +} + +// TestStep is a single apply sequence of a test, done within the +// context of a state. +// +// Multiple TestSteps can be sequenced in a Test to allow testing +// potentially complex update logic. In general, simply create/destroy +// tests will only need one step. +type TestStep struct { + // Config a string of the configuration to give to Terraform. + Config string + + // Check is called after the Config is applied. Use this step to + // make your own API calls to check the status of things, and to + // inspect the format of the ResourceState itself. + // + // If an error is returned, the test will fail. In this case, a + // destroy plan will still be attempted. + // + // If this is nil, no check is done on this step. + Check TestCheckFunc + + // Destroy will create a destroy plan if set to true. + Destroy bool +} + +// Test performs an acceptance test on a resource. +// +// Tests are not run unless an environmental variable "TF_ACC" is +// set to some non-empty value. This is to avoid test cases surprising +// a user by creating real resources. +// +// Tests will fail unless the verbose flag (`go test -v`, or explicitly +// the "-test.v" flag) is set. Because some acceptance tests take quite +// long, we require the verbose flag so users are able to see progress +// output. +func Test(t TestT, c TestCase) { + // We only run acceptance tests if an env var is set because they're + // slow and generally require some outside configuration. + if os.Getenv(TestEnvVar) == "" { + t.Skip(fmt.Sprintf( + "Acceptance tests skipped unless env '%s' set", + TestEnvVar)) + return + } + + // We require verbose mode so that the user knows what is going on. + if !testTesting && !testing.Verbose() { + t.Fatal("Acceptance tests must be run with the -v flag on tests") + return + } + + // Build our context options that we can + ctxProviders := make(map[string]terraform.ResourceProviderFactory) + for k, p := range c.Providers { + ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) + } + opts := terraform.ContextOpts{Providers: ctxProviders} + + // A single state variable to track the lifecycle, starting with no state + var state *terraform.State + + // Go through each step and run it + for i, step := range c.Steps { + var err error + state, err = testStep(opts, state, step) + if err != nil { + t.Error(fmt.Sprintf( + "Step %d error: %s", i, err)) + break + } + } + + // If we have a state, then run the destroy + if state != nil { + destroyStep := TestStep{ + Config: c.Steps[len(c.Steps)-1].Config, + Check: c.CheckDestroy, + Destroy: true, + } + + state, err := testStep(opts, state, destroyStep) + if err != nil { + t.Error(fmt.Sprintf( + "Error destroying resource! WARNING: Dangling resources\n"+ + "may exist. The full state and error is shown below.\n\n"+ + "Error: %s\n\nState: %s", + err, + state)) + } + } +} + +func testStep( + opts terraform.ContextOpts, + state *terraform.State, + step TestStep) (*terraform.State, error) { + // Write the configuration + cfgF, err := ioutil.TempFile("", "tf-test") + if err != nil { + return state, fmt.Errorf( + "Error creating temporary file for config: %s", err) + } + cfgPath := cfgF.Name() + ".tf" + cfgF.Close() + os.Remove(cfgF.Name()) + + cfgF, err = os.Create(cfgPath) + if err != nil { + return state, fmt.Errorf( + "Error creating temporary file for config: %s", err) + } + defer os.Remove(cfgPath) + + _, err = io.Copy(cfgF, strings.NewReader(step.Config)) + cfgF.Close() + if err != nil { + return state, fmt.Errorf( + "Error creating temporary file for config: %s", err) + } + + // Parse the configuration + config, err := config.Load(cfgPath) + if err != nil { + return state, fmt.Errorf( + "Error parsing configuration: %s", err) + } + + // Build the context + opts.Config = config + ctx := terraform.NewContext(&opts) + if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { + return state, fmt.Errorf( + "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", + ws, es) + } + + // Plan! + if _, err := ctx.Plan(&terraform.PlanOpts{Destroy: step.Destroy}); err != nil { + return state, fmt.Errorf( + "Error planning: %s", err) + } + + // Apply! + state, err = ctx.Apply() + if err != nil { + return state, fmt.Errorf("Error applying: %s", err) + } + + // Check! Excitement! + if step.Check != nil { + if err = step.Check(state); err != nil { + err = fmt.Errorf("Check failed: %s", err) + } + } + + return state, err +} + +// TestT is the interface used to handle the test lifecycle of a test. +// +// Users should just use a *testing.T object, which implements this. +type TestT interface { + Error(args ...interface{}) + Fatal(args ...interface{}) + Skip(args ...interface{}) +} + +// This is set to true by unit tests to alter some behavior +var testTesting = false diff --git a/helper/resource/testing_test.go b/helper/resource/testing_test.go new file mode 100644 index 000000000..33fc2727d --- /dev/null +++ b/helper/resource/testing_test.go @@ -0,0 +1,163 @@ +package resource + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func init() { + testTesting = true + + if err := os.Setenv(TestEnvVar, "1"); err != nil { + panic(err) + } +} + +func TestTest(t *testing.T) { + mp := testProvider() + mp.ApplyReturn = &terraform.ResourceState{ + ID: "foo", + } + + checkDestroy := false + checkStep := false + + checkDestroyFn := func(*terraform.State) error { + checkDestroy = true + return nil + } + + checkStepFn := func(s *terraform.State) error { + checkStep = true + + rs, ok := s.Resources["test_instance.foo"] + if !ok { + t.Error("test_instance.foo is not present") + return nil + } + if rs.ID != "foo" { + t.Errorf("bad check ID: %s", rs.ID) + } + + return nil + } + + mt := new(mockT) + Test(mt, TestCase{ + Providers: map[string]terraform.ResourceProvider{ + "test": mp, + }, + CheckDestroy: checkDestroyFn, + Steps: []TestStep{ + TestStep{ + Config: testConfigStr, + Check: checkStepFn, + }, + }, + }) + + if mt.failed() { + t.Fatalf("test failed: %s", mt.failMessage()) + } + if !checkStep { + t.Fatal("didn't call check for step") + } + if !checkDestroy { + t.Fatal("didn't call check for destroy") + } +} + +func TestTest_empty(t *testing.T) { + destroyCalled := false + checkDestroyFn := func(*terraform.State) error { + destroyCalled = true + return nil + } + + mt := new(mockT) + Test(mt, TestCase{ + CheckDestroy: checkDestroyFn, + }) + + if mt.failed() { + t.Fatal("test failed") + } + if destroyCalled { + t.Fatal("should not call check destroy if there is no steps") + } +} + +func TestTest_noEnv(t *testing.T) { + // Unset the variable + if err := os.Setenv(TestEnvVar, ""); err != nil { + t.Fatalf("err: %s", err) + } + + mt := new(mockT) + Test(mt, TestCase{}) + + if !mt.SkipCalled { + t.Fatal("skip not called") + } +} + +// mockT implements TestT for testing +type mockT struct { + ErrorCalled bool + ErrorArgs []interface{} + FatalCalled bool + FatalArgs []interface{} + SkipCalled bool + SkipArgs []interface{} + + f bool +} + +func (t *mockT) Error(args ...interface{}) { + t.ErrorCalled = true + t.ErrorArgs = args + t.f = true +} + +func (t *mockT) Fatal(args ...interface{}) { + t.FatalCalled = true + t.FatalArgs = args + t.f = true +} + +func (t *mockT) Skip(args ...interface{}) { + t.SkipCalled = true + t.SkipArgs = args + t.f = true +} + +func (t *mockT) failed() bool { + return t.f +} + +func (t *mockT) failMessage() string { + if t.FatalCalled { + return t.FatalArgs[0].(string) + } else if t.ErrorCalled { + return t.ErrorArgs[0].(string) + } else if t.SkipCalled { + return t.SkipArgs[0].(string) + } + + return "unknown" +} + +func testProvider() *terraform.MockResourceProvider { + mp := new(terraform.MockResourceProvider) + mp.ResourcesReturn = []terraform.ResourceType{ + terraform.ResourceType{Name: "test_instance"}, + } + + return mp +} + +const testConfigStr = ` +resource "test_instance" "foo" {} +` From 55c1bf7f791d2eb0730f2d271a620224976a8f8e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 10:30:41 -0700 Subject: [PATCH 3/9] helper/resource: more tests --- helper/resource/testing.go | 4 +++ helper/resource/testing_test.go | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 84a287e68..48866bce7 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -22,6 +22,9 @@ type TestCheckFunc func(*terraform.State) error // TestCase is a single acceptance test case used to test the apply/destroy // lifecycle of a resource in a specific configuration. +// +// When the destroy plan is executed, the config from the last TestStep +// is used to plan it. type TestCase struct { // Provider is the ResourceProvider that will be under test. Providers map[string]terraform.ResourceProvider @@ -99,6 +102,7 @@ func Test(t TestT, c TestCase) { for i, step := range c.Steps { var err error state, err = testStep(opts, state, step) + println(fmt.Sprintf("FOO: %#v", state)) if err != nil { t.Error(fmt.Sprintf( "Step %d error: %s", i, err)) diff --git a/helper/resource/testing_test.go b/helper/resource/testing_test.go index 33fc2727d..9df8aad13 100644 --- a/helper/resource/testing_test.go +++ b/helper/resource/testing_test.go @@ -1,6 +1,7 @@ package resource import ( + "fmt" "os" "testing" @@ -94,6 +95,7 @@ func TestTest_noEnv(t *testing.T) { if err := os.Setenv(TestEnvVar, ""); err != nil { t.Fatalf("err: %s", err) } + defer os.Setenv(TestEnvVar, "1") mt := new(mockT) Test(mt, TestCase{}) @@ -103,6 +105,47 @@ func TestTest_noEnv(t *testing.T) { } } +func TestTest_stepError(t *testing.T) { + mp := testProvider() + mp.ApplyReturn = &terraform.ResourceState{ + ID: "foo", + } + + checkDestroy := false + + checkDestroyFn := func(*terraform.State) error { + checkDestroy = true + return nil + } + + checkStepFn := func(*terraform.State) error { + return fmt.Errorf("error") + } + + mt := new(mockT) + Test(mt, TestCase{ + Providers: map[string]terraform.ResourceProvider{ + "test": mp, + }, + CheckDestroy: checkDestroyFn, + Steps: []TestStep{ + TestStep{ + Config: testConfigStr, + Check: checkStepFn, + }, + }, + }) + + if !mt.failed() { + t.Fatal("test should've failed") + } + t.Logf("Fail message: %s", mt.failMessage()) + + if !checkDestroy { + t.Fatal("didn't call check for destroy") + } +} + // mockT implements TestT for testing type mockT struct { ErrorCalled bool @@ -151,6 +194,13 @@ func (t *mockT) failMessage() string { func testProvider() *terraform.MockResourceProvider { mp := new(terraform.MockResourceProvider) + mp.DiffReturn = &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "foo": &terraform.ResourceAttrDiff{ + New: "bar", + }, + }, + } mp.ResourcesReturn = []terraform.ResourceType{ terraform.ResourceType{Name: "test_instance"}, } From be82499f3c7ef1b212c337ce00dd2150e7482b14 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 10:31:06 -0700 Subject: [PATCH 4/9] helper/resource: remove debugging stuff --- helper/resource/testing.go | 1 - 1 file changed, 1 deletion(-) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 48866bce7..c28870e82 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -102,7 +102,6 @@ func Test(t TestT, c TestCase) { for i, step := range c.Steps { var err error state, err = testStep(opts, state, step) - println(fmt.Sprintf("FOO: %#v", state)) if err != nil { t.Error(fmt.Sprintf( "Step %d error: %s", i, err)) From 3e4bdb5584543e7b29685afaf94f1e6afd0521f0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 11:13:17 -0700 Subject: [PATCH 5/9] terraform: fix more cases where nil access can happen --- terraform/context.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index 0babb455e..6e2228169 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -271,6 +271,11 @@ func (c *Context) Validate() ([]string, []error) { // the variables. This dynamically discovers the attributes instead of // using a static map[string]string that the genericWalkFn uses. func (c *Context) computeVars(raw *config.RawConfig) error { + // If there isn't a raw configuration, don't do anything + if raw == nil { + return nil + } + // If there are on variables, then we're done if len(raw.Variables) == 0 { return nil @@ -735,14 +740,14 @@ func (c *Context) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { return nil case *GraphNodeResourceProvider: // Interpolate in the variables and configure all the providers - var rc *ResourceConfig + var raw *config.RawConfig if m.Config != nil { - if err := c.computeVars(m.Config.RawConfig); err != nil { - panic(err) - } - rc = NewResourceConfig(m.Config.RawConfig) + raw = m.Config.RawConfig } + rc := NewResourceConfig(raw) + rc.interpolate(c) + for k, p := range m.Providers { log.Printf("[INFO] Configuring provider: %s", k) err := p.Configure(rc) From bc146d21a32dba995203be3f33a93f45000e90d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 11:31:07 -0700 Subject: [PATCH 6/9] helper/resource: persist state, log --- helper/resource/testing.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index c28870e82..079724839 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "os" "strings" "testing" @@ -101,6 +102,7 @@ func Test(t TestT, c TestCase) { // Go through each step and run it for i, step := range c.Steps { var err error + log.Printf("[WARN] Test: Executing step %d", i) state, err = testStep(opts, state, step) if err != nil { t.Error(fmt.Sprintf( @@ -117,6 +119,7 @@ func Test(t TestT, c TestCase) { Destroy: true, } + log.Printf("[WARN] Test: Executing destroy step") state, err := testStep(opts, state, destroyStep) if err != nil { t.Error(fmt.Sprintf( @@ -126,6 +129,8 @@ func Test(t TestT, c TestCase) { err, state)) } + } else { + log.Printf("[WARN] Skipping destroy test since there is no state.") } } @@ -166,6 +171,7 @@ func testStep( // Build the context opts.Config = config + opts.State = state ctx := terraform.NewContext(&opts) if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { return state, fmt.Errorf( From b3fd62beb0d8045015b91fddb6ed6edc41c1666b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 11:31:16 -0700 Subject: [PATCH 7/9] providers/aws: basic VPC test --- builtin/providers/aws/config.go | 5 ++ .../providers/aws/resource_aws_vpc_test.go | 86 +++++++++++++++++++ builtin/providers/aws/resource_provider.go | 20 +++-- .../providers/aws/resource_provider_test.go | 25 ++++++ 4 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 builtin/providers/aws/resource_aws_vpc_test.go diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 6fae6ac10..32b3af623 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -1,6 +1,7 @@ package aws import ( + "os" "strings" "unicode" @@ -36,6 +37,10 @@ func (c *Config) AWSRegion() (aws.Region, error) { return aws.Regions[c.Region], nil } + if v := os.Getenv("AWS_REGION"); v != "" { + return aws.Regions[v], nil + } + md, err := aws.GetMetaData("placement/availability-zone") if err != nil { return aws.Region{}, err diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go new file mode 100644 index 000000000..30c3fe7ab --- /dev/null +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -0,0 +1,86 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/goamz/ec2" +) + +func TestAccVpc(t *testing.T) { + testAccPreCheck(t) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpcConfig, + Check: testAccCheckVpcExists("aws_vpc.foo"), + }, + }, + }) +} + +func testAccCheckVpcDestroy(s *terraform.State) error { + conn := testAccProvider.ec2conn + + for _, rs := range s.Resources { + if rs.Type != "aws_vpc" { + continue + } + + // Try to find the VPC + resp, err := conn.DescribeVpcs([]string{rs.ID}, ec2.NewFilter()) + if err == nil { + if len(resp.VPCs) > 0 { + return fmt.Errorf("VPCs still exist.") + } + + return nil + } + + // Verify the error is what we want + ec2err, ok := err.(*ec2.Error) + if !ok { + return err + } + if ec2err.Code != "InvalidVpcID.NotFound" { + return err + } + } + + return nil +} + +func testAccCheckVpcExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.ID == "" { + return fmt.Errorf("No VPC ID is set") + } + + conn := testAccProvider.ec2conn + resp, err := conn.DescribeVpcs([]string{rs.ID}, ec2.NewFilter()) + if err != nil { + return err + } + if len(resp.VPCs) == 0 { + return fmt.Errorf("VPC not found") + } + + return nil + } +} + +const testAccVpcConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} +` diff --git a/builtin/providers/aws/resource_provider.go b/builtin/providers/aws/resource_provider.go index f5ada7080..38ddbeba7 100644 --- a/builtin/providers/aws/resource_provider.go +++ b/builtin/providers/aws/resource_provider.go @@ -20,7 +20,15 @@ type ResourceProvider struct { } func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { - return nil, nil + v := &config.Validator{ + Optional: []string{ + "access_key", + "secret_key", + "region", + }, + } + + return v.Validate(c) } func (p *ResourceProvider) ValidateResource( @@ -36,24 +44,24 @@ func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error { // Get the auth and region. This can fail if keys/regions were not // specified and we're attempting to use the environment. var errs []error - log.Println("Building AWS auth structure") + log.Println("[INFO] Building AWS auth structure") auth, err := p.Config.AWSAuth() if err != nil { errs = append(errs, err) } - log.Println("Building AWS region structure") + log.Println("[INFO] Building AWS region structure") region, err := p.Config.AWSRegion() if err != nil { errs = append(errs, err) } if len(errs) == 0 { - log.Println("Initializing EC2 connection") + log.Println("[INFO] Initializing EC2 connection") p.ec2conn = ec2.New(auth, region) - log.Println("Initializing ELB connection") + log.Println("[INFO] Initializing ELB connection") p.elbconn = elb.New(auth, region) - log.Println("Initializing AutoScaling connection") + log.Println("[INFO] Initializing AutoScaling connection") p.autoscalingconn = autoscaling.New(auth, region) } diff --git a/builtin/providers/aws/resource_provider_test.go b/builtin/providers/aws/resource_provider_test.go index 433e94b2e..fddcb3a39 100644 --- a/builtin/providers/aws/resource_provider_test.go +++ b/builtin/providers/aws/resource_provider_test.go @@ -1,6 +1,8 @@ package aws import ( + "os" + "log" "reflect" "testing" @@ -8,6 +10,16 @@ import ( "github.com/hashicorp/terraform/terraform" ) +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *ResourceProvider + +func init() { + testAccProvider = new(ResourceProvider) + testAccProviders = map[string]terraform.ResourceProvider{ + "aws": testAccProvider, + } +} + func TestResourceProvider_impl(t *testing.T) { var _ terraform.ResourceProvider = new(ResourceProvider) } @@ -41,3 +53,16 @@ func TestResourceProvider_Configure(t *testing.T) { t.Fatalf("bad: %#v", rp.Config) } } + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("AWS_ACCESS_KEY"); v == "" { + t.Fatal("AWS_ACCESS_KEY must be set for acceptance tests") + } + if v := os.Getenv("AWS_SECRET_KEY"); v == "" { + t.Fatal("AWS_SECRET_KEY must be set for acceptance tests") + } + if v := os.Getenv("AWS_REGION"); v == "" { + log.Println("[INFO] Test: Using us-west-2 as test region") + os.Setenv("AWS_REGION", "us-west-2") + } +} From 9d3adc07caba9af19aee5e1d3894cf09b8c78de8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 11:41:18 -0700 Subject: [PATCH 8/9] Add "testacc" target for easy acceptance testing --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index 2f5d93a61..146f8cdb5 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,13 @@ libucl: vendor/libucl/$(LIBUCL_NAME) test: libucl go test $(TEST) $(TESTARGS) +testacc: libucl + @if [ "$(TEST)" = "./..." ]; then \ + echo "ERROR: Set TEST to a specific package"; \ + exit 1; \ + fi + TF_ACC=1 go test $(TEST) -v $(TESTARGS) + testrace: libucl go test -race $(TEST) $(TESTARGS) From 701634ad5ccd9a5bb3de2c9ecfa6b0b798c79ec7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Jul 2014 11:43:32 -0700 Subject: [PATCH 9/9] Makefile --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 146f8cdb5..2f456b1ca 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ CGO_CFLAGS:=-I$(CURDIR)/vendor/libucl/include CGO_LDFLAGS:=-L$(CURDIR)/vendor/libucl LIBUCL_NAME=libucl.a TEST?=./... -TESTARGS?=-timeout=5s # Windows-only ifeq ($(OS), Windows_NT) @@ -23,7 +22,7 @@ dev: libucl libucl: vendor/libucl/$(LIBUCL_NAME) test: libucl - go test $(TEST) $(TESTARGS) + go test $(TEST) $(TESTARGS) -timeout=10s testacc: libucl @if [ "$(TEST)" = "./..." ]; then \