package configload import ( "path/filepath" "reflect" "sort" "strings" "testing" "github.com/davecgh/go-spew/spew" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/configs" ) func TestLoaderLoadConfig_okay(t *testing.T) { fixtureDir := filepath.Clean("testdata/already-installed") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } cfg, diags := loader.LoadConfig(fixtureDir) assertNoDiagnostics(t, diags) if cfg == nil { t.Fatalf("config is nil; want non-nil") } var gotPaths []string cfg.DeepEach(func(c *configs.Config) { gotPaths = append(gotPaths, strings.Join(c.Path, ".")) }) sort.Strings(gotPaths) wantPaths := []string{ "", // root module "child_a", "child_a.child_c", "child_b", "child_b.child_d", } if !reflect.DeepEqual(gotPaths, wantPaths) { t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths)) } t.Run("child_a.child_c output", func(t *testing.T) { output := cfg.Children["child_a"].Children["child_c"].Module.Outputs["hello"] got, diags := output.Expr.Value(nil) assertNoDiagnostics(t, diags) assertResultCtyEqual(t, got, cty.StringVal("Hello from child_c")) }) t.Run("child_b.child_d output", func(t *testing.T) { output := cfg.Children["child_b"].Children["child_d"].Module.Outputs["hello"] got, diags := output.Expr.Value(nil) assertNoDiagnostics(t, diags) assertResultCtyEqual(t, got, cty.StringVal("Hello from child_d")) }) } func TestLoaderLoadConfig_addVersion(t *testing.T) { // This test is for what happens when there is a version constraint added // to a module that previously didn't have one. fixtureDir := filepath.Clean("testdata/add-version-constraint") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } _, diags := loader.LoadConfig(fixtureDir) if !diags.HasErrors() { t.Fatalf("success; want error") } got := diags.Error() want := "Module version requirements have changed" if !strings.Contains(got, want) { t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want) } } func TestLoaderLoadConfig_loadDiags(t *testing.T) { // building a config which didn't load correctly may cause configs to panic fixtureDir := filepath.Clean("testdata/invalid-names") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } cfg, diags := loader.LoadConfig(fixtureDir) if !diags.HasErrors() { t.Fatal("success; want error") } if cfg == nil { t.Fatal("partial config not returned with diagnostics") } if cfg.Module == nil { t.Fatal("expected config module") } } func TestLoaderLoadConfig_loadDiagsFromSubmodules(t *testing.T) { // building a config which didn't load correctly may cause configs to panic fixtureDir := filepath.Clean("testdata/invalid-names-in-submodules") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } cfg, diags := loader.LoadConfig(fixtureDir) if !diags.HasErrors() { t.Fatalf("loading succeeded; want an error") } if got, want := diags.Error(), " Invalid provider local name"; !strings.Contains(got, want) { t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got) } if cfg == nil { t.Fatal("partial config not returned with diagnostics") } if cfg.Module == nil { t.Fatal("expected config module") } } func TestLoaderLoadConfig_childProviderGrandchildCount(t *testing.T) { // This test is focused on the specific situation where: // - A child module contains a nested provider block, which is no longer // recommended but supported for backward-compatibility. // - A child of that child does _not_ contain a nested provider block, // and is called with "count" (would also apply to "for_each" and // "depends_on"). // It isn't valid to use "count" with a module that _itself_ contains // a provider configuration, but it _is_ valid for a module with a // provider configuration to call another module with count. We previously // botched this rule and so this is a regression test to cover the // solution to that mistake: // https://github.com/hashicorp/terraform/issues/31081 // Since this test is based on success rather than failure and it's // covering a relatively large set of code where only a small part // contributes to the test, we'll make sure to test both the success and // failure cases here so that we'll have a better chance of noticing if a // future change makes this succeed only because we've reorganized the code // so that the check isn't happening at all anymore. // // If the "not okay" subtest fails, you should also be skeptical about // whether the "okay" subtest is still valid, even if it happens to // still be passing. t.Run("okay", func(t *testing.T) { fixtureDir := filepath.Clean("testdata/child-provider-grandchild-count") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } cfg, diags := loader.LoadConfig(fixtureDir) assertNoDiagnostics(t, diags) if cfg == nil { t.Fatalf("config is nil; want non-nil") } var gotPaths []string cfg.DeepEach(func(c *configs.Config) { gotPaths = append(gotPaths, strings.Join(c.Path, ".")) }) sort.Strings(gotPaths) wantPaths := []string{ "", // root module "child", "child.grandchild", } if !reflect.DeepEqual(gotPaths, wantPaths) { t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths)) } }) t.Run("not okay", func(t *testing.T) { fixtureDir := filepath.Clean("testdata/child-provider-child-count") loader, err := NewLoader(&Config{ ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), }) if err != nil { t.Fatalf("unexpected error from NewLoader: %s", err) } _, diags := loader.LoadConfig(fixtureDir) if !diags.HasErrors() { t.Fatalf("loading succeeded; want an error") } if got, want := diags.Error(), "Module is incompatible with count, for_each, and depends_on"; !strings.Contains(got, want) { t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got) } }) }