github.com/opentofu/opentofu@v1.7.1/internal/configs/configload/loader_load_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package configload 7 8 import ( 9 "path/filepath" 10 "reflect" 11 "sort" 12 "strings" 13 "testing" 14 15 "github.com/davecgh/go-spew/spew" 16 "github.com/zclconf/go-cty/cty" 17 18 "github.com/opentofu/opentofu/internal/configs" 19 ) 20 21 func TestLoaderLoadConfig_okay(t *testing.T) { 22 fixtureDir := filepath.Clean("testdata/already-installed") 23 loader, err := NewLoader(&Config{ 24 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 25 }) 26 if err != nil { 27 t.Fatalf("unexpected error from NewLoader: %s", err) 28 } 29 30 cfg, diags := loader.LoadConfig(fixtureDir) 31 assertNoDiagnostics(t, diags) 32 if cfg == nil { 33 t.Fatalf("config is nil; want non-nil") 34 } 35 36 var gotPaths []string 37 cfg.DeepEach(func(c *configs.Config) { 38 gotPaths = append(gotPaths, strings.Join(c.Path, ".")) 39 }) 40 sort.Strings(gotPaths) 41 wantPaths := []string{ 42 "", // root module 43 "child_a", 44 "child_a.child_c", 45 "child_b", 46 "child_b.child_d", 47 } 48 49 if !reflect.DeepEqual(gotPaths, wantPaths) { 50 t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths)) 51 } 52 53 t.Run("child_a.child_c output", func(t *testing.T) { 54 output := cfg.Children["child_a"].Children["child_c"].Module.Outputs["hello"] 55 got, diags := output.Expr.Value(nil) 56 assertNoDiagnostics(t, diags) 57 assertResultCtyEqual(t, got, cty.StringVal("Hello from child_c")) 58 }) 59 t.Run("child_b.child_d output", func(t *testing.T) { 60 output := cfg.Children["child_b"].Children["child_d"].Module.Outputs["hello"] 61 got, diags := output.Expr.Value(nil) 62 assertNoDiagnostics(t, diags) 63 assertResultCtyEqual(t, got, cty.StringVal("Hello from child_d")) 64 }) 65 } 66 67 func TestLoaderLoadConfig_addVersion(t *testing.T) { 68 // This test is for what happens when there is a version constraint added 69 // to a module that previously didn't have one. 70 fixtureDir := filepath.Clean("testdata/add-version-constraint") 71 loader, err := NewLoader(&Config{ 72 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 73 }) 74 if err != nil { 75 t.Fatalf("unexpected error from NewLoader: %s", err) 76 } 77 78 _, diags := loader.LoadConfig(fixtureDir) 79 if !diags.HasErrors() { 80 t.Fatalf("success; want error") 81 } 82 got := diags.Error() 83 want := "Module version requirements have changed" 84 if !strings.Contains(got, want) { 85 t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want) 86 } 87 } 88 89 func TestLoaderLoadConfig_loadDiags(t *testing.T) { 90 // building a config which didn't load correctly may cause configs to panic 91 fixtureDir := filepath.Clean("testdata/invalid-names") 92 loader, err := NewLoader(&Config{ 93 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 94 }) 95 if err != nil { 96 t.Fatalf("unexpected error from NewLoader: %s", err) 97 } 98 99 cfg, diags := loader.LoadConfig(fixtureDir) 100 if !diags.HasErrors() { 101 t.Fatal("success; want error") 102 } 103 104 if cfg == nil { 105 t.Fatal("partial config not returned with diagnostics") 106 } 107 108 if cfg.Module == nil { 109 t.Fatal("expected config module") 110 } 111 } 112 113 func TestLoaderLoadConfig_loadDiagsFromSubmodules(t *testing.T) { 114 // building a config which didn't load correctly may cause configs to panic 115 fixtureDir := filepath.Clean("testdata/invalid-names-in-submodules") 116 loader, err := NewLoader(&Config{ 117 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 118 }) 119 if err != nil { 120 t.Fatalf("unexpected error from NewLoader: %s", err) 121 } 122 123 cfg, diags := loader.LoadConfig(fixtureDir) 124 if !diags.HasErrors() { 125 t.Fatalf("loading succeeded; want an error") 126 } 127 if got, want := diags.Error(), " Invalid provider local name"; !strings.Contains(got, want) { 128 t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got) 129 } 130 131 if cfg == nil { 132 t.Fatal("partial config not returned with diagnostics") 133 } 134 135 if cfg.Module == nil { 136 t.Fatal("expected config module") 137 } 138 } 139 140 func TestLoaderLoadConfig_childProviderGrandchildCount(t *testing.T) { 141 // This test is focused on the specific situation where: 142 // - A child module contains a nested provider block, which is no longer 143 // recommended but supported for backward-compatibility. 144 // - A child of that child does _not_ contain a nested provider block, 145 // and is called with "count" (would also apply to "for_each" and 146 // "depends_on"). 147 // It isn't valid to use "count" with a module that _itself_ contains 148 // a provider configuration, but it _is_ valid for a module with a 149 // provider configuration to call another module with count. We previously 150 // botched this rule and so this is a regression test to cover the 151 // solution to that mistake: 152 // https://github.com/hashicorp/terraform/issues/31081 153 154 // Since this test is based on success rather than failure and it's 155 // covering a relatively large set of code where only a small part 156 // contributes to the test, we'll make sure to test both the success and 157 // failure cases here so that we'll have a better chance of noticing if a 158 // future change makes this succeed only because we've reorganized the code 159 // so that the check isn't happening at all anymore. 160 // 161 // If the "not okay" subtest fails, you should also be skeptical about 162 // whether the "okay" subtest is still valid, even if it happens to 163 // still be passing. 164 t.Run("okay", func(t *testing.T) { 165 fixtureDir := filepath.Clean("testdata/child-provider-grandchild-count") 166 loader, err := NewLoader(&Config{ 167 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 168 }) 169 if err != nil { 170 t.Fatalf("unexpected error from NewLoader: %s", err) 171 } 172 173 cfg, diags := loader.LoadConfig(fixtureDir) 174 assertNoDiagnostics(t, diags) 175 if cfg == nil { 176 t.Fatalf("config is nil; want non-nil") 177 } 178 179 var gotPaths []string 180 cfg.DeepEach(func(c *configs.Config) { 181 gotPaths = append(gotPaths, strings.Join(c.Path, ".")) 182 }) 183 sort.Strings(gotPaths) 184 wantPaths := []string{ 185 "", // root module 186 "child", 187 "child.grandchild", 188 } 189 190 if !reflect.DeepEqual(gotPaths, wantPaths) { 191 t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths)) 192 } 193 }) 194 t.Run("not okay", func(t *testing.T) { 195 fixtureDir := filepath.Clean("testdata/child-provider-child-count") 196 loader, err := NewLoader(&Config{ 197 ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), 198 }) 199 if err != nil { 200 t.Fatalf("unexpected error from NewLoader: %s", err) 201 } 202 203 _, diags := loader.LoadConfig(fixtureDir) 204 if !diags.HasErrors() { 205 t.Fatalf("loading succeeded; want an error") 206 } 207 if got, want := diags.Error(), "Module is incompatible with count, for_each, and depends_on"; !strings.Contains(got, want) { 208 t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got) 209 } 210 }) 211 212 }