func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
	return func(s *terraform.State) error {
		is, err := primaryInstanceState(s, name)
		if err != nil {
			return err
		}
		if v, ok := is.Attributes[key]; !ok || v != value {
			if !ok {
				return fmt.Errorf("%s: Attribute '%s' not found", name, key)
			}
			return fmt.Errorf(
				"%s: Attribute '%s' expected %#v, got %#v",
				name,
				key,
				value,
				v)
		}
		return nil
	}
}
TestCheckResourceAttr is a TestCheckFunc which validates
the value in state for the given name/key combination.
package resource
import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"testing"
	"github.com/davecgh/go-spew/spew"
	"github.com/hashicorp/go-getter"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/terraform/config/module"
	"github.com/hashicorp/terraform/helper/logging"
	"github.com/hashicorp/terraform/terraform"
)
const TestEnvVar = "TF_ACC"
type TestProvider interface {
	TestReset() error
}
type TestCheckFunc func(*terraform.State) error
type ImportStateCheckFunc func([]*terraform.InstanceState) error
type TestCase struct {
	IsUnitTest bool
	PreCheck func()
	Providers         map[string]terraform.ResourceProvider
	ProviderFactories map[string]terraform.ResourceProviderFactory
	PreventPostDestroyRefresh bool
	CheckDestroy TestCheckFunc
	Steps []TestStep
	IDRefreshName   string
	IDRefreshIgnore []string
}
type TestStep struct {
	ResourceName string
	PreConfig func()
	Config string
	Check TestCheckFunc
	Destroy bool
	ExpectNonEmptyPlan bool
	ExpectError *regexp.Regexp
	PreventPostDestroyRefresh bool
	ImportState bool
	ImportStateId string
	ImportStateCheck ImportStateCheckFunc
	ImportStateVerify       bool
	ImportStateVerifyIgnore []string
}
func Test(t TestT, c TestCase) {
	if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest {
		t.Skip(fmt.Sprintf(
			"Acceptance tests skipped unless env '%s' set",
			TestEnvVar))
		return
	}
	logWriter, err := logging.LogOutput()
	if err != nil {
		t.Error(fmt.Errorf("error setting up logging: %s", err))
	}
	log.SetOutput(logWriter)
	if !testTesting && !testing.Verbose() && !c.IsUnitTest {
		t.Fatal("Acceptance tests must be run with the -v flag on tests")
		return
	}
	if c.PreCheck != nil {
		c.PreCheck()
	}
	ctxProviders, err := testProviderFactories(c)
	if err != nil {
		t.Fatal(err)
	}
	opts := terraform.ContextOpts{Providers: ctxProviders}
	var state *terraform.State
	var idRefreshCheck *terraform.ResourceState
	idRefresh := c.IDRefreshName != ""
	errored := false
	for i, step := range c.Steps {
		var err error
		log.Printf("[WARN] Test: Executing step %d", i)
		if step.Config != "" {
			state, err = testStepConfig(opts, state, step)
		} else if step.ImportState {
			state, err = testStepImportState(opts, state, step)
		} else {
			err = fmt.Errorf(
				"unknown test mode for step. Please see TestStep docs\n\n%#v",
				step)
		}
		if err != nil {
			if step.ExpectError != nil {
				if !step.ExpectError.MatchString(err.Error()) {
					errored = true
					t.Error(fmt.Sprintf(
						"Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n",
						i, err, step.ExpectError))
					break
				}
			} else {
				errored = true
				t.Error(fmt.Sprintf(
					"Step %d error: %s", i, err))
				break
			}
		}
		if idRefresh && idRefreshCheck == nil && !state.Empty() {
			for _, m := range state.Modules {
				if len(m.Resources) > 0 {
					if v, ok := m.Resources[c.IDRefreshName]; ok {
						idRefreshCheck = v
					}
					break
				}
			}
			if idRefreshCheck != nil {
				log.Printf(
					"[WARN] Test: Running ID-only refresh check on %s",
					idRefreshCheck.Primary.ID)
				if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil {
					log.Printf("[ERROR] Test: ID-only test failed: %s", err)
					t.Error(fmt.Sprintf(
						"[ERROR] Test: ID-only test failed: %s", err))
					break
				}
			}
		}
	}
	if idRefresh {
		if !errored && len(c.Steps) > 0 && idRefreshCheck == nil {
			t.Error("ID-only refresh check never ran.")
		}
	}
	if state != nil {
		lastStep := c.Steps[len(c.Steps)-1]
		destroyStep := TestStep{
			Config:                    lastStep.Config,
			Check:                     c.CheckDestroy,
			Destroy:                   true,
			PreventPostDestroyRefresh: c.PreventPostDestroyRefresh,
		}
		log.Printf("[WARN] Test: Executing destroy step")
		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))
		}
	} else {
		log.Printf("[WARN] Skipping destroy test since there is no state.")
	}
}
func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFactory, error) {
	ctxProviders := make(map[string]terraform.ResourceProviderFactory)
	for k, p := range c.Providers {
		ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
	}
	for k, pf := range c.ProviderFactories {
		p, err := pf()
		ctxProviders[k] = func() (terraform.ResourceProvider, error) {
			return p, err
		}
	}
	for k, pf := range ctxProviders {
		p, _ := pf()
		if p, ok := p.(TestProvider); ok {
			err := p.TestReset()
			if err != nil {
				return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err)
			}
		}
	}
	return ctxProviders, nil
}
func UnitTest(t TestT, c TestCase) {
	c.IsUnitTest = true
	Test(t, c)
}
func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error {
	if os.Getenv("TF_ACC_IDONLY") == "" {
		return nil
	}
	name := fmt.Sprintf("%s.foo", r.Type)
	state := terraform.NewState()
	state.RootModule().Resources[name] = &terraform.ResourceState{
		Type: r.Type,
		Primary: &terraform.InstanceState{
			ID: r.Primary.ID,
		},
	}
	mod, err := testModule(opts, step)
	if err != nil {
		return err
	}
	opts.Module = mod
	opts.State = state
	ctx, err := terraform.NewContext(&opts)
	if err != nil {
		return err
	}
	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
		if len(es) > 0 {
			estrs := make([]string, len(es))
			for i, e := range es {
				estrs[i] = e.Error()
			}
			return fmt.Errorf(
				"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
				ws, estrs)
		}
		log.Printf("[WARN] Config warnings: %#v", ws)
	}
	state, err = ctx.Refresh()
	if err != nil {
		return fmt.Errorf("Error refreshing: %s", err)
	}
	actualR := state.RootModule().Resources[name]
	if actualR == nil {
		return fmt.Errorf("Resource gone!")
	}
	if actualR.Primary == nil {
		return fmt.Errorf("Resource has no primary instance")
	}
	actual := actualR.Primary.Attributes
	expected := r.Primary.Attributes
	for _, v := range c.IDRefreshIgnore {
		for k, _ := range actual {
			if strings.HasPrefix(k, v) {
				delete(actual, k)
			}
		}
		for k, _ := range expected {
			if strings.HasPrefix(k, v) {
				delete(expected, k)
			}
		}
	}
	if !reflect.DeepEqual(actual, expected) {
		for k, v := range expected {
			if av, ok := actual[k]; ok && v == av {
				delete(expected, k)
				delete(actual, k)
			}
		}
		spewConf := spew.NewDefaultConfig()
		spewConf.SortKeys = true
		return fmt.Errorf(
			"Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
				"\n\n%s\n\n%s",
			spewConf.Sdump(actual), spewConf.Sdump(expected))
	}
	return nil
}
func testModule(
	opts terraform.ContextOpts,
	step TestStep) (*module.Tree, error) {
	if step.PreConfig != nil {
		step.PreConfig()
	}
	cfgPath, err := ioutil.TempDir("", "tf-test")
	if err != nil {
		return nil, fmt.Errorf(
			"Error creating temporary directory for config: %s", err)
	}
	defer os.RemoveAll(cfgPath)
	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
	if err != nil {
		return nil, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
	cfgF.Close()
	if err != nil {
		return nil, fmt.Errorf(
			"Error creating temporary file for config: %s", err)
	}
	mod, err := module.NewTreeModule("", cfgPath)
	if err != nil {
		return nil, fmt.Errorf(
			"Error loading configuration: %s", err)
	}
	modStorage := &getter.FolderStorage{
		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
	}
	err = mod.Load(modStorage, module.GetModeGet)
	if err != nil {
		return nil, fmt.Errorf("Error downloading modules: %s", err)
	}
	return mod, nil
}
func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) {
	if c.ResourceName == "" {
		return nil, fmt.Errorf("ResourceName must be set in TestStep")
	}
	for _, m := range state.Modules {
		if len(m.Resources) > 0 {
			if v, ok := m.Resources[c.ResourceName]; ok {
				return v, nil
			}
		}
	}
	return nil, fmt.Errorf(
		"Resource specified by ResourceName couldn't be found: %s", c.ResourceName)
}
func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		for i, f := range fs {
			if err := f(s); err != nil {
				return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)
			}
		}
		return nil
	}
}
func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
	return func(s *terraform.State) error {
		var result *multierror.Error
		for i, f := range fs {
			if err := f(s); err != nil {
				result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err))
			}
		}
		return result.ErrorOrNil()
	}
}
func TestCheckResourceAttrSet(name, key string) TestCheckFunc {
	return func(s *terraform.State) error {
		is, err := primaryInstanceState(s, name)
		if err != nil {
			return err
		}
		if val, ok := is.Attributes[key]; ok && val != "" {
			return nil
		}
		return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key)
	}
}