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  }