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  }