kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/config_build_test.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "path/filepath" 7 "reflect" 8 "sort" 9 "strings" 10 "testing" 11 12 "github.com/davecgh/go-spew/spew" 13 14 version "github.com/hashicorp/go-version" 15 "github.com/hashicorp/hcl/v2" 16 ) 17 18 func TestBuildConfig(t *testing.T) { 19 parser := NewParser(nil) 20 mod, diags := parser.LoadConfigDir("testdata/config-build") 21 assertNoDiagnostics(t, diags) 22 if mod == nil { 23 t.Fatal("got nil root module; want non-nil") 24 } 25 26 versionI := 0 27 cfg, diags := BuildConfig(mod, ModuleWalkerFunc( 28 func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 29 // For the sake of this test we're going to just treat our 30 // SourceAddr as a path relative to our fixture directory. 31 // A "real" implementation of ModuleWalker should accept the 32 // various different source address syntaxes Terraform supports. 33 sourcePath := filepath.Join("testdata/config-build", req.SourceAddr.String()) 34 35 mod, diags := parser.LoadConfigDir(sourcePath) 36 version, _ := version.NewVersion(fmt.Sprintf("1.0.%d", versionI)) 37 versionI++ 38 return mod, version, diags 39 }, 40 )) 41 assertNoDiagnostics(t, diags) 42 if cfg == nil { 43 t.Fatal("got nil config; want non-nil") 44 } 45 46 var got []string 47 cfg.DeepEach(func(c *Config) { 48 got = append(got, fmt.Sprintf("%s %s", strings.Join(c.Path, "."), c.Version)) 49 }) 50 sort.Strings(got) 51 want := []string{ 52 " <nil>", 53 "child_a 1.0.0", 54 "child_a.child_c 1.0.1", 55 "child_b 1.0.2", 56 "child_b.child_c 1.0.3", 57 } 58 59 if !reflect.DeepEqual(got, want) { 60 t.Fatalf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) 61 } 62 63 if _, exists := cfg.Children["child_a"].Children["child_c"].Module.Outputs["hello"]; !exists { 64 t.Fatalf("missing output 'hello' in child_a.child_c") 65 } 66 if _, exists := cfg.Children["child_b"].Children["child_c"].Module.Outputs["hello"]; !exists { 67 t.Fatalf("missing output 'hello' in child_b.child_c") 68 } 69 if cfg.Children["child_a"].Children["child_c"].Module == cfg.Children["child_b"].Children["child_c"].Module { 70 t.Fatalf("child_a.child_c is same object as child_b.child_c; should not be") 71 } 72 } 73 74 func TestBuildConfigDiags(t *testing.T) { 75 parser := NewParser(nil) 76 mod, diags := parser.LoadConfigDir("testdata/nested-errors") 77 assertNoDiagnostics(t, diags) 78 if mod == nil { 79 t.Fatal("got nil root module; want non-nil") 80 } 81 82 versionI := 0 83 cfg, diags := BuildConfig(mod, ModuleWalkerFunc( 84 func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 85 // For the sake of this test we're going to just treat our 86 // SourceAddr as a path relative to our fixture directory. 87 // A "real" implementation of ModuleWalker should accept the 88 // various different source address syntaxes Terraform supports. 89 sourcePath := filepath.Join("testdata/nested-errors", req.SourceAddr.String()) 90 91 mod, diags := parser.LoadConfigDir(sourcePath) 92 version, _ := version.NewVersion(fmt.Sprintf("1.0.%d", versionI)) 93 versionI++ 94 return mod, version, diags 95 }, 96 )) 97 98 wantDiag := `testdata/nested-errors/child_c/child_c.tf:5,1-8: ` + 99 `Unsupported block type; Blocks of type "invalid" are not expected here.` 100 assertExactDiagnostics(t, diags, []string{wantDiag}) 101 102 // we should still have module structure loaded 103 var got []string 104 cfg.DeepEach(func(c *Config) { 105 got = append(got, fmt.Sprintf("%s %s", strings.Join(c.Path, "."), c.Version)) 106 }) 107 sort.Strings(got) 108 want := []string{ 109 " <nil>", 110 "child_a 1.0.0", 111 "child_a.child_c 1.0.1", 112 } 113 114 if !reflect.DeepEqual(got, want) { 115 t.Fatalf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) 116 } 117 } 118 119 func TestBuildConfigChildModuleBackend(t *testing.T) { 120 parser := NewParser(nil) 121 mod, diags := parser.LoadConfigDir("testdata/nested-backend-warning") 122 assertNoDiagnostics(t, diags) 123 if mod == nil { 124 t.Fatal("got nil root module; want non-nil") 125 } 126 127 cfg, diags := BuildConfig(mod, ModuleWalkerFunc( 128 func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 129 // For the sake of this test we're going to just treat our 130 // SourceAddr as a path relative to our fixture directory. 131 // A "real" implementation of ModuleWalker should accept the 132 // various different source address syntaxes Terraform supports. 133 sourcePath := filepath.Join("testdata/nested-backend-warning", req.SourceAddr.String()) 134 135 mod, diags := parser.LoadConfigDir(sourcePath) 136 version, _ := version.NewVersion("1.0.0") 137 return mod, version, diags 138 }, 139 )) 140 141 assertDiagnosticSummary(t, diags, "Backend configuration ignored") 142 143 // we should still have module structure loaded 144 var got []string 145 cfg.DeepEach(func(c *Config) { 146 got = append(got, fmt.Sprintf("%s %s", strings.Join(c.Path, "."), c.Version)) 147 }) 148 sort.Strings(got) 149 want := []string{ 150 " <nil>", 151 "child 1.0.0", 152 } 153 154 if !reflect.DeepEqual(got, want) { 155 t.Fatalf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) 156 } 157 } 158 159 func TestBuildConfigInvalidModules(t *testing.T) { 160 testDir := "testdata/config-diagnostics" 161 dirs, err := ioutil.ReadDir(testDir) 162 if err != nil { 163 t.Fatal(err) 164 } 165 166 for _, info := range dirs { 167 name := info.Name() 168 t.Run(name, func(t *testing.T) { 169 parser := NewParser(nil) 170 path := filepath.Join(testDir, name) 171 172 mod, diags := parser.LoadConfigDir(path) 173 if diags.HasErrors() { 174 // these tests should only trigger errors that are caught in 175 // the config loader. 176 t.Errorf("error loading config dir") 177 for _, diag := range diags { 178 t.Logf("- %s", diag) 179 } 180 } 181 182 readDiags := func(data []byte, _ error) []string { 183 var expected []string 184 for _, s := range strings.Split(string(data), "\n") { 185 msg := strings.TrimSpace(s) 186 msg = strings.ReplaceAll(msg, `\n`, "\n") 187 if msg != "" { 188 expected = append(expected, msg) 189 } 190 } 191 return expected 192 } 193 194 // Load expected errors and warnings. 195 // Each line in the file is matched as a substring against the 196 // diagnostic outputs. 197 // Capturing part of the path and source range in the message lets 198 // us also ensure the diagnostic is being attributed to the 199 // expected location in the source, but is not required. 200 // The literal characters `\n` are replaced with newlines, but 201 // otherwise the string is unchanged. 202 expectedErrs := readDiags(ioutil.ReadFile(filepath.Join(testDir, name, "errors"))) 203 expectedWarnings := readDiags(ioutil.ReadFile(filepath.Join(testDir, name, "warnings"))) 204 205 _, buildDiags := BuildConfig(mod, ModuleWalkerFunc( 206 func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 207 // for simplicity, these tests will treat all source 208 // addresses as relative to the root module 209 sourcePath := filepath.Join(path, req.SourceAddr.String()) 210 mod, diags := parser.LoadConfigDir(sourcePath) 211 version, _ := version.NewVersion("1.0.0") 212 return mod, version, diags 213 }, 214 )) 215 216 // we can make this less repetitive later if we want 217 for _, msg := range expectedErrs { 218 found := false 219 for _, diag := range buildDiags { 220 if diag.Severity == hcl.DiagError && strings.Contains(diag.Error(), msg) { 221 found = true 222 break 223 } 224 } 225 226 if !found { 227 t.Errorf("Expected error diagnostic containing %q", msg) 228 } 229 } 230 231 for _, diag := range buildDiags { 232 if diag.Severity != hcl.DiagError { 233 continue 234 } 235 found := false 236 for _, msg := range expectedErrs { 237 if strings.Contains(diag.Error(), msg) { 238 found = true 239 break 240 } 241 } 242 243 if !found { 244 t.Errorf("Unexpected error: %q", diag) 245 } 246 } 247 248 for _, msg := range expectedWarnings { 249 found := false 250 for _, diag := range buildDiags { 251 if diag.Severity == hcl.DiagWarning && strings.Contains(diag.Error(), msg) { 252 found = true 253 break 254 } 255 } 256 257 if !found { 258 t.Errorf("Expected warning diagnostic containing %q", msg) 259 } 260 } 261 262 for _, diag := range buildDiags { 263 if diag.Severity != hcl.DiagWarning { 264 continue 265 } 266 found := false 267 for _, msg := range expectedWarnings { 268 if strings.Contains(diag.Error(), msg) { 269 found = true 270 break 271 } 272 } 273 274 if !found { 275 t.Errorf("Unexpected warning: %q", diag) 276 } 277 } 278 279 }) 280 } 281 }