github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/terraform/context_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-version"
    10  	"github.com/hashicorp/terraform/flatmap"
    11  )
    12  
    13  func TestNewContextRequiredVersion(t *testing.T) {
    14  	cases := []struct {
    15  		Name    string
    16  		Module  string
    17  		Version string
    18  		Value   string
    19  		Err     bool
    20  	}{
    21  		{
    22  			"no requirement",
    23  			"",
    24  			"0.1.0",
    25  			"",
    26  			false,
    27  		},
    28  
    29  		{
    30  			"doesn't match",
    31  			"",
    32  			"0.1.0",
    33  			"> 0.6.0",
    34  			true,
    35  		},
    36  
    37  		{
    38  			"matches",
    39  			"",
    40  			"0.7.0",
    41  			"> 0.6.0",
    42  			false,
    43  		},
    44  
    45  		{
    46  			"module matches",
    47  			"context-required-version-module",
    48  			"0.5.0",
    49  			"",
    50  			false,
    51  		},
    52  
    53  		{
    54  			"module doesn't match",
    55  			"context-required-version-module",
    56  			"0.4.0",
    57  			"",
    58  			true,
    59  		},
    60  	}
    61  
    62  	for i, tc := range cases {
    63  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
    64  			// Reset the version for the tests
    65  			old := SemVersion
    66  			SemVersion = version.Must(version.NewVersion(tc.Version))
    67  			defer func() { SemVersion = old }()
    68  
    69  			name := "context-required-version"
    70  			if tc.Module != "" {
    71  				name = tc.Module
    72  			}
    73  			mod := testModule(t, name)
    74  			if tc.Value != "" {
    75  				mod.Config().Terraform.RequiredVersion = tc.Value
    76  			}
    77  			_, err := NewContext(&ContextOpts{
    78  				Module: mod,
    79  			})
    80  			if (err != nil) != tc.Err {
    81  				t.Fatalf("err: %s", err)
    82  			}
    83  			if err != nil {
    84  				return
    85  			}
    86  		})
    87  	}
    88  }
    89  
    90  func TestNewContextState(t *testing.T) {
    91  	cases := map[string]struct {
    92  		Input *ContextOpts
    93  		Err   bool
    94  	}{
    95  		"empty TFVersion": {
    96  			&ContextOpts{
    97  				State: &State{},
    98  			},
    99  			false,
   100  		},
   101  
   102  		"past TFVersion": {
   103  			&ContextOpts{
   104  				State: &State{TFVersion: "0.1.2"},
   105  			},
   106  			false,
   107  		},
   108  
   109  		"equal TFVersion": {
   110  			&ContextOpts{
   111  				State: &State{TFVersion: Version},
   112  			},
   113  			false,
   114  		},
   115  
   116  		"future TFVersion": {
   117  			&ContextOpts{
   118  				State: &State{TFVersion: "99.99.99"},
   119  			},
   120  			true,
   121  		},
   122  
   123  		"future TFVersion, allowed": {
   124  			&ContextOpts{
   125  				State:              &State{TFVersion: "99.99.99"},
   126  				StateFutureAllowed: true,
   127  			},
   128  			false,
   129  		},
   130  	}
   131  
   132  	for k, tc := range cases {
   133  		ctx, err := NewContext(tc.Input)
   134  		if (err != nil) != tc.Err {
   135  			t.Fatalf("%s: err: %s", k, err)
   136  		}
   137  		if err != nil {
   138  			continue
   139  		}
   140  
   141  		// Version should always be set to our current
   142  		if ctx.state.TFVersion != Version {
   143  			t.Fatalf("%s: state not set to current version", k)
   144  		}
   145  	}
   146  }
   147  
   148  func testContext2(t *testing.T, opts *ContextOpts) *Context {
   149  	// Enable the shadow graph
   150  	opts.Shadow = true
   151  
   152  	ctx, err := NewContext(opts)
   153  	if err != nil {
   154  		t.Fatalf("err: %s", err)
   155  	}
   156  
   157  	return ctx
   158  }
   159  
   160  func testDataApplyFn(
   161  	info *InstanceInfo,
   162  	d *InstanceDiff) (*InstanceState, error) {
   163  	return testApplyFn(info, new(InstanceState), d)
   164  }
   165  
   166  func testDataDiffFn(
   167  	info *InstanceInfo,
   168  	c *ResourceConfig) (*InstanceDiff, error) {
   169  	return testDiffFn(info, new(InstanceState), c)
   170  }
   171  
   172  func testApplyFn(
   173  	info *InstanceInfo,
   174  	s *InstanceState,
   175  	d *InstanceDiff) (*InstanceState, error) {
   176  	if d.Destroy {
   177  		return nil, nil
   178  	}
   179  
   180  	id := "foo"
   181  	if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed {
   182  		id = idAttr.New
   183  	}
   184  
   185  	result := &InstanceState{
   186  		ID:         id,
   187  		Attributes: make(map[string]string),
   188  	}
   189  
   190  	// Copy all the prior attributes
   191  	for k, v := range s.Attributes {
   192  		result.Attributes[k] = v
   193  	}
   194  
   195  	if d != nil {
   196  		result = result.MergeDiff(d)
   197  	}
   198  	return result, nil
   199  }
   200  
   201  func testDiffFn(
   202  	info *InstanceInfo,
   203  	s *InstanceState,
   204  	c *ResourceConfig) (*InstanceDiff, error) {
   205  	diff := new(InstanceDiff)
   206  	diff.Attributes = make(map[string]*ResourceAttrDiff)
   207  
   208  	if s != nil {
   209  		diff.DestroyTainted = s.Tainted
   210  	}
   211  
   212  	for k, v := range c.Raw {
   213  		// Ignore __-prefixed keys since they're used for magic
   214  		if k[0] == '_' && k[1] == '_' {
   215  			continue
   216  		}
   217  
   218  		if k == "nil" {
   219  			return nil, nil
   220  		}
   221  
   222  		// This key is used for other purposes
   223  		if k == "compute_value" {
   224  			continue
   225  		}
   226  
   227  		if k == "compute" {
   228  			attrDiff := &ResourceAttrDiff{
   229  				Old:         "",
   230  				New:         "",
   231  				NewComputed: true,
   232  			}
   233  
   234  			if cv, ok := c.Config["compute_value"]; ok {
   235  				if cv.(string) == "1" {
   236  					attrDiff.NewComputed = false
   237  					attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
   238  				}
   239  			}
   240  
   241  			diff.Attributes[v.(string)] = attrDiff
   242  			continue
   243  		}
   244  
   245  		// If this key is not computed, then look it up in the
   246  		// cleaned config.
   247  		found := false
   248  		for _, ck := range c.ComputedKeys {
   249  			if ck == k {
   250  				found = true
   251  				break
   252  			}
   253  		}
   254  		if !found {
   255  			v = c.Config[k]
   256  		}
   257  
   258  		for k, attrDiff := range testFlatAttrDiffs(k, v) {
   259  			if k == "require_new" {
   260  				attrDiff.RequiresNew = true
   261  			}
   262  			if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
   263  				attrDiff.RequiresNew = true
   264  			}
   265  			diff.Attributes[k] = attrDiff
   266  		}
   267  	}
   268  
   269  	for _, k := range c.ComputedKeys {
   270  		diff.Attributes[k] = &ResourceAttrDiff{
   271  			Old:         "",
   272  			NewComputed: true,
   273  		}
   274  	}
   275  
   276  	// If we recreate this resource because it's tainted, we keep all attrs
   277  	if !diff.RequiresNew() {
   278  		for k, v := range diff.Attributes {
   279  			if v.NewComputed {
   280  				continue
   281  			}
   282  
   283  			old, ok := s.Attributes[k]
   284  			if !ok {
   285  				continue
   286  			}
   287  
   288  			if old == v.New {
   289  				delete(diff.Attributes, k)
   290  			}
   291  		}
   292  	}
   293  
   294  	if !diff.Empty() {
   295  		diff.Attributes["type"] = &ResourceAttrDiff{
   296  			Old: "",
   297  			New: info.Type,
   298  		}
   299  	}
   300  
   301  	return diff, nil
   302  }
   303  
   304  // generate ResourceAttrDiffs for nested data structures in tests
   305  func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
   306  	diffs := make(map[string]*ResourceAttrDiff)
   307  	// check for strings and empty containers first
   308  	switch t := i.(type) {
   309  	case string:
   310  		diffs[k] = &ResourceAttrDiff{New: t}
   311  		return diffs
   312  	case map[string]interface{}:
   313  		if len(t) == 0 {
   314  			diffs[k] = &ResourceAttrDiff{New: ""}
   315  			return diffs
   316  		}
   317  	case []interface{}:
   318  		if len(t) == 0 {
   319  			diffs[k] = &ResourceAttrDiff{New: ""}
   320  			return diffs
   321  		}
   322  	}
   323  
   324  	flat := flatmap.Flatten(map[string]interface{}{k: i})
   325  
   326  	for k, v := range flat {
   327  		attrDiff := &ResourceAttrDiff{
   328  			Old: "",
   329  			New: v,
   330  		}
   331  		diffs[k] = attrDiff
   332  	}
   333  
   334  	return diffs
   335  }
   336  
   337  func testProvider(prefix string) *MockResourceProvider {
   338  	p := new(MockResourceProvider)
   339  	p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
   340  		return s, nil
   341  	}
   342  	p.ResourcesReturn = []ResourceType{
   343  		ResourceType{
   344  			Name: fmt.Sprintf("%s_instance", prefix),
   345  		},
   346  	}
   347  
   348  	return p
   349  }
   350  
   351  func testProvisioner() *MockResourceProvisioner {
   352  	p := new(MockResourceProvisioner)
   353  	return p
   354  }
   355  
   356  func checkStateString(t *testing.T, state *State, expected string) {
   357  	actual := strings.TrimSpace(state.String())
   358  	expected = strings.TrimSpace(expected)
   359  
   360  	if actual != expected {
   361  		t.Fatalf("state does not match! actual:\n%s\n\nexpected:\n%s", actual, expected)
   362  	}
   363  }
   364  
   365  func resourceState(resourceType, resourceID string) *ResourceState {
   366  	return &ResourceState{
   367  		Type: resourceType,
   368  		Primary: &InstanceState{
   369  			ID: resourceID,
   370  		},
   371  	}
   372  }
   373  
   374  // Test helper that gives a function 3 seconds to finish, assumes deadlock and
   375  // fails test if it does not.
   376  func testCheckDeadlock(t *testing.T, f func()) {
   377  	timeout := make(chan bool, 1)
   378  	done := make(chan bool, 1)
   379  	go func() {
   380  		time.Sleep(3 * time.Second)
   381  		timeout <- true
   382  	}()
   383  	go func(f func(), done chan bool) {
   384  		defer func() { done <- true }()
   385  		f()
   386  	}(f, done)
   387  	select {
   388  	case <-timeout:
   389  		t.Fatalf("timed out! probably deadlock")
   390  	case <-done:
   391  		// ok
   392  	}
   393  }
   394  
   395  const testContextGraph = `
   396  root: root
   397  aws_instance.bar
   398    aws_instance.bar -> provider.aws
   399  aws_instance.foo
   400    aws_instance.foo -> provider.aws
   401  provider.aws
   402  root
   403    root -> aws_instance.bar
   404    root -> aws_instance.foo
   405  `
   406  
   407  const testContextRefreshModuleStr = `
   408  aws_instance.web: (tainted)
   409    ID = bar
   410  
   411  module.child:
   412    aws_instance.web:
   413      ID = new
   414  `
   415  
   416  const testContextRefreshOutputStr = `
   417  aws_instance.web:
   418    ID = foo
   419    foo = bar
   420  
   421  Outputs:
   422  
   423  foo = bar
   424  `
   425  
   426  const testContextRefreshOutputPartialStr = `
   427  <no state>
   428  `
   429  
   430  const testContextRefreshTaintedStr = `
   431  aws_instance.web: (tainted)
   432    ID = foo
   433  `