github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/helper/resource/testing_test.go (about)

     1  package resource
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"regexp"
     8  	"strings"
     9  	"sync"
    10  	"sync/atomic"
    11  	"testing"
    12  
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/hashicorp/terraform/terraform"
    15  )
    16  
    17  func init() {
    18  	testTesting = true
    19  
    20  	// TODO: Remove when we remove the guard on id checks
    21  	if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil {
    22  		panic(err)
    23  	}
    24  
    25  	if err := os.Setenv(TestEnvVar, "1"); err != nil {
    26  		panic(err)
    27  	}
    28  }
    29  
    30  // wrap the mock provider to implement TestProvider
    31  type resetProvider struct {
    32  	*terraform.MockResourceProvider
    33  	mu              sync.Mutex
    34  	TestResetCalled bool
    35  	TestResetError  error
    36  }
    37  
    38  func (p *resetProvider) TestReset() error {
    39  	p.mu.Lock()
    40  	defer p.mu.Unlock()
    41  	p.TestResetCalled = true
    42  	return p.TestResetError
    43  }
    44  
    45  func TestTest(t *testing.T) {
    46  	mp := &resetProvider{
    47  		MockResourceProvider: testProvider(),
    48  	}
    49  
    50  	mp.DiffReturn = nil
    51  
    52  	mp.ApplyFn = func(
    53  		info *terraform.InstanceInfo,
    54  		state *terraform.InstanceState,
    55  		diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
    56  		if !diff.Destroy {
    57  			return &terraform.InstanceState{
    58  				ID: "foo",
    59  			}, nil
    60  		}
    61  
    62  		return nil, nil
    63  	}
    64  
    65  	var refreshCount int32
    66  	mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
    67  		atomic.AddInt32(&refreshCount, 1)
    68  		return &terraform.InstanceState{ID: "foo"}, nil
    69  	}
    70  
    71  	checkDestroy := false
    72  	checkStep := false
    73  
    74  	checkDestroyFn := func(*terraform.State) error {
    75  		checkDestroy = true
    76  		return nil
    77  	}
    78  
    79  	checkStepFn := func(s *terraform.State) error {
    80  		checkStep = true
    81  
    82  		rs, ok := s.RootModule().Resources["test_instance.foo"]
    83  		if !ok {
    84  			t.Error("test_instance.foo is not present")
    85  			return nil
    86  		}
    87  		is := rs.Primary
    88  		if is.ID != "foo" {
    89  			t.Errorf("bad check ID: %s", is.ID)
    90  		}
    91  
    92  		return nil
    93  	}
    94  
    95  	mt := new(mockT)
    96  	Test(mt, TestCase{
    97  		Providers: map[string]terraform.ResourceProvider{
    98  			"test": mp,
    99  		},
   100  		CheckDestroy: checkDestroyFn,
   101  		Steps: []TestStep{
   102  			TestStep{
   103  				Config: testConfigStr,
   104  				Check:  checkStepFn,
   105  			},
   106  		},
   107  	})
   108  
   109  	if mt.failed() {
   110  		t.Fatalf("test failed: %s", mt.failMessage())
   111  	}
   112  	if !checkStep {
   113  		t.Fatal("didn't call check for step")
   114  	}
   115  	if !checkDestroy {
   116  		t.Fatal("didn't call check for destroy")
   117  	}
   118  	if !mp.TestResetCalled {
   119  		t.Fatal("didn't call TestReset")
   120  	}
   121  }
   122  
   123  func TestTest_plan_only(t *testing.T) {
   124  	mp := testProvider()
   125  	mp.ApplyReturn = &terraform.InstanceState{
   126  		ID: "foo",
   127  	}
   128  
   129  	checkDestroy := false
   130  
   131  	checkDestroyFn := func(*terraform.State) error {
   132  		checkDestroy = true
   133  		return nil
   134  	}
   135  
   136  	mt := new(mockT)
   137  	Test(mt, TestCase{
   138  		Providers: map[string]terraform.ResourceProvider{
   139  			"test": mp,
   140  		},
   141  		CheckDestroy: checkDestroyFn,
   142  		Steps: []TestStep{
   143  			TestStep{
   144  				Config:             testConfigStr,
   145  				PlanOnly:           true,
   146  				ExpectNonEmptyPlan: false,
   147  			},
   148  		},
   149  	})
   150  
   151  	if !mt.failed() {
   152  		t.Fatal("test should've failed")
   153  	}
   154  
   155  	expected := `Step 0 error: After applying this step, the plan was not empty:
   156  
   157  DIFF:
   158  
   159  CREATE: test_instance.foo
   160    foo: "" => "bar"
   161  
   162  STATE:
   163  
   164  <no state>`
   165  
   166  	if mt.failMessage() != expected {
   167  		t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage())
   168  	}
   169  
   170  	if !checkDestroy {
   171  		t.Fatal("didn't call check for destroy")
   172  	}
   173  }
   174  
   175  func TestTest_idRefresh(t *testing.T) {
   176  	// Refresh count should be 3:
   177  	//   1.) initial Ref/Plan/Apply
   178  	//   2.) post Ref/Plan/Apply for plan-check
   179  	//   3.) id refresh check
   180  	var expectedRefresh int32 = 3
   181  
   182  	mp := testProvider()
   183  	mp.DiffReturn = nil
   184  
   185  	mp.ApplyFn = func(
   186  		info *terraform.InstanceInfo,
   187  		state *terraform.InstanceState,
   188  		diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   189  		if !diff.Destroy {
   190  			return &terraform.InstanceState{
   191  				ID: "foo",
   192  			}, nil
   193  		}
   194  
   195  		return nil, nil
   196  	}
   197  
   198  	var refreshCount int32
   199  	mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
   200  		atomic.AddInt32(&refreshCount, 1)
   201  		return &terraform.InstanceState{ID: "foo"}, nil
   202  	}
   203  
   204  	mt := new(mockT)
   205  	Test(mt, TestCase{
   206  		IDRefreshName: "test_instance.foo",
   207  		Providers: map[string]terraform.ResourceProvider{
   208  			"test": mp,
   209  		},
   210  		Steps: []TestStep{
   211  			TestStep{
   212  				Config: testConfigStr,
   213  			},
   214  		},
   215  	})
   216  
   217  	if mt.failed() {
   218  		t.Fatalf("test failed: %s", mt.failMessage())
   219  	}
   220  
   221  	// See declaration of expectedRefresh for why that number
   222  	if refreshCount != expectedRefresh {
   223  		t.Fatalf("bad refresh count: %d", refreshCount)
   224  	}
   225  }
   226  
   227  func TestTest_idRefreshCustomName(t *testing.T) {
   228  	// Refresh count should be 3:
   229  	//   1.) initial Ref/Plan/Apply
   230  	//   2.) post Ref/Plan/Apply for plan-check
   231  	//   3.) id refresh check
   232  	var expectedRefresh int32 = 3
   233  
   234  	mp := testProvider()
   235  	mp.DiffReturn = nil
   236  
   237  	mp.ApplyFn = func(
   238  		info *terraform.InstanceInfo,
   239  		state *terraform.InstanceState,
   240  		diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   241  		if !diff.Destroy {
   242  			return &terraform.InstanceState{
   243  				ID: "foo",
   244  			}, nil
   245  		}
   246  
   247  		return nil, nil
   248  	}
   249  
   250  	var refreshCount int32
   251  	mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
   252  		atomic.AddInt32(&refreshCount, 1)
   253  		return &terraform.InstanceState{ID: "foo"}, nil
   254  	}
   255  
   256  	mt := new(mockT)
   257  	Test(mt, TestCase{
   258  		IDRefreshName: "test_instance.foo",
   259  		Providers: map[string]terraform.ResourceProvider{
   260  			"test": mp,
   261  		},
   262  		Steps: []TestStep{
   263  			TestStep{
   264  				Config: testConfigStr,
   265  			},
   266  		},
   267  	})
   268  
   269  	if mt.failed() {
   270  		t.Fatalf("test failed: %s", mt.failMessage())
   271  	}
   272  
   273  	// See declaration of expectedRefresh for why that number
   274  	if refreshCount != expectedRefresh {
   275  		t.Fatalf("bad refresh count: %d", refreshCount)
   276  	}
   277  }
   278  
   279  func TestTest_idRefreshFail(t *testing.T) {
   280  	// Refresh count should be 3:
   281  	//   1.) initial Ref/Plan/Apply
   282  	//   2.) post Ref/Plan/Apply for plan-check
   283  	//   3.) id refresh check
   284  	var expectedRefresh int32 = 3
   285  
   286  	mp := testProvider()
   287  	mp.DiffReturn = nil
   288  
   289  	mp.ApplyFn = func(
   290  		info *terraform.InstanceInfo,
   291  		state *terraform.InstanceState,
   292  		diff *terraform.InstanceDiff) (*terraform.InstanceState, error) {
   293  		if !diff.Destroy {
   294  			return &terraform.InstanceState{
   295  				ID: "foo",
   296  			}, nil
   297  		}
   298  
   299  		return nil, nil
   300  	}
   301  
   302  	var refreshCount int32
   303  	mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) {
   304  		atomic.AddInt32(&refreshCount, 1)
   305  		if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 {
   306  			return &terraform.InstanceState{
   307  				ID:         "foo",
   308  				Attributes: map[string]string{"foo": "bar"},
   309  			}, nil
   310  		} else if atomic.LoadInt32(&refreshCount) < expectedRefresh {
   311  			return &terraform.InstanceState{ID: "foo"}, nil
   312  		} else {
   313  			return nil, nil
   314  		}
   315  	}
   316  
   317  	mt := new(mockT)
   318  	Test(mt, TestCase{
   319  		IDRefreshName: "test_instance.foo",
   320  		Providers: map[string]terraform.ResourceProvider{
   321  			"test": mp,
   322  		},
   323  		Steps: []TestStep{
   324  			TestStep{
   325  				Config: testConfigStr,
   326  			},
   327  		},
   328  	})
   329  
   330  	if !mt.failed() {
   331  		t.Fatal("test didn't fail")
   332  	}
   333  	t.Logf("failure reason: %s", mt.failMessage())
   334  
   335  	// See declaration of expectedRefresh for why that number
   336  	if refreshCount != expectedRefresh {
   337  		t.Fatalf("bad refresh count: %d", refreshCount)
   338  	}
   339  }
   340  
   341  func TestTest_empty(t *testing.T) {
   342  	destroyCalled := false
   343  	checkDestroyFn := func(*terraform.State) error {
   344  		destroyCalled = true
   345  		return nil
   346  	}
   347  
   348  	mt := new(mockT)
   349  	Test(mt, TestCase{
   350  		CheckDestroy: checkDestroyFn,
   351  	})
   352  
   353  	if mt.failed() {
   354  		t.Fatal("test failed")
   355  	}
   356  	if destroyCalled {
   357  		t.Fatal("should not call check destroy if there is no steps")
   358  	}
   359  }
   360  
   361  func TestTest_noEnv(t *testing.T) {
   362  	// Unset the variable
   363  	if err := os.Setenv(TestEnvVar, ""); err != nil {
   364  		t.Fatalf("err: %s", err)
   365  	}
   366  	defer os.Setenv(TestEnvVar, "1")
   367  
   368  	mt := new(mockT)
   369  	Test(mt, TestCase{})
   370  
   371  	if !mt.SkipCalled {
   372  		t.Fatal("skip not called")
   373  	}
   374  }
   375  
   376  func TestTest_preCheck(t *testing.T) {
   377  	called := false
   378  
   379  	mt := new(mockT)
   380  	Test(mt, TestCase{
   381  		PreCheck: func() { called = true },
   382  	})
   383  
   384  	if !called {
   385  		t.Fatal("precheck should be called")
   386  	}
   387  }
   388  
   389  func TestTest_stepError(t *testing.T) {
   390  	mp := testProvider()
   391  	mp.ApplyReturn = &terraform.InstanceState{
   392  		ID: "foo",
   393  	}
   394  
   395  	checkDestroy := false
   396  
   397  	checkDestroyFn := func(*terraform.State) error {
   398  		checkDestroy = true
   399  		return nil
   400  	}
   401  
   402  	checkStepFn := func(*terraform.State) error {
   403  		return fmt.Errorf("error")
   404  	}
   405  
   406  	mt := new(mockT)
   407  	Test(mt, TestCase{
   408  		Providers: map[string]terraform.ResourceProvider{
   409  			"test": mp,
   410  		},
   411  		CheckDestroy: checkDestroyFn,
   412  		Steps: []TestStep{
   413  			TestStep{
   414  				Config: testConfigStr,
   415  				Check:  checkStepFn,
   416  			},
   417  		},
   418  	})
   419  
   420  	if !mt.failed() {
   421  		t.Fatal("test should've failed")
   422  	}
   423  	expected := "Step 0 error: Check failed: error"
   424  	if mt.failMessage() != expected {
   425  		t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage())
   426  	}
   427  
   428  	if !checkDestroy {
   429  		t.Fatal("didn't call check for destroy")
   430  	}
   431  }
   432  
   433  func TestTest_factoryError(t *testing.T) {
   434  	resourceFactoryError := fmt.Errorf("resource factory error")
   435  
   436  	factory := func() (terraform.ResourceProvider, error) {
   437  		return nil, resourceFactoryError
   438  	}
   439  
   440  	mt := new(mockT)
   441  	Test(mt, TestCase{
   442  		ProviderFactories: map[string]terraform.ResourceProviderFactory{
   443  			"test": factory,
   444  		},
   445  		Steps: []TestStep{
   446  			TestStep{
   447  				ExpectError: regexp.MustCompile("resource factory error"),
   448  			},
   449  		},
   450  	})
   451  
   452  	if !mt.failed() {
   453  		t.Fatal("test should've failed")
   454  	}
   455  }
   456  
   457  func TestTest_resetError(t *testing.T) {
   458  	mp := &resetProvider{
   459  		MockResourceProvider: testProvider(),
   460  		TestResetError:       fmt.Errorf("provider reset error"),
   461  	}
   462  
   463  	mt := new(mockT)
   464  	Test(mt, TestCase{
   465  		Providers: map[string]terraform.ResourceProvider{
   466  			"test": mp,
   467  		},
   468  		Steps: []TestStep{
   469  			TestStep{
   470  				ExpectError: regexp.MustCompile("provider reset error"),
   471  			},
   472  		},
   473  	})
   474  
   475  	if !mt.failed() {
   476  		t.Fatal("test should've failed")
   477  	}
   478  }
   479  
   480  func TestComposeAggregateTestCheckFunc(t *testing.T) {
   481  	check1 := func(s *terraform.State) error {
   482  		return errors.New("Error 1")
   483  	}
   484  
   485  	check2 := func(s *terraform.State) error {
   486  		return errors.New("Error 2")
   487  	}
   488  
   489  	f := ComposeAggregateTestCheckFunc(check1, check2)
   490  	err := f(nil)
   491  	if err == nil {
   492  		t.Fatalf("Expected errors")
   493  	}
   494  
   495  	multi := err.(*multierror.Error)
   496  	if !strings.Contains(multi.Errors[0].Error(), "Error 1") {
   497  		t.Fatalf("Expected Error 1, Got %s", multi.Errors[0])
   498  	}
   499  	if !strings.Contains(multi.Errors[1].Error(), "Error 2") {
   500  		t.Fatalf("Expected Error 2, Got %s", multi.Errors[1])
   501  	}
   502  }
   503  
   504  func TestComposeTestCheckFunc(t *testing.T) {
   505  	cases := []struct {
   506  		F      []TestCheckFunc
   507  		Result string
   508  	}{
   509  		{
   510  			F: []TestCheckFunc{
   511  				func(*terraform.State) error { return nil },
   512  			},
   513  			Result: "",
   514  		},
   515  
   516  		{
   517  			F: []TestCheckFunc{
   518  				func(*terraform.State) error {
   519  					return fmt.Errorf("error")
   520  				},
   521  				func(*terraform.State) error { return nil },
   522  			},
   523  			Result: "Check 1/2 error: error",
   524  		},
   525  
   526  		{
   527  			F: []TestCheckFunc{
   528  				func(*terraform.State) error { return nil },
   529  				func(*terraform.State) error {
   530  					return fmt.Errorf("error")
   531  				},
   532  			},
   533  			Result: "Check 2/2 error: error",
   534  		},
   535  
   536  		{
   537  			F: []TestCheckFunc{
   538  				func(*terraform.State) error { return nil },
   539  				func(*terraform.State) error { return nil },
   540  			},
   541  			Result: "",
   542  		},
   543  	}
   544  
   545  	for i, tc := range cases {
   546  		f := ComposeTestCheckFunc(tc.F...)
   547  		err := f(nil)
   548  		if err == nil {
   549  			err = fmt.Errorf("")
   550  		}
   551  		if tc.Result != err.Error() {
   552  			t.Fatalf("Case %d bad: %s", i, err)
   553  		}
   554  	}
   555  }
   556  
   557  // mockT implements TestT for testing
   558  type mockT struct {
   559  	ErrorCalled bool
   560  	ErrorArgs   []interface{}
   561  	FatalCalled bool
   562  	FatalArgs   []interface{}
   563  	SkipCalled  bool
   564  	SkipArgs    []interface{}
   565  
   566  	f bool
   567  }
   568  
   569  func (t *mockT) Error(args ...interface{}) {
   570  	t.ErrorCalled = true
   571  	t.ErrorArgs = args
   572  	t.f = true
   573  }
   574  
   575  func (t *mockT) Fatal(args ...interface{}) {
   576  	t.FatalCalled = true
   577  	t.FatalArgs = args
   578  	t.f = true
   579  }
   580  
   581  func (t *mockT) Skip(args ...interface{}) {
   582  	t.SkipCalled = true
   583  	t.SkipArgs = args
   584  	t.f = true
   585  }
   586  
   587  func (t *mockT) failed() bool {
   588  	return t.f
   589  }
   590  
   591  func (t *mockT) failMessage() string {
   592  	if t.FatalCalled {
   593  		return t.FatalArgs[0].(string)
   594  	} else if t.ErrorCalled {
   595  		return t.ErrorArgs[0].(string)
   596  	} else if t.SkipCalled {
   597  		return t.SkipArgs[0].(string)
   598  	}
   599  
   600  	return "unknown"
   601  }
   602  
   603  func testProvider() *terraform.MockResourceProvider {
   604  	mp := new(terraform.MockResourceProvider)
   605  	mp.DiffReturn = &terraform.InstanceDiff{
   606  		Attributes: map[string]*terraform.ResourceAttrDiff{
   607  			"foo": &terraform.ResourceAttrDiff{
   608  				New: "bar",
   609  			},
   610  		},
   611  	}
   612  	mp.ResourcesReturn = []terraform.ResourceType{
   613  		terraform.ResourceType{Name: "test_instance"},
   614  	}
   615  
   616  	return mp
   617  }
   618  
   619  const testConfigStr = `
   620  resource "test_instance" "foo" {}
   621  `