github.com/opentofu/opentofu@v1.7.1/internal/configs/parser_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 configs 7 8 import ( 9 "fmt" 10 "os" 11 "path" 12 "path/filepath" 13 "reflect" 14 "testing" 15 16 "github.com/davecgh/go-spew/spew" 17 18 version "github.com/hashicorp/go-version" 19 "github.com/hashicorp/hcl/v2" 20 "github.com/spf13/afero" 21 ) 22 23 // testParser returns a parser that reads files from the given map, which 24 // is from paths to file contents. 25 // 26 // Since this function uses only in-memory objects, it should never fail. 27 // If any errors are encountered in practice, this function will panic. 28 func testParser(files map[string]string) *Parser { 29 fs := afero.Afero{Fs: afero.NewMemMapFs()} 30 31 for filePath, contents := range files { 32 dirPath := path.Dir(filePath) 33 err := fs.MkdirAll(dirPath, os.ModePerm) 34 if err != nil { 35 panic(err) 36 } 37 err = fs.WriteFile(filePath, []byte(contents), os.ModePerm) 38 if err != nil { 39 panic(err) 40 } 41 } 42 43 return NewParser(fs) 44 } 45 46 // testModuleConfigFrom File reads a single file from the given path as a 47 // module and returns its configuration. This is a helper for use in unit tests. 48 func testModuleConfigFromFile(filename string) (*Config, hcl.Diagnostics) { 49 parser := NewParser(nil) 50 f, diags := parser.LoadConfigFile(filename) 51 mod, modDiags := NewModule([]*File{f}, nil) 52 diags = append(diags, modDiags...) 53 cfg, moreDiags := BuildConfig(mod, nil) 54 return cfg, append(diags, moreDiags...) 55 } 56 57 // testModuleFromDir reads configuration from the given directory path as 58 // a module and returns it. This is a helper for use in unit tests. 59 func testModuleFromDir(path string) (*Module, hcl.Diagnostics) { 60 parser := NewParser(nil) 61 return parser.LoadConfigDir(path) 62 } 63 64 // testModuleFromDir reads configuration from the given directory path as a 65 // module and returns its configuration. This is a helper for use in unit tests. 66 func testModuleConfigFromDir(path string) (*Config, hcl.Diagnostics) { 67 parser := NewParser(nil) 68 mod, diags := parser.LoadConfigDir(path) 69 cfg, moreDiags := BuildConfig(mod, nil) 70 return cfg, append(diags, moreDiags...) 71 } 72 73 // testNestedModuleConfigFromDirWithTests matches testNestedModuleConfigFromDir 74 // except it also loads any test files within the directory. 75 func testNestedModuleConfigFromDirWithTests(t *testing.T, path string) (*Config, hcl.Diagnostics) { 76 t.Helper() 77 78 parser := NewParser(nil) 79 mod, diags := parser.LoadConfigDirWithTests(path, "tests") 80 if mod == nil { 81 t.Fatal("got nil root module; want non-nil") 82 } 83 84 cfg, nestedDiags := buildNestedModuleConfig(mod, path, parser) 85 86 diags = append(diags, nestedDiags...) 87 return cfg, diags 88 } 89 90 // testNestedModuleConfigFromDir reads configuration from the given directory path as 91 // a module with (optional) submodules and returns its configuration. This is a 92 // helper for use in unit tests. 93 func testNestedModuleConfigFromDir(t *testing.T, path string) (*Config, hcl.Diagnostics) { 94 t.Helper() 95 96 parser := NewParser(nil) 97 mod, diags := parser.LoadConfigDir(path) 98 if mod == nil { 99 t.Fatal("got nil root module; want non-nil") 100 } 101 102 cfg, nestedDiags := buildNestedModuleConfig(mod, path, parser) 103 104 diags = append(diags, nestedDiags...) 105 return cfg, diags 106 } 107 108 func buildNestedModuleConfig(mod *Module, path string, parser *Parser) (*Config, hcl.Diagnostics) { 109 versionI := 0 110 return BuildConfig(mod, ModuleWalkerFunc( 111 func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) { 112 // For the sake of this test we're going to just treat our 113 // SourceAddr as a path relative to the calling module. 114 // A "real" implementation of ModuleWalker should accept the 115 // various different source address syntaxes OpenTofu supports. 116 117 // Build a full path by walking up the module tree, prepending each 118 // source address path until we hit the root 119 paths := []string{req.SourceAddr.String()} 120 for config := req.Parent; config != nil && config.Parent != nil; config = config.Parent { 121 paths = append([]string{config.SourceAddr.String()}, paths...) 122 } 123 paths = append([]string{path}, paths...) 124 sourcePath := filepath.Join(paths...) 125 126 mod, diags := parser.LoadConfigDir(sourcePath) 127 version, _ := version.NewVersion(fmt.Sprintf("1.0.%d", versionI)) 128 versionI++ 129 return mod, version, diags 130 }, 131 )) 132 } 133 134 func assertNoDiagnostics(t *testing.T, diags hcl.Diagnostics) bool { 135 t.Helper() 136 return assertDiagnosticCount(t, diags, 0) 137 } 138 139 func assertDiagnosticCount(t *testing.T, diags hcl.Diagnostics, want int) bool { 140 t.Helper() 141 if len(diags) != want { 142 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), want) 143 for _, diag := range diags { 144 t.Logf("- %s", diag) 145 } 146 return true 147 } 148 return false 149 } 150 151 func assertDiagnosticSummary(t *testing.T, diags hcl.Diagnostics, want string) bool { 152 t.Helper() 153 154 for _, diag := range diags { 155 if diag.Summary == want { 156 return false 157 } 158 } 159 160 t.Errorf("missing diagnostic summary %q", want) 161 for _, diag := range diags { 162 t.Logf("- %s", diag) 163 } 164 return true 165 } 166 167 func assertExactDiagnostics(t *testing.T, diags hcl.Diagnostics, want []string) bool { 168 t.Helper() 169 170 gotDiags := map[string]bool{} 171 wantDiags := map[string]bool{} 172 173 for _, diag := range diags { 174 gotDiags[diag.Error()] = true 175 } 176 for _, msg := range want { 177 wantDiags[msg] = true 178 } 179 180 bad := false 181 for got := range gotDiags { 182 if _, exists := wantDiags[got]; !exists { 183 t.Errorf("unexpected diagnostic: %s", got) 184 bad = true 185 } 186 } 187 for want := range wantDiags { 188 if _, exists := gotDiags[want]; !exists { 189 t.Errorf("missing expected diagnostic: %s", want) 190 bad = true 191 } 192 } 193 194 return bad 195 } 196 197 func assertResultDeepEqual(t *testing.T, got, want interface{}) bool { 198 t.Helper() 199 if !reflect.DeepEqual(got, want) { 200 t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) 201 return true 202 } 203 return false 204 } 205 206 func stringPtr(s string) *string { 207 return &s 208 }