github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/initwd/module_install_test.go (about)

     1  package initwd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"flag"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/davecgh/go-spew/spew"
    15  	"github.com/go-test/deep"
    16  	"github.com/google/go-cmp/cmp"
    17  	version "github.com/hashicorp/go-version"
    18  	svchost "github.com/hashicorp/terraform-svchost"
    19  	"github.com/hashicorp/terraform/internal/addrs"
    20  	"github.com/hashicorp/terraform/internal/configs"
    21  	"github.com/hashicorp/terraform/internal/configs/configload"
    22  	"github.com/hashicorp/terraform/internal/copy"
    23  	"github.com/hashicorp/terraform/internal/registry"
    24  	"github.com/hashicorp/terraform/internal/tfdiags"
    25  
    26  	_ "github.com/hashicorp/terraform/internal/logging"
    27  )
    28  
    29  func TestMain(m *testing.M) {
    30  	flag.Parse()
    31  	os.Exit(m.Run())
    32  }
    33  
    34  func TestModuleInstaller(t *testing.T) {
    35  	fixtureDir := filepath.Clean("testdata/local-modules")
    36  	dir, done := tempChdir(t, fixtureDir)
    37  	defer done()
    38  
    39  	hooks := &testInstallHooks{}
    40  
    41  	modulesDir := filepath.Join(dir, ".terraform/modules")
    42  	inst := NewModuleInstaller(modulesDir, nil)
    43  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
    44  	assertNoDiagnostics(t, diags)
    45  
    46  	wantCalls := []testInstallHookCall{
    47  		{
    48  			Name:        "Install",
    49  			ModuleAddr:  "child_a",
    50  			PackageAddr: "",
    51  			LocalPath:   "child_a",
    52  		},
    53  		{
    54  			Name:        "Install",
    55  			ModuleAddr:  "child_a.child_b",
    56  			PackageAddr: "",
    57  			LocalPath:   "child_a/child_b",
    58  		},
    59  	}
    60  
    61  	if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
    62  		return
    63  	}
    64  
    65  	loader, err := configload.NewLoader(&configload.Config{
    66  		ModulesDir: modulesDir,
    67  	})
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	// Make sure the configuration is loadable now.
    73  	// (This ensures that correct information is recorded in the manifest.)
    74  	config, loadDiags := loader.LoadConfig(".")
    75  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
    76  
    77  	wantTraces := map[string]string{
    78  		"":                "in root module",
    79  		"child_a":         "in child_a module",
    80  		"child_a.child_b": "in child_b module",
    81  	}
    82  	gotTraces := map[string]string{}
    83  	config.DeepEach(func(c *configs.Config) {
    84  		path := strings.Join(c.Path, ".")
    85  		if c.Module.Variables["v"] == nil {
    86  			gotTraces[path] = "<missing>"
    87  			return
    88  		}
    89  		varDesc := c.Module.Variables["v"].Description
    90  		gotTraces[path] = varDesc
    91  	})
    92  	assertResultDeepEqual(t, gotTraces, wantTraces)
    93  }
    94  
    95  func TestModuleInstaller_error(t *testing.T) {
    96  	fixtureDir := filepath.Clean("testdata/local-module-error")
    97  	dir, done := tempChdir(t, fixtureDir)
    98  	defer done()
    99  
   100  	hooks := &testInstallHooks{}
   101  
   102  	modulesDir := filepath.Join(dir, ".terraform/modules")
   103  	inst := NewModuleInstaller(modulesDir, nil)
   104  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   105  
   106  	if !diags.HasErrors() {
   107  		t.Fatal("expected error")
   108  	} else {
   109  		assertDiagnosticSummary(t, diags, "Invalid module source address")
   110  	}
   111  }
   112  
   113  func TestModuleInstaller_packageEscapeError(t *testing.T) {
   114  	fixtureDir := filepath.Clean("testdata/load-module-package-escape")
   115  	dir, done := tempChdir(t, fixtureDir)
   116  	defer done()
   117  
   118  	// For this particular test we need an absolute path in the root module
   119  	// that must actually resolve to our temporary directory in "dir", so
   120  	// we need to do a little rewriting. We replace the arbitrary placeholder
   121  	// %%BASE%% with the temporary directory path.
   122  	{
   123  		rootFilename := filepath.Join(dir, "package-escape.tf")
   124  		template, err := ioutil.ReadFile(rootFilename)
   125  		if err != nil {
   126  			t.Fatal(err)
   127  		}
   128  		final := bytes.ReplaceAll(template, []byte("%%BASE%%"), []byte(filepath.ToSlash(dir)))
   129  		err = ioutil.WriteFile(rootFilename, final, 0644)
   130  		if err != nil {
   131  			t.Fatal(err)
   132  		}
   133  	}
   134  
   135  	hooks := &testInstallHooks{}
   136  
   137  	modulesDir := filepath.Join(dir, ".terraform/modules")
   138  	inst := NewModuleInstaller(modulesDir, nil)
   139  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   140  
   141  	if !diags.HasErrors() {
   142  		t.Fatal("expected error")
   143  	} else {
   144  		assertDiagnosticSummary(t, diags, "Local module path escapes module package")
   145  	}
   146  }
   147  
   148  func TestModuleInstaller_explicitPackageBoundary(t *testing.T) {
   149  	fixtureDir := filepath.Clean("testdata/load-module-package-prefix")
   150  	dir, done := tempChdir(t, fixtureDir)
   151  	defer done()
   152  
   153  	// For this particular test we need an absolute path in the root module
   154  	// that must actually resolve to our temporary directory in "dir", so
   155  	// we need to do a little rewriting. We replace the arbitrary placeholder
   156  	// %%BASE%% with the temporary directory path.
   157  	{
   158  		rootFilename := filepath.Join(dir, "package-prefix.tf")
   159  		template, err := ioutil.ReadFile(rootFilename)
   160  		if err != nil {
   161  			t.Fatal(err)
   162  		}
   163  		final := bytes.ReplaceAll(template, []byte("%%BASE%%"), []byte(filepath.ToSlash(dir)))
   164  		err = ioutil.WriteFile(rootFilename, final, 0644)
   165  		if err != nil {
   166  			t.Fatal(err)
   167  		}
   168  	}
   169  
   170  	hooks := &testInstallHooks{}
   171  
   172  	modulesDir := filepath.Join(dir, ".terraform/modules")
   173  	inst := NewModuleInstaller(modulesDir, nil)
   174  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   175  
   176  	if diags.HasErrors() {
   177  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
   178  	}
   179  }
   180  
   181  func TestModuleInstaller_invalid_version_constraint_error(t *testing.T) {
   182  	fixtureDir := filepath.Clean("testdata/invalid-version-constraint")
   183  	dir, done := tempChdir(t, fixtureDir)
   184  	defer done()
   185  
   186  	hooks := &testInstallHooks{}
   187  
   188  	modulesDir := filepath.Join(dir, ".terraform/modules")
   189  	inst := NewModuleInstaller(modulesDir, nil)
   190  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   191  
   192  	if !diags.HasErrors() {
   193  		t.Fatal("expected error")
   194  	} else {
   195  		// We use the presence of the "version" argument as a heuristic for
   196  		// user intent to use a registry module, and so we intentionally catch
   197  		// this as an invalid registry module address rather than an invalid
   198  		// version constraint, so we can surface the specific address parsing
   199  		// error instead of a generic version constraint error.
   200  		assertDiagnosticSummary(t, diags, "Invalid registry module source address")
   201  	}
   202  }
   203  
   204  func TestModuleInstaller_invalidVersionConstraintGetter(t *testing.T) {
   205  	fixtureDir := filepath.Clean("testdata/invalid-version-constraint")
   206  	dir, done := tempChdir(t, fixtureDir)
   207  	defer done()
   208  
   209  	hooks := &testInstallHooks{}
   210  
   211  	modulesDir := filepath.Join(dir, ".terraform/modules")
   212  	inst := NewModuleInstaller(modulesDir, nil)
   213  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   214  
   215  	if !diags.HasErrors() {
   216  		t.Fatal("expected error")
   217  	} else {
   218  		// We use the presence of the "version" argument as a heuristic for
   219  		// user intent to use a registry module, and so we intentionally catch
   220  		// this as an invalid registry module address rather than an invalid
   221  		// version constraint, so we can surface the specific address parsing
   222  		// error instead of a generic version constraint error.
   223  		assertDiagnosticSummary(t, diags, "Invalid registry module source address")
   224  	}
   225  }
   226  
   227  func TestModuleInstaller_invalidVersionConstraintLocal(t *testing.T) {
   228  	fixtureDir := filepath.Clean("testdata/invalid-version-constraint-local")
   229  	dir, done := tempChdir(t, fixtureDir)
   230  	defer done()
   231  
   232  	hooks := &testInstallHooks{}
   233  
   234  	modulesDir := filepath.Join(dir, ".terraform/modules")
   235  	inst := NewModuleInstaller(modulesDir, nil)
   236  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   237  
   238  	if !diags.HasErrors() {
   239  		t.Fatal("expected error")
   240  	} else {
   241  		// We use the presence of the "version" argument as a heuristic for
   242  		// user intent to use a registry module, and so we intentionally catch
   243  		// this as an invalid registry module address rather than an invalid
   244  		// version constraint, so we can surface the specific address parsing
   245  		// error instead of a generic version constraint error.
   246  		assertDiagnosticSummary(t, diags, "Invalid registry module source address")
   247  	}
   248  }
   249  
   250  func TestModuleInstaller_symlink(t *testing.T) {
   251  	fixtureDir := filepath.Clean("testdata/local-module-symlink")
   252  	dir, done := tempChdir(t, fixtureDir)
   253  	defer done()
   254  
   255  	hooks := &testInstallHooks{}
   256  
   257  	modulesDir := filepath.Join(dir, ".terraform/modules")
   258  	inst := NewModuleInstaller(modulesDir, nil)
   259  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   260  	assertNoDiagnostics(t, diags)
   261  
   262  	wantCalls := []testInstallHookCall{
   263  		{
   264  			Name:        "Install",
   265  			ModuleAddr:  "child_a",
   266  			PackageAddr: "",
   267  			LocalPath:   "child_a",
   268  		},
   269  		{
   270  			Name:        "Install",
   271  			ModuleAddr:  "child_a.child_b",
   272  			PackageAddr: "",
   273  			LocalPath:   "child_a/child_b",
   274  		},
   275  	}
   276  
   277  	if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
   278  		return
   279  	}
   280  
   281  	loader, err := configload.NewLoader(&configload.Config{
   282  		ModulesDir: modulesDir,
   283  	})
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  
   288  	// Make sure the configuration is loadable now.
   289  	// (This ensures that correct information is recorded in the manifest.)
   290  	config, loadDiags := loader.LoadConfig(".")
   291  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   292  
   293  	wantTraces := map[string]string{
   294  		"":                "in root module",
   295  		"child_a":         "in child_a module",
   296  		"child_a.child_b": "in child_b module",
   297  	}
   298  	gotTraces := map[string]string{}
   299  	config.DeepEach(func(c *configs.Config) {
   300  		path := strings.Join(c.Path, ".")
   301  		if c.Module.Variables["v"] == nil {
   302  			gotTraces[path] = "<missing>"
   303  			return
   304  		}
   305  		varDesc := c.Module.Variables["v"].Description
   306  		gotTraces[path] = varDesc
   307  	})
   308  	assertResultDeepEqual(t, gotTraces, wantTraces)
   309  }
   310  
   311  func TestLoaderInstallModules_registry(t *testing.T) {
   312  	if os.Getenv("TF_ACC") == "" {
   313  		t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
   314  	}
   315  
   316  	fixtureDir := filepath.Clean("testdata/registry-modules")
   317  	tmpDir, done := tempChdir(t, fixtureDir)
   318  	// the module installer runs filepath.EvalSymlinks() on the destination
   319  	// directory before copying files, and the resultant directory is what is
   320  	// returned by the install hooks. Without this, tests could fail on machines
   321  	// where the default temp dir was a symlink.
   322  	dir, err := filepath.EvalSymlinks(tmpDir)
   323  	if err != nil {
   324  		t.Error(err)
   325  	}
   326  
   327  	defer done()
   328  
   329  	hooks := &testInstallHooks{}
   330  	modulesDir := filepath.Join(dir, ".terraform/modules")
   331  	inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
   332  	_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
   333  	assertNoDiagnostics(t, diags)
   334  
   335  	v := version.Must(version.NewVersion("0.0.1"))
   336  
   337  	wantCalls := []testInstallHookCall{
   338  		// the configuration builder visits each level of calls in lexicographical
   339  		// order by name, so the following list is kept in the same order.
   340  
   341  		// acctest_child_a accesses //modules/child_a directly
   342  		{
   343  			Name:        "Download",
   344  			ModuleAddr:  "acctest_child_a",
   345  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws", // intentionally excludes the subdir because we're downloading the whole package here
   346  			Version:     v,
   347  		},
   348  		{
   349  			Name:       "Install",
   350  			ModuleAddr: "acctest_child_a",
   351  			Version:    v,
   352  			// NOTE: This local path and the other paths derived from it below
   353  			// can vary depending on how the registry is implemented. At the
   354  			// time of writing this test, registry.terraform.io returns
   355  			// git repository source addresses and so this path refers to the
   356  			// root of the git clone, but historically the registry referred
   357  			// to GitHub-provided tar archives which meant that there was an
   358  			// extra level of subdirectory here for the typical directory
   359  			// nesting in tar archives, which would've been reflected as
   360  			// an extra segment on this path. If this test fails due to an
   361  			// additional path segment in future, then a change to the upstream
   362  			// registry might be the root cause.
   363  			LocalPath: filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_a"),
   364  		},
   365  
   366  		// acctest_child_a.child_b
   367  		// (no download because it's a relative path inside acctest_child_a)
   368  		{
   369  			Name:       "Install",
   370  			ModuleAddr: "acctest_child_a.child_b",
   371  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_b"),
   372  		},
   373  
   374  		// acctest_child_b accesses //modules/child_b directly
   375  		{
   376  			Name:        "Download",
   377  			ModuleAddr:  "acctest_child_b",
   378  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws", // intentionally excludes the subdir because we're downloading the whole package here
   379  			Version:     v,
   380  		},
   381  		{
   382  			Name:       "Install",
   383  			ModuleAddr: "acctest_child_b",
   384  			Version:    v,
   385  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_b/modules/child_b"),
   386  		},
   387  
   388  		// acctest_root
   389  		{
   390  			Name:        "Download",
   391  			ModuleAddr:  "acctest_root",
   392  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws",
   393  			Version:     v,
   394  		},
   395  		{
   396  			Name:       "Install",
   397  			ModuleAddr: "acctest_root",
   398  			Version:    v,
   399  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root"),
   400  		},
   401  
   402  		// acctest_root.child_a
   403  		// (no download because it's a relative path inside acctest_root)
   404  		{
   405  			Name:       "Install",
   406  			ModuleAddr: "acctest_root.child_a",
   407  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_a"),
   408  		},
   409  
   410  		// acctest_root.child_a.child_b
   411  		// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
   412  		{
   413  			Name:       "Install",
   414  			ModuleAddr: "acctest_root.child_a.child_b",
   415  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_b"),
   416  		},
   417  	}
   418  
   419  	if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
   420  		t.Fatalf("wrong installer calls\n%s", diff)
   421  	}
   422  
   423  	//check that the registry reponses were cached
   424  	packageAddr := addrs.ModuleRegistryPackage{
   425  		Host:         svchost.Hostname("registry.terraform.io"),
   426  		Namespace:    "hashicorp",
   427  		Name:         "module-installer-acctest",
   428  		TargetSystem: "aws",
   429  	}
   430  	if _, ok := inst.registryPackageVersions[packageAddr]; !ok {
   431  		t.Errorf("module versions cache was not populated\ngot: %s\nwant: key hashicorp/module-installer-acctest/aws", spew.Sdump(inst.registryPackageVersions))
   432  	}
   433  	if _, ok := inst.registryPackageSources[moduleVersion{module: packageAddr, version: "0.0.1"}]; !ok {
   434  		t.Errorf("module download url cache was not populated\ngot: %s", spew.Sdump(inst.registryPackageSources))
   435  	}
   436  
   437  	loader, err := configload.NewLoader(&configload.Config{
   438  		ModulesDir: modulesDir,
   439  	})
   440  	if err != nil {
   441  		t.Fatal(err)
   442  	}
   443  
   444  	// Make sure the configuration is loadable now.
   445  	// (This ensures that correct information is recorded in the manifest.)
   446  	config, loadDiags := loader.LoadConfig(".")
   447  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   448  
   449  	wantTraces := map[string]string{
   450  		"":                             "in local caller for registry-modules",
   451  		"acctest_root":                 "in root module",
   452  		"acctest_root.child_a":         "in child_a module",
   453  		"acctest_root.child_a.child_b": "in child_b module",
   454  		"acctest_child_a":              "in child_a module",
   455  		"acctest_child_a.child_b":      "in child_b module",
   456  		"acctest_child_b":              "in child_b module",
   457  	}
   458  	gotTraces := map[string]string{}
   459  	config.DeepEach(func(c *configs.Config) {
   460  		path := strings.Join(c.Path, ".")
   461  		if c.Module.Variables["v"] == nil {
   462  			gotTraces[path] = "<missing>"
   463  			return
   464  		}
   465  		varDesc := c.Module.Variables["v"].Description
   466  		gotTraces[path] = varDesc
   467  	})
   468  	assertResultDeepEqual(t, gotTraces, wantTraces)
   469  
   470  }
   471  
   472  func TestLoaderInstallModules_goGetter(t *testing.T) {
   473  	if os.Getenv("TF_ACC") == "" {
   474  		t.Skip("this test accesses github.com; set TF_ACC=1 to run it")
   475  	}
   476  
   477  	fixtureDir := filepath.Clean("testdata/go-getter-modules")
   478  	tmpDir, done := tempChdir(t, fixtureDir)
   479  	// the module installer runs filepath.EvalSymlinks() on the destination
   480  	// directory before copying files, and the resultant directory is what is
   481  	// returned by the install hooks. Without this, tests could fail on machines
   482  	// where the default temp dir was a symlink.
   483  	dir, err := filepath.EvalSymlinks(tmpDir)
   484  	if err != nil {
   485  		t.Error(err)
   486  	}
   487  	defer done()
   488  
   489  	hooks := &testInstallHooks{}
   490  	modulesDir := filepath.Join(dir, ".terraform/modules")
   491  	inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
   492  	_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
   493  	assertNoDiagnostics(t, diags)
   494  
   495  	wantCalls := []testInstallHookCall{
   496  		// the configuration builder visits each level of calls in lexicographical
   497  		// order by name, so the following list is kept in the same order.
   498  
   499  		// acctest_child_a accesses //modules/child_a directly
   500  		{
   501  			Name:        "Download",
   502  			ModuleAddr:  "acctest_child_a",
   503  			PackageAddr: "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1", // intentionally excludes the subdir because we're downloading the whole repo here
   504  		},
   505  		{
   506  			Name:       "Install",
   507  			ModuleAddr: "acctest_child_a",
   508  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_a"),
   509  		},
   510  
   511  		// acctest_child_a.child_b
   512  		// (no download because it's a relative path inside acctest_child_a)
   513  		{
   514  			Name:       "Install",
   515  			ModuleAddr: "acctest_child_a.child_b",
   516  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_b"),
   517  		},
   518  
   519  		// acctest_child_b accesses //modules/child_b directly
   520  		{
   521  			Name:        "Download",
   522  			ModuleAddr:  "acctest_child_b",
   523  			PackageAddr: "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1", // intentionally excludes the subdir because we're downloading the whole package here
   524  		},
   525  		{
   526  			Name:       "Install",
   527  			ModuleAddr: "acctest_child_b",
   528  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_b/modules/child_b"),
   529  		},
   530  
   531  		// acctest_root
   532  		{
   533  			Name:        "Download",
   534  			ModuleAddr:  "acctest_root",
   535  			PackageAddr: "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1",
   536  		},
   537  		{
   538  			Name:       "Install",
   539  			ModuleAddr: "acctest_root",
   540  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root"),
   541  		},
   542  
   543  		// acctest_root.child_a
   544  		// (no download because it's a relative path inside acctest_root)
   545  		{
   546  			Name:       "Install",
   547  			ModuleAddr: "acctest_root.child_a",
   548  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_a"),
   549  		},
   550  
   551  		// acctest_root.child_a.child_b
   552  		// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
   553  		{
   554  			Name:       "Install",
   555  			ModuleAddr: "acctest_root.child_a.child_b",
   556  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_b"),
   557  		},
   558  	}
   559  
   560  	if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
   561  		t.Fatalf("wrong installer calls\n%s", diff)
   562  	}
   563  
   564  	loader, err := configload.NewLoader(&configload.Config{
   565  		ModulesDir: modulesDir,
   566  	})
   567  	if err != nil {
   568  		t.Fatal(err)
   569  	}
   570  
   571  	// Make sure the configuration is loadable now.
   572  	// (This ensures that correct information is recorded in the manifest.)
   573  	config, loadDiags := loader.LoadConfig(".")
   574  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   575  
   576  	wantTraces := map[string]string{
   577  		"":                             "in local caller for go-getter-modules",
   578  		"acctest_root":                 "in root module",
   579  		"acctest_root.child_a":         "in child_a module",
   580  		"acctest_root.child_a.child_b": "in child_b module",
   581  		"acctest_child_a":              "in child_a module",
   582  		"acctest_child_a.child_b":      "in child_b module",
   583  		"acctest_child_b":              "in child_b module",
   584  	}
   585  	gotTraces := map[string]string{}
   586  	config.DeepEach(func(c *configs.Config) {
   587  		path := strings.Join(c.Path, ".")
   588  		if c.Module.Variables["v"] == nil {
   589  			gotTraces[path] = "<missing>"
   590  			return
   591  		}
   592  		varDesc := c.Module.Variables["v"].Description
   593  		gotTraces[path] = varDesc
   594  	})
   595  	assertResultDeepEqual(t, gotTraces, wantTraces)
   596  
   597  }
   598  
   599  type testInstallHooks struct {
   600  	Calls []testInstallHookCall
   601  }
   602  
   603  type testInstallHookCall struct {
   604  	Name        string
   605  	ModuleAddr  string
   606  	PackageAddr string
   607  	Version     *version.Version
   608  	LocalPath   string
   609  }
   610  
   611  func (h *testInstallHooks) Download(moduleAddr, packageAddr string, version *version.Version) {
   612  	h.Calls = append(h.Calls, testInstallHookCall{
   613  		Name:        "Download",
   614  		ModuleAddr:  moduleAddr,
   615  		PackageAddr: packageAddr,
   616  		Version:     version,
   617  	})
   618  }
   619  
   620  func (h *testInstallHooks) Install(moduleAddr string, version *version.Version, localPath string) {
   621  	h.Calls = append(h.Calls, testInstallHookCall{
   622  		Name:       "Install",
   623  		ModuleAddr: moduleAddr,
   624  		Version:    version,
   625  		LocalPath:  localPath,
   626  	})
   627  }
   628  
   629  // tempChdir copies the contents of the given directory to a temporary
   630  // directory and changes the test process's current working directory to
   631  // point to that directory. Also returned is a function that should be
   632  // called at the end of the test (e.g. via "defer") to restore the previous
   633  // working directory.
   634  //
   635  // Tests using this helper cannot safely be run in parallel with other tests.
   636  func tempChdir(t *testing.T, sourceDir string) (string, func()) {
   637  	t.Helper()
   638  
   639  	tmpDir, err := ioutil.TempDir("", "terraform-configload")
   640  	if err != nil {
   641  		t.Fatalf("failed to create temporary directory: %s", err)
   642  		return "", nil
   643  	}
   644  
   645  	if err := copy.CopyDir(tmpDir, sourceDir); err != nil {
   646  		t.Fatalf("failed to copy fixture to temporary directory: %s", err)
   647  		return "", nil
   648  	}
   649  
   650  	oldDir, err := os.Getwd()
   651  	if err != nil {
   652  		t.Fatalf("failed to determine current working directory: %s", err)
   653  		return "", nil
   654  	}
   655  
   656  	err = os.Chdir(tmpDir)
   657  	if err != nil {
   658  		t.Fatalf("failed to switch to temp dir %s: %s", tmpDir, err)
   659  		return "", nil
   660  	}
   661  
   662  	// Most of the tests need this, so we'll make it just in case.
   663  	os.MkdirAll(filepath.Join(tmpDir, ".terraform/modules"), os.ModePerm)
   664  
   665  	t.Logf("tempChdir switched to %s after copying from %s", tmpDir, sourceDir)
   666  
   667  	return tmpDir, func() {
   668  		err := os.Chdir(oldDir)
   669  		if err != nil {
   670  			panic(fmt.Errorf("failed to restore previous working directory %s: %s", oldDir, err))
   671  		}
   672  
   673  		if os.Getenv("TF_CONFIGLOAD_TEST_KEEP_TMP") == "" {
   674  			os.RemoveAll(tmpDir)
   675  		}
   676  	}
   677  }
   678  
   679  func assertNoDiagnostics(t *testing.T, diags tfdiags.Diagnostics) bool {
   680  	t.Helper()
   681  	return assertDiagnosticCount(t, diags, 0)
   682  }
   683  
   684  func assertDiagnosticCount(t *testing.T, diags tfdiags.Diagnostics, want int) bool {
   685  	t.Helper()
   686  	if len(diags) != 0 {
   687  		t.Errorf("wrong number of diagnostics %d; want %d", len(diags), want)
   688  		for _, diag := range diags {
   689  			t.Logf("- %#v", diag)
   690  		}
   691  		return true
   692  	}
   693  	return false
   694  }
   695  
   696  func assertDiagnosticSummary(t *testing.T, diags tfdiags.Diagnostics, want string) bool {
   697  	t.Helper()
   698  
   699  	for _, diag := range diags {
   700  		if diag.Description().Summary == want {
   701  			return false
   702  		}
   703  	}
   704  
   705  	t.Errorf("missing diagnostic summary %q", want)
   706  	for _, diag := range diags {
   707  		t.Logf("- %#v", diag)
   708  	}
   709  	return true
   710  }
   711  
   712  func assertResultDeepEqual(t *testing.T, got, want interface{}) bool {
   713  	t.Helper()
   714  	if diff := deep.Equal(got, want); diff != nil {
   715  		for _, problem := range diff {
   716  			t.Errorf("%s", problem)
   717  		}
   718  		return true
   719  	}
   720  	return false
   721  }