github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/terraform/addrs"
    20  	"github.com/cycloidio/terraform/configs"
    21  	"github.com/cycloidio/terraform/configs/configload"
    22  	"github.com/cycloidio/terraform/copy"
    23  	"github.com/cycloidio/terraform/registry"
    24  	"github.com/cycloidio/terraform/tfdiags"
    25  
    26  	_ "github.com/cycloidio/terraform/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  		assertDiagnosticSummary(t, diags, "Invalid version constraint")
   196  	}
   197  }
   198  
   199  func TestModuleInstaller_invalidVersionConstraintGetter(t *testing.T) {
   200  	fixtureDir := filepath.Clean("testdata/invalid-version-constraint")
   201  	dir, done := tempChdir(t, fixtureDir)
   202  	defer done()
   203  
   204  	hooks := &testInstallHooks{}
   205  
   206  	modulesDir := filepath.Join(dir, ".terraform/modules")
   207  	inst := NewModuleInstaller(modulesDir, nil)
   208  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   209  
   210  	if !diags.HasErrors() {
   211  		t.Fatal("expected error")
   212  	} else {
   213  		assertDiagnosticSummary(t, diags, "Invalid version constraint")
   214  	}
   215  }
   216  
   217  func TestModuleInstaller_invalidVersionConstraintLocal(t *testing.T) {
   218  	fixtureDir := filepath.Clean("testdata/invalid-version-constraint-local")
   219  	dir, done := tempChdir(t, fixtureDir)
   220  	defer done()
   221  
   222  	hooks := &testInstallHooks{}
   223  
   224  	modulesDir := filepath.Join(dir, ".terraform/modules")
   225  	inst := NewModuleInstaller(modulesDir, nil)
   226  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   227  
   228  	if !diags.HasErrors() {
   229  		t.Fatal("expected error")
   230  	} else {
   231  		assertDiagnosticSummary(t, diags, "Invalid version constraint")
   232  	}
   233  }
   234  
   235  func TestModuleInstaller_symlink(t *testing.T) {
   236  	fixtureDir := filepath.Clean("testdata/local-module-symlink")
   237  	dir, done := tempChdir(t, fixtureDir)
   238  	defer done()
   239  
   240  	hooks := &testInstallHooks{}
   241  
   242  	modulesDir := filepath.Join(dir, ".terraform/modules")
   243  	inst := NewModuleInstaller(modulesDir, nil)
   244  	_, diags := inst.InstallModules(context.Background(), ".", false, hooks)
   245  	assertNoDiagnostics(t, diags)
   246  
   247  	wantCalls := []testInstallHookCall{
   248  		{
   249  			Name:        "Install",
   250  			ModuleAddr:  "child_a",
   251  			PackageAddr: "",
   252  			LocalPath:   "child_a",
   253  		},
   254  		{
   255  			Name:        "Install",
   256  			ModuleAddr:  "child_a.child_b",
   257  			PackageAddr: "",
   258  			LocalPath:   "child_a/child_b",
   259  		},
   260  	}
   261  
   262  	if assertResultDeepEqual(t, hooks.Calls, wantCalls) {
   263  		return
   264  	}
   265  
   266  	loader, err := configload.NewLoader(&configload.Config{
   267  		ModulesDir: modulesDir,
   268  	})
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// Make sure the configuration is loadable now.
   274  	// (This ensures that correct information is recorded in the manifest.)
   275  	config, loadDiags := loader.LoadConfig(".")
   276  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   277  
   278  	wantTraces := map[string]string{
   279  		"":                "in root module",
   280  		"child_a":         "in child_a module",
   281  		"child_a.child_b": "in child_b module",
   282  	}
   283  	gotTraces := map[string]string{}
   284  	config.DeepEach(func(c *configs.Config) {
   285  		path := strings.Join(c.Path, ".")
   286  		if c.Module.Variables["v"] == nil {
   287  			gotTraces[path] = "<missing>"
   288  			return
   289  		}
   290  		varDesc := c.Module.Variables["v"].Description
   291  		gotTraces[path] = varDesc
   292  	})
   293  	assertResultDeepEqual(t, gotTraces, wantTraces)
   294  }
   295  
   296  func TestLoaderInstallModules_registry(t *testing.T) {
   297  	if os.Getenv("TF_ACC") == "" {
   298  		t.Skip("this test accesses registry.terraform.io and github.com; set TF_ACC=1 to run it")
   299  	}
   300  
   301  	fixtureDir := filepath.Clean("testdata/registry-modules")
   302  	tmpDir, done := tempChdir(t, fixtureDir)
   303  	// the module installer runs filepath.EvalSymlinks() on the destination
   304  	// directory before copying files, and the resultant directory is what is
   305  	// returned by the install hooks. Without this, tests could fail on machines
   306  	// where the default temp dir was a symlink.
   307  	dir, err := filepath.EvalSymlinks(tmpDir)
   308  	if err != nil {
   309  		t.Error(err)
   310  	}
   311  
   312  	defer done()
   313  
   314  	hooks := &testInstallHooks{}
   315  	modulesDir := filepath.Join(dir, ".terraform/modules")
   316  	inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
   317  	_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
   318  	assertNoDiagnostics(t, diags)
   319  
   320  	v := version.Must(version.NewVersion("0.0.1"))
   321  
   322  	wantCalls := []testInstallHookCall{
   323  		// the configuration builder visits each level of calls in lexicographical
   324  		// order by name, so the following list is kept in the same order.
   325  
   326  		// acctest_child_a accesses //modules/child_a directly
   327  		{
   328  			Name:        "Download",
   329  			ModuleAddr:  "acctest_child_a",
   330  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws", // intentionally excludes the subdir because we're downloading the whole package here
   331  			Version:     v,
   332  		},
   333  		{
   334  			Name:       "Install",
   335  			ModuleAddr: "acctest_child_a",
   336  			Version:    v,
   337  			// NOTE: This local path and the other paths derived from it below
   338  			// can vary depending on how the registry is implemented. At the
   339  			// time of writing this test, registry.terraform.io returns
   340  			// git repository source addresses and so this path refers to the
   341  			// root of the git clone, but historically the registry referred
   342  			// to GitHub-provided tar archives which meant that there was an
   343  			// extra level of subdirectory here for the typical directory
   344  			// nesting in tar archives, which would've been reflected as
   345  			// an extra segment on this path. If this test fails due to an
   346  			// additional path segment in future, then a change to the upstream
   347  			// registry might be the root cause.
   348  			LocalPath: filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_a"),
   349  		},
   350  
   351  		// acctest_child_a.child_b
   352  		// (no download because it's a relative path inside acctest_child_a)
   353  		{
   354  			Name:       "Install",
   355  			ModuleAddr: "acctest_child_a.child_b",
   356  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_b"),
   357  		},
   358  
   359  		// acctest_child_b accesses //modules/child_b directly
   360  		{
   361  			Name:        "Download",
   362  			ModuleAddr:  "acctest_child_b",
   363  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws", // intentionally excludes the subdir because we're downloading the whole package here
   364  			Version:     v,
   365  		},
   366  		{
   367  			Name:       "Install",
   368  			ModuleAddr: "acctest_child_b",
   369  			Version:    v,
   370  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_b/modules/child_b"),
   371  		},
   372  
   373  		// acctest_root
   374  		{
   375  			Name:        "Download",
   376  			ModuleAddr:  "acctest_root",
   377  			PackageAddr: "registry.terraform.io/hashicorp/module-installer-acctest/aws",
   378  			Version:     v,
   379  		},
   380  		{
   381  			Name:       "Install",
   382  			ModuleAddr: "acctest_root",
   383  			Version:    v,
   384  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root"),
   385  		},
   386  
   387  		// acctest_root.child_a
   388  		// (no download because it's a relative path inside acctest_root)
   389  		{
   390  			Name:       "Install",
   391  			ModuleAddr: "acctest_root.child_a",
   392  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_a"),
   393  		},
   394  
   395  		// acctest_root.child_a.child_b
   396  		// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
   397  		{
   398  			Name:       "Install",
   399  			ModuleAddr: "acctest_root.child_a.child_b",
   400  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_b"),
   401  		},
   402  	}
   403  
   404  	if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
   405  		t.Fatalf("wrong installer calls\n%s", diff)
   406  	}
   407  
   408  	//check that the registry reponses were cached
   409  	packageAddr := addrs.ModuleRegistryPackage{
   410  		Host:         svchost.Hostname("registry.terraform.io"),
   411  		Namespace:    "hashicorp",
   412  		Name:         "module-installer-acctest",
   413  		TargetSystem: "aws",
   414  	}
   415  	if _, ok := inst.registryPackageVersions[packageAddr]; !ok {
   416  		t.Errorf("module versions cache was not populated\ngot: %s\nwant: key hashicorp/module-installer-acctest/aws", spew.Sdump(inst.registryPackageVersions))
   417  	}
   418  	if _, ok := inst.registryPackageSources[moduleVersion{module: packageAddr, version: "0.0.1"}]; !ok {
   419  		t.Errorf("module download url cache was not populated\ngot: %s", spew.Sdump(inst.registryPackageSources))
   420  	}
   421  
   422  	loader, err := configload.NewLoader(&configload.Config{
   423  		ModulesDir: modulesDir,
   424  	})
   425  	if err != nil {
   426  		t.Fatal(err)
   427  	}
   428  
   429  	// Make sure the configuration is loadable now.
   430  	// (This ensures that correct information is recorded in the manifest.)
   431  	config, loadDiags := loader.LoadConfig(".")
   432  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   433  
   434  	wantTraces := map[string]string{
   435  		"":                             "in local caller for registry-modules",
   436  		"acctest_root":                 "in root module",
   437  		"acctest_root.child_a":         "in child_a module",
   438  		"acctest_root.child_a.child_b": "in child_b module",
   439  		"acctest_child_a":              "in child_a module",
   440  		"acctest_child_a.child_b":      "in child_b module",
   441  		"acctest_child_b":              "in child_b module",
   442  	}
   443  	gotTraces := map[string]string{}
   444  	config.DeepEach(func(c *configs.Config) {
   445  		path := strings.Join(c.Path, ".")
   446  		if c.Module.Variables["v"] == nil {
   447  			gotTraces[path] = "<missing>"
   448  			return
   449  		}
   450  		varDesc := c.Module.Variables["v"].Description
   451  		gotTraces[path] = varDesc
   452  	})
   453  	assertResultDeepEqual(t, gotTraces, wantTraces)
   454  
   455  }
   456  
   457  func TestLoaderInstallModules_goGetter(t *testing.T) {
   458  	if os.Getenv("TF_ACC") == "" {
   459  		t.Skip("this test accesses github.com; set TF_ACC=1 to run it")
   460  	}
   461  
   462  	fixtureDir := filepath.Clean("testdata/go-getter-modules")
   463  	tmpDir, done := tempChdir(t, fixtureDir)
   464  	// the module installer runs filepath.EvalSymlinks() on the destination
   465  	// directory before copying files, and the resultant directory is what is
   466  	// returned by the install hooks. Without this, tests could fail on machines
   467  	// where the default temp dir was a symlink.
   468  	dir, err := filepath.EvalSymlinks(tmpDir)
   469  	if err != nil {
   470  		t.Error(err)
   471  	}
   472  	defer done()
   473  
   474  	hooks := &testInstallHooks{}
   475  	modulesDir := filepath.Join(dir, ".terraform/modules")
   476  	inst := NewModuleInstaller(modulesDir, registry.NewClient(nil, nil))
   477  	_, diags := inst.InstallModules(context.Background(), dir, false, hooks)
   478  	assertNoDiagnostics(t, diags)
   479  
   480  	wantCalls := []testInstallHookCall{
   481  		// the configuration builder visits each level of calls in lexicographical
   482  		// order by name, so the following list is kept in the same order.
   483  
   484  		// acctest_child_a accesses //modules/child_a directly
   485  		{
   486  			Name:        "Download",
   487  			ModuleAddr:  "acctest_child_a",
   488  			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
   489  		},
   490  		{
   491  			Name:       "Install",
   492  			ModuleAddr: "acctest_child_a",
   493  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_a"),
   494  		},
   495  
   496  		// acctest_child_a.child_b
   497  		// (no download because it's a relative path inside acctest_child_a)
   498  		{
   499  			Name:       "Install",
   500  			ModuleAddr: "acctest_child_a.child_b",
   501  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_a/modules/child_b"),
   502  		},
   503  
   504  		// acctest_child_b accesses //modules/child_b directly
   505  		{
   506  			Name:        "Download",
   507  			ModuleAddr:  "acctest_child_b",
   508  			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
   509  		},
   510  		{
   511  			Name:       "Install",
   512  			ModuleAddr: "acctest_child_b",
   513  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_child_b/modules/child_b"),
   514  		},
   515  
   516  		// acctest_root
   517  		{
   518  			Name:        "Download",
   519  			ModuleAddr:  "acctest_root",
   520  			PackageAddr: "git::https://github.com/hashicorp/terraform-aws-module-installer-acctest.git?ref=v0.0.1",
   521  		},
   522  		{
   523  			Name:       "Install",
   524  			ModuleAddr: "acctest_root",
   525  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root"),
   526  		},
   527  
   528  		// acctest_root.child_a
   529  		// (no download because it's a relative path inside acctest_root)
   530  		{
   531  			Name:       "Install",
   532  			ModuleAddr: "acctest_root.child_a",
   533  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_a"),
   534  		},
   535  
   536  		// acctest_root.child_a.child_b
   537  		// (no download because it's a relative path inside acctest_root, via acctest_root.child_a)
   538  		{
   539  			Name:       "Install",
   540  			ModuleAddr: "acctest_root.child_a.child_b",
   541  			LocalPath:  filepath.Join(dir, ".terraform/modules/acctest_root/modules/child_b"),
   542  		},
   543  	}
   544  
   545  	if diff := cmp.Diff(wantCalls, hooks.Calls); diff != "" {
   546  		t.Fatalf("wrong installer calls\n%s", diff)
   547  	}
   548  
   549  	loader, err := configload.NewLoader(&configload.Config{
   550  		ModulesDir: modulesDir,
   551  	})
   552  	if err != nil {
   553  		t.Fatal(err)
   554  	}
   555  
   556  	// Make sure the configuration is loadable now.
   557  	// (This ensures that correct information is recorded in the manifest.)
   558  	config, loadDiags := loader.LoadConfig(".")
   559  	assertNoDiagnostics(t, tfdiags.Diagnostics{}.Append(loadDiags))
   560  
   561  	wantTraces := map[string]string{
   562  		"":                             "in local caller for go-getter-modules",
   563  		"acctest_root":                 "in root module",
   564  		"acctest_root.child_a":         "in child_a module",
   565  		"acctest_root.child_a.child_b": "in child_b module",
   566  		"acctest_child_a":              "in child_a module",
   567  		"acctest_child_a.child_b":      "in child_b module",
   568  		"acctest_child_b":              "in child_b module",
   569  	}
   570  	gotTraces := map[string]string{}
   571  	config.DeepEach(func(c *configs.Config) {
   572  		path := strings.Join(c.Path, ".")
   573  		if c.Module.Variables["v"] == nil {
   574  			gotTraces[path] = "<missing>"
   575  			return
   576  		}
   577  		varDesc := c.Module.Variables["v"].Description
   578  		gotTraces[path] = varDesc
   579  	})
   580  	assertResultDeepEqual(t, gotTraces, wantTraces)
   581  
   582  }
   583  
   584  type testInstallHooks struct {
   585  	Calls []testInstallHookCall
   586  }
   587  
   588  type testInstallHookCall struct {
   589  	Name        string
   590  	ModuleAddr  string
   591  	PackageAddr string
   592  	Version     *version.Version
   593  	LocalPath   string
   594  }
   595  
   596  func (h *testInstallHooks) Download(moduleAddr, packageAddr string, version *version.Version) {
   597  	h.Calls = append(h.Calls, testInstallHookCall{
   598  		Name:        "Download",
   599  		ModuleAddr:  moduleAddr,
   600  		PackageAddr: packageAddr,
   601  		Version:     version,
   602  	})
   603  }
   604  
   605  func (h *testInstallHooks) Install(moduleAddr string, version *version.Version, localPath string) {
   606  	h.Calls = append(h.Calls, testInstallHookCall{
   607  		Name:       "Install",
   608  		ModuleAddr: moduleAddr,
   609  		Version:    version,
   610  		LocalPath:  localPath,
   611  	})
   612  }
   613  
   614  // tempChdir copies the contents of the given directory to a temporary
   615  // directory and changes the test process's current working directory to
   616  // point to that directory. Also returned is a function that should be
   617  // called at the end of the test (e.g. via "defer") to restore the previous
   618  // working directory.
   619  //
   620  // Tests using this helper cannot safely be run in parallel with other tests.
   621  func tempChdir(t *testing.T, sourceDir string) (string, func()) {
   622  	t.Helper()
   623  
   624  	tmpDir, err := ioutil.TempDir("", "terraform-configload")
   625  	if err != nil {
   626  		t.Fatalf("failed to create temporary directory: %s", err)
   627  		return "", nil
   628  	}
   629  
   630  	if err := copy.CopyDir(tmpDir, sourceDir); err != nil {
   631  		t.Fatalf("failed to copy fixture to temporary directory: %s", err)
   632  		return "", nil
   633  	}
   634  
   635  	oldDir, err := os.Getwd()
   636  	if err != nil {
   637  		t.Fatalf("failed to determine current working directory: %s", err)
   638  		return "", nil
   639  	}
   640  
   641  	err = os.Chdir(tmpDir)
   642  	if err != nil {
   643  		t.Fatalf("failed to switch to temp dir %s: %s", tmpDir, err)
   644  		return "", nil
   645  	}
   646  
   647  	// Most of the tests need this, so we'll make it just in case.
   648  	os.MkdirAll(filepath.Join(tmpDir, ".terraform/modules"), os.ModePerm)
   649  
   650  	t.Logf("tempChdir switched to %s after copying from %s", tmpDir, sourceDir)
   651  
   652  	return tmpDir, func() {
   653  		err := os.Chdir(oldDir)
   654  		if err != nil {
   655  			panic(fmt.Errorf("failed to restore previous working directory %s: %s", oldDir, err))
   656  		}
   657  
   658  		if os.Getenv("TF_CONFIGLOAD_TEST_KEEP_TMP") == "" {
   659  			os.RemoveAll(tmpDir)
   660  		}
   661  	}
   662  }
   663  
   664  func assertNoDiagnostics(t *testing.T, diags tfdiags.Diagnostics) bool {
   665  	t.Helper()
   666  	return assertDiagnosticCount(t, diags, 0)
   667  }
   668  
   669  func assertDiagnosticCount(t *testing.T, diags tfdiags.Diagnostics, want int) bool {
   670  	t.Helper()
   671  	if len(diags) != 0 {
   672  		t.Errorf("wrong number of diagnostics %d; want %d", len(diags), want)
   673  		for _, diag := range diags {
   674  			t.Logf("- %#v", diag)
   675  		}
   676  		return true
   677  	}
   678  	return false
   679  }
   680  
   681  func assertDiagnosticSummary(t *testing.T, diags tfdiags.Diagnostics, want string) bool {
   682  	t.Helper()
   683  
   684  	for _, diag := range diags {
   685  		if diag.Description().Summary == want {
   686  			return false
   687  		}
   688  	}
   689  
   690  	t.Errorf("missing diagnostic summary %q", want)
   691  	for _, diag := range diags {
   692  		t.Logf("- %#v", diag)
   693  	}
   694  	return true
   695  }
   696  
   697  func assertResultDeepEqual(t *testing.T, got, want interface{}) bool {
   698  	t.Helper()
   699  	if diff := deep.Equal(got, want); diff != nil {
   700  		for _, problem := range diff {
   701  			t.Errorf("%s", problem)
   702  		}
   703  		return true
   704  	}
   705  	return false
   706  }