github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/context_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"github.com/google/go-cmp/cmp/cmpopts"
    17  	"github.com/hashicorp/go-version"
    18  	"github.com/hashicorp/terraform/internal/addrs"
    19  	"github.com/hashicorp/terraform/internal/configs"
    20  	"github.com/hashicorp/terraform/internal/configs/configload"
    21  	"github.com/hashicorp/terraform/internal/configs/configschema"
    22  	"github.com/hashicorp/terraform/internal/configs/hcl2shim"
    23  	"github.com/hashicorp/terraform/internal/depsfile"
    24  	"github.com/hashicorp/terraform/internal/plans"
    25  	"github.com/hashicorp/terraform/internal/plans/planfile"
    26  	"github.com/hashicorp/terraform/internal/providers"
    27  	"github.com/hashicorp/terraform/internal/provisioners"
    28  	"github.com/hashicorp/terraform/internal/states"
    29  	"github.com/hashicorp/terraform/internal/states/statefile"
    30  	"github.com/hashicorp/terraform/internal/tfdiags"
    31  	tfversion "github.com/hashicorp/terraform/version"
    32  	"github.com/zclconf/go-cty/cty"
    33  )
    34  
    35  var (
    36  	equateEmpty   = cmpopts.EquateEmpty()
    37  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    38  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    39  	valueTrans    = cmp.Transformer("hcl2shim", hcl2shim.ConfigValueFromHCL2)
    40  )
    41  
    42  func TestNewContextRequiredVersion(t *testing.T) {
    43  	cases := []struct {
    44  		Name    string
    45  		Module  string
    46  		Version string
    47  		Value   string
    48  		Err     bool
    49  	}{
    50  		{
    51  			"no requirement",
    52  			"",
    53  			"0.1.0",
    54  			"",
    55  			false,
    56  		},
    57  
    58  		{
    59  			"doesn't match",
    60  			"",
    61  			"0.1.0",
    62  			"> 0.6.0",
    63  			true,
    64  		},
    65  
    66  		{
    67  			"matches",
    68  			"",
    69  			"0.7.0",
    70  			"> 0.6.0",
    71  			false,
    72  		},
    73  
    74  		{
    75  			"module matches",
    76  			"context-required-version-module",
    77  			"0.5.0",
    78  			"",
    79  			false,
    80  		},
    81  
    82  		{
    83  			"module doesn't match",
    84  			"context-required-version-module",
    85  			"0.4.0",
    86  			"",
    87  			true,
    88  		},
    89  	}
    90  
    91  	for i, tc := range cases {
    92  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
    93  			// Reset the version for the tests
    94  			old := tfversion.SemVer
    95  			tfversion.SemVer = version.Must(version.NewVersion(tc.Version))
    96  			defer func() { tfversion.SemVer = old }()
    97  
    98  			name := "context-required-version"
    99  			if tc.Module != "" {
   100  				name = tc.Module
   101  			}
   102  			mod := testModule(t, name)
   103  			if tc.Value != "" {
   104  				constraint, err := version.NewConstraint(tc.Value)
   105  				if err != nil {
   106  					t.Fatalf("can't parse %q as version constraint", tc.Value)
   107  				}
   108  				mod.Module.CoreVersionConstraints = append(mod.Module.CoreVersionConstraints, configs.VersionConstraint{
   109  					Required: constraint,
   110  				})
   111  			}
   112  			c, diags := NewContext(&ContextOpts{})
   113  			if diags.HasErrors() {
   114  				t.Fatalf("unexpected NewContext errors: %s", diags.Err())
   115  			}
   116  
   117  			diags = c.Validate(mod)
   118  			if diags.HasErrors() != tc.Err {
   119  				t.Fatalf("err: %s", diags.Err())
   120  			}
   121  		})
   122  	}
   123  }
   124  
   125  func TestNewContext_lockedDependencies(t *testing.T) {
   126  	// TODO: Remove this test altogether once we've factored out the version
   127  	// and checksum verification to be exclusively the caller's responsibility.
   128  	t.Skip("only one step away from locked dependencies being the caller's responsibility")
   129  
   130  	configBeepGreaterThanOne := `
   131  terraform {
   132    required_providers {
   133      beep = {
   134        source  = "example.com/foo/beep"
   135        version = ">= 1.0.0"
   136      }
   137    }
   138  }
   139  `
   140  	configBeepLessThanOne := `
   141  terraform {
   142    required_providers {
   143      beep = {
   144        source  = "example.com/foo/beep"
   145        version = "< 1.0.0"
   146      }
   147    }
   148  }
   149  `
   150  	configBuiltin := `
   151  terraform {
   152    required_providers {
   153      terraform = {
   154        source = "terraform.io/builtin/terraform"
   155  	}
   156    }
   157  }
   158  `
   159  	locksBeepGreaterThanOne := `
   160  provider "example.com/foo/beep" {
   161  	version     = "1.0.0"
   162  	constraints = ">= 1.0.0"
   163  	hashes = [
   164  		"h1:does-not-match",
   165  	]
   166  }
   167  `
   168  	configBeepBoop := `
   169  terraform {
   170    required_providers {
   171      beep = {
   172        source  = "example.com/foo/beep"
   173        version = "< 1.0.0" # different from locks
   174      }
   175      boop = {
   176        source  = "example.com/foo/boop"
   177        version = ">= 2.0.0"
   178      }
   179    }
   180  }
   181  `
   182  	locksBeepBoop := `
   183  provider "example.com/foo/beep" {
   184  	version     = "1.0.0"
   185  	constraints = ">= 1.0.0"
   186  	hashes = [
   187  		"h1:does-not-match",
   188  	]
   189  }
   190  provider "example.com/foo/boop" {
   191  	version     = "2.3.4"
   192  	constraints = ">= 2.0.0"
   193  	hashes = [
   194  		"h1:does-not-match",
   195  	]
   196  }
   197  `
   198  	beepAddr := addrs.MustParseProviderSourceString("example.com/foo/beep")
   199  	boopAddr := addrs.MustParseProviderSourceString("example.com/foo/boop")
   200  
   201  	testCases := map[string]struct {
   202  		Config       string
   203  		LockFile     string
   204  		DevProviders []addrs.Provider
   205  		WantErr      string
   206  	}{
   207  		"dependencies met": {
   208  			Config:   configBeepGreaterThanOne,
   209  			LockFile: locksBeepGreaterThanOne,
   210  		},
   211  		"no locks given": {
   212  			Config: configBeepGreaterThanOne,
   213  		},
   214  		"builtin provider with empty locks": {
   215  			Config:   configBuiltin,
   216  			LockFile: `# This file is maintained automatically by "terraform init".`,
   217  		},
   218  		"multiple providers, one in development": {
   219  			Config:       configBeepBoop,
   220  			LockFile:     locksBeepBoop,
   221  			DevProviders: []addrs.Provider{beepAddr},
   222  		},
   223  		"development provider with empty locks": {
   224  			Config:       configBeepGreaterThanOne,
   225  			LockFile:     `# This file is maintained automatically by "terraform init".`,
   226  			DevProviders: []addrs.Provider{beepAddr},
   227  		},
   228  		"multiple providers, one in development, one missing": {
   229  			Config:       configBeepBoop,
   230  			LockFile:     locksBeepGreaterThanOne,
   231  			DevProviders: []addrs.Provider{beepAddr},
   232  			WantErr: `Provider requirements cannot be satisfied by locked dependencies: The following required providers are not installed:
   233  
   234  - example.com/foo/boop (>= 2.0.0)
   235  
   236  Please run "terraform init".`,
   237  		},
   238  		"wrong provider version": {
   239  			Config:   configBeepLessThanOne,
   240  			LockFile: locksBeepGreaterThanOne,
   241  			WantErr: `Provider requirements cannot be satisfied by locked dependencies: The following required providers are not installed:
   242  
   243  - example.com/foo/beep (< 1.0.0)
   244  
   245  Please run "terraform init".`,
   246  		},
   247  		"empty locks": {
   248  			Config:   configBeepGreaterThanOne,
   249  			LockFile: `# This file is maintained automatically by "terraform init".`,
   250  			WantErr: `Provider requirements cannot be satisfied by locked dependencies: The following required providers are not installed:
   251  
   252  - example.com/foo/beep (>= 1.0.0)
   253  
   254  Please run "terraform init".`,
   255  		},
   256  	}
   257  	for name, tc := range testCases {
   258  		t.Run(name, func(t *testing.T) {
   259  			var locks *depsfile.Locks
   260  			if tc.LockFile != "" {
   261  				var diags tfdiags.Diagnostics
   262  				locks, diags = depsfile.LoadLocksFromBytes([]byte(tc.LockFile), "test.lock.hcl")
   263  				if len(diags) > 0 {
   264  					t.Fatalf("unexpected error loading locks file: %s", diags.Err())
   265  				}
   266  			}
   267  			devProviders := make(map[addrs.Provider]struct{})
   268  			for _, provider := range tc.DevProviders {
   269  				devProviders[provider] = struct{}{}
   270  			}
   271  			opts := &ContextOpts{
   272  				LockedDependencies:     locks,
   273  				ProvidersInDevelopment: devProviders,
   274  				Providers: map[addrs.Provider]providers.Factory{
   275  					beepAddr:                              testProviderFuncFixed(testProvider("beep")),
   276  					boopAddr:                              testProviderFuncFixed(testProvider("boop")),
   277  					addrs.NewBuiltInProvider("terraform"): testProviderFuncFixed(testProvider("terraform")),
   278  				},
   279  			}
   280  
   281  			m := testModuleInline(t, map[string]string{
   282  				"main.tf": tc.Config,
   283  			})
   284  
   285  			c, diags := NewContext(opts)
   286  			if diags.HasErrors() {
   287  				t.Fatalf("unexpected NewContext error: %s", diags.Err())
   288  			}
   289  
   290  			diags = c.Validate(m)
   291  			if tc.WantErr != "" {
   292  				if len(diags) == 0 {
   293  					t.Fatal("expected diags but none returned")
   294  				}
   295  				if got, want := diags.Err().Error(), tc.WantErr; got != want {
   296  					t.Errorf("wrong diags\n got: %s\nwant: %s", got, want)
   297  				}
   298  			} else {
   299  				if len(diags) > 0 {
   300  					t.Errorf("unexpected diags: %s", diags.Err())
   301  				}
   302  			}
   303  		})
   304  	}
   305  }
   306  
   307  func testContext2(t *testing.T, opts *ContextOpts) *Context {
   308  	t.Helper()
   309  
   310  	ctx, diags := NewContext(opts)
   311  	if diags.HasErrors() {
   312  		t.Fatalf("failed to create test context\n\n%s\n", diags.Err())
   313  	}
   314  
   315  	return ctx
   316  }
   317  
   318  func testApplyFn(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   319  	resp.NewState = req.PlannedState
   320  	if req.PlannedState.IsNull() {
   321  		resp.NewState = cty.NullVal(req.PriorState.Type())
   322  		return
   323  	}
   324  
   325  	planned := req.PlannedState.AsValueMap()
   326  	if planned == nil {
   327  		planned = map[string]cty.Value{}
   328  	}
   329  
   330  	id, ok := planned["id"]
   331  	if !ok || id.IsNull() || !id.IsKnown() {
   332  		planned["id"] = cty.StringVal("foo")
   333  	}
   334  
   335  	// our default schema has a computed "type" attr
   336  	if ty, ok := planned["type"]; ok && !ty.IsNull() {
   337  		planned["type"] = cty.StringVal(req.TypeName)
   338  	}
   339  
   340  	if cmp, ok := planned["compute"]; ok && !cmp.IsNull() {
   341  		computed := cmp.AsString()
   342  		if val, ok := planned[computed]; ok && !val.IsKnown() {
   343  			planned[computed] = cty.StringVal("computed_value")
   344  		}
   345  	}
   346  
   347  	for k, v := range planned {
   348  		if k == "unknown" {
   349  			// "unknown" should cause an error
   350  			continue
   351  		}
   352  
   353  		if !v.IsKnown() {
   354  			switch k {
   355  			case "type":
   356  				planned[k] = cty.StringVal(req.TypeName)
   357  			default:
   358  				planned[k] = cty.NullVal(v.Type())
   359  			}
   360  		}
   361  	}
   362  
   363  	resp.NewState = cty.ObjectVal(planned)
   364  	return
   365  }
   366  
   367  func testDiffFn(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   368  	var planned map[string]cty.Value
   369  	if !req.ProposedNewState.IsNull() {
   370  		planned = req.ProposedNewState.AsValueMap()
   371  	}
   372  	if planned == nil {
   373  		planned = map[string]cty.Value{}
   374  	}
   375  
   376  	// id is always computed for the tests
   377  	if id, ok := planned["id"]; ok && id.IsNull() {
   378  		planned["id"] = cty.UnknownVal(cty.String)
   379  	}
   380  
   381  	// the old tests have require_new replace on every plan
   382  	if _, ok := planned["require_new"]; ok {
   383  		resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: "require_new"}})
   384  	}
   385  
   386  	for k := range planned {
   387  		requiresNewKey := "__" + k + "_requires_new"
   388  		_, ok := planned[requiresNewKey]
   389  		if ok {
   390  			resp.RequiresReplace = append(resp.RequiresReplace, cty.Path{cty.GetAttrStep{Name: requiresNewKey}})
   391  		}
   392  	}
   393  
   394  	if v, ok := planned["compute"]; ok && !v.IsNull() {
   395  		k := v.AsString()
   396  		unknown := cty.UnknownVal(cty.String)
   397  		if strings.HasSuffix(k, ".#") {
   398  			k = k[:len(k)-2]
   399  			unknown = cty.UnknownVal(cty.List(cty.String))
   400  		}
   401  		planned[k] = unknown
   402  	}
   403  
   404  	if t, ok := planned["type"]; ok && t.IsNull() {
   405  		planned["type"] = cty.UnknownVal(cty.String)
   406  	}
   407  
   408  	resp.PlannedState = cty.ObjectVal(planned)
   409  	return
   410  }
   411  
   412  func testProvider(prefix string) *MockProvider {
   413  	p := new(MockProvider)
   414  	p.GetProviderSchemaResponse = testProviderSchema(prefix)
   415  
   416  	return p
   417  }
   418  
   419  func testProvisioner() *MockProvisioner {
   420  	p := new(MockProvisioner)
   421  	p.GetSchemaResponse = provisioners.GetSchemaResponse{
   422  		Provisioner: &configschema.Block{
   423  			Attributes: map[string]*configschema.Attribute{
   424  				"command": {
   425  					Type:     cty.String,
   426  					Optional: true,
   427  				},
   428  				"order": {
   429  					Type:     cty.String,
   430  					Optional: true,
   431  				},
   432  				"when": {
   433  					Type:     cty.String,
   434  					Optional: true,
   435  				},
   436  			},
   437  		},
   438  	}
   439  	return p
   440  }
   441  
   442  func checkStateString(t *testing.T, state *states.State, expected string) {
   443  	t.Helper()
   444  	actual := strings.TrimSpace(state.String())
   445  	expected = strings.TrimSpace(expected)
   446  
   447  	if actual != expected {
   448  		t.Fatalf("incorrect state\ngot:\n%s\n\nwant:\n%s", actual, expected)
   449  	}
   450  }
   451  
   452  // Test helper that gives a function 3 seconds to finish, assumes deadlock and
   453  // fails test if it does not.
   454  func testCheckDeadlock(t *testing.T, f func()) {
   455  	t.Helper()
   456  	timeout := make(chan bool, 1)
   457  	done := make(chan bool, 1)
   458  	go func() {
   459  		time.Sleep(3 * time.Second)
   460  		timeout <- true
   461  	}()
   462  	go func(f func(), done chan bool) {
   463  		defer func() { done <- true }()
   464  		f()
   465  	}(f, done)
   466  	select {
   467  	case <-timeout:
   468  		t.Fatalf("timed out! probably deadlock")
   469  	case <-done:
   470  		// ok
   471  	}
   472  }
   473  
   474  func testProviderSchema(name string) *providers.GetProviderSchemaResponse {
   475  	return getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   476  		Provider: &configschema.Block{
   477  			Attributes: map[string]*configschema.Attribute{
   478  				"region": {
   479  					Type:     cty.String,
   480  					Optional: true,
   481  				},
   482  				"foo": {
   483  					Type:     cty.String,
   484  					Optional: true,
   485  				},
   486  				"value": {
   487  					Type:     cty.String,
   488  					Optional: true,
   489  				},
   490  				"root": {
   491  					Type:     cty.Number,
   492  					Optional: true,
   493  				},
   494  			},
   495  		},
   496  		ResourceTypes: map[string]*configschema.Block{
   497  			name + "_instance": {
   498  				Attributes: map[string]*configschema.Attribute{
   499  					"id": {
   500  						Type:     cty.String,
   501  						Computed: true,
   502  					},
   503  					"ami": {
   504  						Type:     cty.String,
   505  						Optional: true,
   506  					},
   507  					"dep": {
   508  						Type:     cty.String,
   509  						Optional: true,
   510  					},
   511  					"num": {
   512  						Type:     cty.Number,
   513  						Optional: true,
   514  					},
   515  					"require_new": {
   516  						Type:     cty.String,
   517  						Optional: true,
   518  					},
   519  					"var": {
   520  						Type:     cty.String,
   521  						Optional: true,
   522  					},
   523  					"foo": {
   524  						Type:     cty.String,
   525  						Optional: true,
   526  						Computed: true,
   527  					},
   528  					"bar": {
   529  						Type:     cty.String,
   530  						Optional: true,
   531  					},
   532  					"compute": {
   533  						Type:     cty.String,
   534  						Optional: true,
   535  						Computed: false,
   536  					},
   537  					"compute_value": {
   538  						Type:     cty.String,
   539  						Optional: true,
   540  						Computed: true,
   541  					},
   542  					"value": {
   543  						Type:     cty.String,
   544  						Optional: true,
   545  						Computed: true,
   546  					},
   547  					"output": {
   548  						Type:     cty.String,
   549  						Optional: true,
   550  					},
   551  					"write": {
   552  						Type:     cty.String,
   553  						Optional: true,
   554  					},
   555  					"instance": {
   556  						Type:     cty.String,
   557  						Optional: true,
   558  					},
   559  					"vpc_id": {
   560  						Type:     cty.String,
   561  						Optional: true,
   562  					},
   563  					"type": {
   564  						Type:     cty.String,
   565  						Computed: true,
   566  					},
   567  
   568  					// Generated by testDiffFn if compute = "unknown" is set in the test config
   569  					"unknown": {
   570  						Type:     cty.String,
   571  						Computed: true,
   572  					},
   573  				},
   574  			},
   575  			name + "_eip": {
   576  				Attributes: map[string]*configschema.Attribute{
   577  					"id": {
   578  						Type:     cty.String,
   579  						Computed: true,
   580  					},
   581  					"instance": {
   582  						Type:     cty.String,
   583  						Optional: true,
   584  					},
   585  				},
   586  			},
   587  			name + "_resource": {
   588  				Attributes: map[string]*configschema.Attribute{
   589  					"id": {
   590  						Type:     cty.String,
   591  						Computed: true,
   592  					},
   593  					"value": {
   594  						Type:     cty.String,
   595  						Optional: true,
   596  					},
   597  					"sensitive_value": {
   598  						Type:      cty.String,
   599  						Sensitive: true,
   600  						Optional:  true,
   601  					},
   602  					"random": {
   603  						Type:     cty.String,
   604  						Optional: true,
   605  					},
   606  				},
   607  				BlockTypes: map[string]*configschema.NestedBlock{
   608  					"nesting_single": {
   609  						Block: configschema.Block{
   610  							Attributes: map[string]*configschema.Attribute{
   611  								"value":           {Type: cty.String, Optional: true},
   612  								"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
   613  							},
   614  						},
   615  						Nesting: configschema.NestingSingle,
   616  					},
   617  				},
   618  			},
   619  			name + "_ami_list": {
   620  				Attributes: map[string]*configschema.Attribute{
   621  					"id": {
   622  						Type:     cty.String,
   623  						Optional: true,
   624  						Computed: true,
   625  					},
   626  					"ids": {
   627  						Type:     cty.List(cty.String),
   628  						Optional: true,
   629  						Computed: true,
   630  					},
   631  				},
   632  			},
   633  			name + "_remote_state": {
   634  				Attributes: map[string]*configschema.Attribute{
   635  					"id": {
   636  						Type:     cty.String,
   637  						Optional: true,
   638  					},
   639  					"foo": {
   640  						Type:     cty.String,
   641  						Optional: true,
   642  					},
   643  					"output": {
   644  						Type:     cty.Map(cty.String),
   645  						Computed: true,
   646  					},
   647  				},
   648  			},
   649  			name + "_file": {
   650  				Attributes: map[string]*configschema.Attribute{
   651  					"id": {
   652  						Type:     cty.String,
   653  						Optional: true,
   654  					},
   655  					"template": {
   656  						Type:     cty.String,
   657  						Optional: true,
   658  					},
   659  					"rendered": {
   660  						Type:     cty.String,
   661  						Computed: true,
   662  					},
   663  					"__template_requires_new": {
   664  						Type:     cty.String,
   665  						Optional: true,
   666  					},
   667  				},
   668  			},
   669  		},
   670  		DataSources: map[string]*configschema.Block{
   671  			name + "_data_source": {
   672  				Attributes: map[string]*configschema.Attribute{
   673  					"id": {
   674  						Type:     cty.String,
   675  						Computed: true,
   676  					},
   677  					"foo": {
   678  						Type:     cty.String,
   679  						Optional: true,
   680  						Computed: true,
   681  					},
   682  				},
   683  			},
   684  			name + "_remote_state": {
   685  				Attributes: map[string]*configschema.Attribute{
   686  					"id": {
   687  						Type:     cty.String,
   688  						Optional: true,
   689  					},
   690  					"foo": {
   691  						Type:     cty.String,
   692  						Optional: true,
   693  					},
   694  					"output": {
   695  						Type:     cty.Map(cty.String),
   696  						Optional: true,
   697  					},
   698  				},
   699  			},
   700  			name + "_file": {
   701  				Attributes: map[string]*configschema.Attribute{
   702  					"id": {
   703  						Type:     cty.String,
   704  						Optional: true,
   705  					},
   706  					"template": {
   707  						Type:     cty.String,
   708  						Optional: true,
   709  					},
   710  					"rendered": {
   711  						Type:     cty.String,
   712  						Computed: true,
   713  					},
   714  				},
   715  			},
   716  		},
   717  	})
   718  }
   719  
   720  // contextForPlanViaFile is a helper that creates a temporary plan file, then
   721  // reads it back in again and produces a ContextOpts object containing the
   722  // planned changes, prior state and config from the plan file.
   723  //
   724  // This is intended for testing the separated plan/apply workflow in a more
   725  // convenient way than spelling out all of these steps every time. Normally
   726  // only the command and backend packages need to deal with such things, but
   727  // our context tests try to exercise lots of stuff at once and so having them
   728  // round-trip things through on-disk files is often an important part of
   729  // fully representing an old bug in a regression test.
   730  func contextOptsForPlanViaFile(configSnap *configload.Snapshot, plan *plans.Plan) (*ContextOpts, *configs.Config, *plans.Plan, error) {
   731  	dir, err := ioutil.TempDir("", "terraform-contextForPlanViaFile")
   732  	if err != nil {
   733  		return nil, nil, nil, err
   734  	}
   735  	defer os.RemoveAll(dir)
   736  
   737  	// We'll just create a dummy statefile.File here because we're not going
   738  	// to run through any of the codepaths that care about Lineage/Serial/etc
   739  	// here anyway.
   740  	stateFile := &statefile.File{
   741  		State: plan.PriorState,
   742  	}
   743  	prevStateFile := &statefile.File{
   744  		State: plan.PrevRunState,
   745  	}
   746  
   747  	// To make life a little easier for test authors, we'll populate a simple
   748  	// backend configuration if they didn't set one, since the backend is
   749  	// usually dealt with in a calling package and so tests in this package
   750  	// don't really care about it.
   751  	if plan.Backend.Config == nil {
   752  		cfg, err := plans.NewDynamicValue(cty.EmptyObjectVal, cty.EmptyObject)
   753  		if err != nil {
   754  			panic(fmt.Sprintf("NewDynamicValue failed: %s", err)) // shouldn't happen because we control the inputs
   755  		}
   756  		plan.Backend.Type = "local"
   757  		plan.Backend.Config = cfg
   758  		plan.Backend.Workspace = "default"
   759  	}
   760  
   761  	filename := filepath.Join(dir, "tfplan")
   762  	err = planfile.Create(filename, configSnap, prevStateFile, stateFile, plan)
   763  	if err != nil {
   764  		return nil, nil, nil, err
   765  	}
   766  
   767  	pr, err := planfile.Open(filename)
   768  	if err != nil {
   769  		return nil, nil, nil, err
   770  	}
   771  
   772  	config, diags := pr.ReadConfig()
   773  	if diags.HasErrors() {
   774  		return nil, nil, nil, diags.Err()
   775  	}
   776  
   777  	plan, err = pr.ReadPlan()
   778  	if err != nil {
   779  		return nil, nil, nil, err
   780  	}
   781  
   782  	return &ContextOpts{
   783  		ProviderSHA256s: plan.ProviderSHA256s,
   784  	}, config, plan, nil
   785  }
   786  
   787  // legacyPlanComparisonString produces a string representation of the changes
   788  // from a plan and a given state togther, as was formerly produced by the
   789  // String method of terraform.Plan.
   790  //
   791  // This is here only for compatibility with existing tests that predate our
   792  // new plan and state types, and should not be used in new tests. Instead, use
   793  // a library like "cmp" to do a deep equality check and diff on the two
   794  // data structures.
   795  func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
   796  	return fmt.Sprintf(
   797  		"DIFF:\n\n%s\n\nSTATE:\n\n%s",
   798  		legacyDiffComparisonString(changes),
   799  		state.String(),
   800  	)
   801  }
   802  
   803  // legacyDiffComparisonString produces a string representation of the changes
   804  // from a planned changes object, as was formerly produced by the String method
   805  // of terraform.Diff.
   806  //
   807  // This is here only for compatibility with existing tests that predate our
   808  // new plan types, and should not be used in new tests. Instead, use a library
   809  // like "cmp" to do a deep equality check and diff on the two data structures.
   810  func legacyDiffComparisonString(changes *plans.Changes) string {
   811  	// The old string representation of a plan was grouped by module, but
   812  	// our new plan structure is not grouped in that way and so we'll need
   813  	// to preprocess it in order to produce that grouping.
   814  	type ResourceChanges struct {
   815  		Current *plans.ResourceInstanceChangeSrc
   816  		Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc
   817  	}
   818  	byModule := map[string]map[string]*ResourceChanges{}
   819  	resourceKeys := map[string][]string{}
   820  	var moduleKeys []string
   821  	for _, rc := range changes.Resources {
   822  		if rc.Action == plans.NoOp {
   823  			// We won't mention no-op changes here at all, since the old plan
   824  			// model we are emulating here didn't have such a concept.
   825  			continue
   826  		}
   827  		moduleKey := rc.Addr.Module.String()
   828  		if _, exists := byModule[moduleKey]; !exists {
   829  			moduleKeys = append(moduleKeys, moduleKey)
   830  			byModule[moduleKey] = make(map[string]*ResourceChanges)
   831  		}
   832  		resourceKey := rc.Addr.Resource.String()
   833  		if _, exists := byModule[moduleKey][resourceKey]; !exists {
   834  			resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey)
   835  			byModule[moduleKey][resourceKey] = &ResourceChanges{
   836  				Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc),
   837  			}
   838  		}
   839  
   840  		if rc.DeposedKey == states.NotDeposed {
   841  			byModule[moduleKey][resourceKey].Current = rc
   842  		} else {
   843  			byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc
   844  		}
   845  	}
   846  	sort.Strings(moduleKeys)
   847  	for _, ks := range resourceKeys {
   848  		sort.Strings(ks)
   849  	}
   850  
   851  	var buf bytes.Buffer
   852  
   853  	for _, moduleKey := range moduleKeys {
   854  		rcs := byModule[moduleKey]
   855  		var mBuf bytes.Buffer
   856  
   857  		for _, resourceKey := range resourceKeys[moduleKey] {
   858  			rc := rcs[resourceKey]
   859  
   860  			crud := "UPDATE"
   861  			if rc.Current != nil {
   862  				switch rc.Current.Action {
   863  				case plans.DeleteThenCreate:
   864  					crud = "DESTROY/CREATE"
   865  				case plans.CreateThenDelete:
   866  					crud = "CREATE/DESTROY"
   867  				case plans.Delete:
   868  					crud = "DESTROY"
   869  				case plans.Create:
   870  					crud = "CREATE"
   871  				}
   872  			} else {
   873  				// We must be working on a deposed object then, in which
   874  				// case destroying is the only possible action.
   875  				crud = "DESTROY"
   876  			}
   877  
   878  			extra := ""
   879  			if rc.Current == nil && len(rc.Deposed) > 0 {
   880  				extra = " (deposed only)"
   881  			}
   882  
   883  			fmt.Fprintf(
   884  				&mBuf, "%s: %s%s\n",
   885  				crud, resourceKey, extra,
   886  			)
   887  
   888  			attrNames := map[string]bool{}
   889  			var oldAttrs map[string]string
   890  			var newAttrs map[string]string
   891  			if rc.Current != nil {
   892  				if before := rc.Current.Before; before != nil {
   893  					ty, err := before.ImpliedType()
   894  					if err == nil {
   895  						val, err := before.Decode(ty)
   896  						if err == nil {
   897  							oldAttrs = hcl2shim.FlatmapValueFromHCL2(val)
   898  							for k := range oldAttrs {
   899  								attrNames[k] = true
   900  							}
   901  						}
   902  					}
   903  				}
   904  				if after := rc.Current.After; after != nil {
   905  					ty, err := after.ImpliedType()
   906  					if err == nil {
   907  						val, err := after.Decode(ty)
   908  						if err == nil {
   909  							newAttrs = hcl2shim.FlatmapValueFromHCL2(val)
   910  							for k := range newAttrs {
   911  								attrNames[k] = true
   912  							}
   913  						}
   914  					}
   915  				}
   916  			}
   917  			if oldAttrs == nil {
   918  				oldAttrs = make(map[string]string)
   919  			}
   920  			if newAttrs == nil {
   921  				newAttrs = make(map[string]string)
   922  			}
   923  
   924  			attrNamesOrder := make([]string, 0, len(attrNames))
   925  			keyLen := 0
   926  			for n := range attrNames {
   927  				attrNamesOrder = append(attrNamesOrder, n)
   928  				if len(n) > keyLen {
   929  					keyLen = len(n)
   930  				}
   931  			}
   932  			sort.Strings(attrNamesOrder)
   933  
   934  			for _, attrK := range attrNamesOrder {
   935  				v := newAttrs[attrK]
   936  				u := oldAttrs[attrK]
   937  
   938  				if v == hcl2shim.UnknownVariableValue {
   939  					v = "<computed>"
   940  				}
   941  				// NOTE: we don't support <sensitive> here because we would
   942  				// need schema to do that. Excluding sensitive values
   943  				// is now done at the UI layer, and so should not be tested
   944  				// at the core layer.
   945  
   946  				updateMsg := ""
   947  				// TODO: Mark " (forces new resource)" in updateMsg when appropriate.
   948  
   949  				fmt.Fprintf(
   950  					&mBuf, "  %s:%s %#v => %#v%s\n",
   951  					attrK,
   952  					strings.Repeat(" ", keyLen-len(attrK)),
   953  					u, v,
   954  					updateMsg,
   955  				)
   956  			}
   957  		}
   958  
   959  		if moduleKey == "" { // root module
   960  			buf.Write(mBuf.Bytes())
   961  			buf.WriteByte('\n')
   962  			continue
   963  		}
   964  
   965  		fmt.Fprintf(&buf, "%s:\n", moduleKey)
   966  		s := bufio.NewScanner(&mBuf)
   967  		for s.Scan() {
   968  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
   969  		}
   970  	}
   971  
   972  	return buf.String()
   973  }
   974  
   975  // assertNoDiagnostics fails the test in progress (using t.Fatal) if the given
   976  // diagnostics is non-empty.
   977  func assertNoDiagnostics(t *testing.T, diags tfdiags.Diagnostics) {
   978  	t.Helper()
   979  	if len(diags) == 0 {
   980  		return
   981  	}
   982  	logDiagnostics(t, diags)
   983  	t.FailNow()
   984  }
   985  
   986  // assertNoDiagnostics fails the test in progress (using t.Fatal) if the given
   987  // diagnostics has any errors.
   988  func assertNoErrors(t *testing.T, diags tfdiags.Diagnostics) {
   989  	t.Helper()
   990  	if !diags.HasErrors() {
   991  		return
   992  	}
   993  	logDiagnostics(t, diags)
   994  	t.FailNow()
   995  }
   996  
   997  // logDiagnostics is a test helper that logs the given diagnostics to to the
   998  // given testing.T using t.Log, in a way that is hopefully useful in debugging
   999  // a test. It does not generate any errors or fail the test. See
  1000  // assertNoDiagnostics and assertNoErrors for more specific helpers that can
  1001  // also fail the test.
  1002  func logDiagnostics(t *testing.T, diags tfdiags.Diagnostics) {
  1003  	t.Helper()
  1004  	for _, diag := range diags {
  1005  		desc := diag.Description()
  1006  		rng := diag.Source()
  1007  
  1008  		var severity string
  1009  		switch diag.Severity() {
  1010  		case tfdiags.Error:
  1011  			severity = "ERROR"
  1012  		case tfdiags.Warning:
  1013  			severity = "WARN"
  1014  		default:
  1015  			severity = "???" // should never happen
  1016  		}
  1017  
  1018  		if subj := rng.Subject; subj != nil {
  1019  			if desc.Detail == "" {
  1020  				t.Logf("[%s@%s] %s", severity, subj.StartString(), desc.Summary)
  1021  			} else {
  1022  				t.Logf("[%s@%s] %s: %s", severity, subj.StartString(), desc.Summary, desc.Detail)
  1023  			}
  1024  		} else {
  1025  			if desc.Detail == "" {
  1026  				t.Logf("[%s] %s", severity, desc.Summary)
  1027  			} else {
  1028  				t.Logf("[%s] %s: %s", severity, desc.Summary, desc.Detail)
  1029  			}
  1030  		}
  1031  	}
  1032  }
  1033  
  1034  const testContextRefreshModuleStr = `
  1035  aws_instance.web: (tainted)
  1036    ID = bar
  1037    provider = provider["registry.terraform.io/hashicorp/aws"]
  1038  
  1039  module.child:
  1040    aws_instance.web:
  1041      ID = new
  1042      provider = provider["registry.terraform.io/hashicorp/aws"]
  1043  `
  1044  
  1045  const testContextRefreshOutputStr = `
  1046  aws_instance.web:
  1047    ID = foo
  1048    provider = provider["registry.terraform.io/hashicorp/aws"]
  1049    foo = bar
  1050  
  1051  Outputs:
  1052  
  1053  foo = bar
  1054  `
  1055  
  1056  const testContextRefreshOutputPartialStr = `
  1057  <no state>
  1058  `
  1059  
  1060  const testContextRefreshTaintedStr = `
  1061  aws_instance.web: (tainted)
  1062    ID = foo
  1063    provider = provider["registry.terraform.io/hashicorp/aws"]
  1064  `