github.com/opentofu/opentofu@v1.7.1/internal/configs/parser_config_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  	"bufio"
    10  	"bytes"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/google/go-cmp/cmp"
    17  
    18  	"github.com/hashicorp/hcl/v2"
    19  )
    20  
    21  // TestParseLoadConfigFileSuccess is a simple test that just verifies that
    22  // a number of test configuration files (in testdata/valid-files) can
    23  // be parsed without raising any diagnostics.
    24  //
    25  // This test does not verify that reading these files produces the correct
    26  // file element contents. More detailed assertions may be made on some subset
    27  // of these configuration files in other tests.
    28  func TestParserLoadConfigFileSuccess(t *testing.T) {
    29  	files, err := os.ReadDir("testdata/valid-files")
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  
    34  	for _, info := range files {
    35  		name := info.Name()
    36  		t.Run(name, func(t *testing.T) {
    37  			src, err := os.ReadFile(filepath.Join("testdata/valid-files", name))
    38  			if err != nil {
    39  				t.Fatal(err)
    40  			}
    41  
    42  			parser := testParser(map[string]string{
    43  				name: string(src),
    44  			})
    45  
    46  			_, diags := parser.LoadConfigFile(name)
    47  			if len(diags) != 0 {
    48  				t.Errorf("unexpected diagnostics")
    49  				for _, diag := range diags {
    50  					t.Logf("- %s", diag)
    51  				}
    52  			}
    53  		})
    54  	}
    55  }
    56  
    57  // TestParseLoadConfigFileFailure is a simple test that just verifies that
    58  // a number of test configuration files (in testdata/invalid-files)
    59  // produce errors as expected.
    60  //
    61  // This test does not verify specific error messages, so more detailed
    62  // assertions should be made on some subset of these configuration files in
    63  // other tests.
    64  func TestParserLoadConfigFileFailure(t *testing.T) {
    65  	files, err := os.ReadDir("testdata/invalid-files")
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	for _, info := range files {
    71  		name := info.Name()
    72  		t.Run(name, func(t *testing.T) {
    73  			src, err := os.ReadFile(filepath.Join("testdata/invalid-files", name))
    74  			if err != nil {
    75  				t.Fatal(err)
    76  			}
    77  
    78  			parser := testParser(map[string]string{
    79  				name: string(src),
    80  			})
    81  
    82  			_, diags := parser.LoadConfigFile(name)
    83  			if !diags.HasErrors() {
    84  				t.Errorf("LoadConfigFile succeeded; want errors")
    85  			}
    86  			for _, diag := range diags {
    87  				t.Logf("- %s", diag)
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  // This test uses a subset of the same fixture files as
    94  // TestParserLoadConfigFileFailure, but additionally verifies that each
    95  // file produces the expected diagnostic summary.
    96  func TestParserLoadConfigFileFailureMessages(t *testing.T) {
    97  	tests := []struct {
    98  		Filename     string
    99  		WantSeverity hcl.DiagnosticSeverity
   100  		WantDiag     string
   101  	}{
   102  		{
   103  			"invalid-files/data-resource-lifecycle.tf",
   104  			hcl.DiagError,
   105  			"Invalid data resource lifecycle argument",
   106  		},
   107  		{
   108  			"invalid-files/variable-type-unknown.tf",
   109  			hcl.DiagError,
   110  			"Invalid type specification",
   111  		},
   112  		{
   113  			"invalid-files/unexpected-attr.tf",
   114  			hcl.DiagError,
   115  			"Unsupported argument",
   116  		},
   117  		{
   118  			"invalid-files/unexpected-block.tf",
   119  			hcl.DiagError,
   120  			"Unsupported block type",
   121  		},
   122  		{
   123  			"invalid-files/resource-count-and-for_each.tf",
   124  			hcl.DiagError,
   125  			`Invalid combination of "count" and "for_each"`,
   126  		},
   127  		{
   128  			"invalid-files/data-count-and-for_each.tf",
   129  			hcl.DiagError,
   130  			`Invalid combination of "count" and "for_each"`,
   131  		},
   132  		{
   133  			"invalid-files/resource-lifecycle-badbool.tf",
   134  			hcl.DiagError,
   135  			"Unsuitable value type",
   136  		},
   137  	}
   138  
   139  	for _, test := range tests {
   140  		t.Run(test.Filename, func(t *testing.T) {
   141  			src, err := os.ReadFile(filepath.Join("testdata", test.Filename))
   142  			if err != nil {
   143  				t.Fatal(err)
   144  			}
   145  
   146  			parser := testParser(map[string]string{
   147  				test.Filename: string(src),
   148  			})
   149  
   150  			_, diags := parser.LoadConfigFile(test.Filename)
   151  			if len(diags) != 1 {
   152  				t.Errorf("Wrong number of diagnostics %d; want 1", len(diags))
   153  				for _, diag := range diags {
   154  					t.Logf("- %s", diag)
   155  				}
   156  				return
   157  			}
   158  			if diags[0].Severity != test.WantSeverity {
   159  				t.Errorf("Wrong diagnostic severity %#v; want %#v", diags[0].Severity, test.WantSeverity)
   160  			}
   161  			if diags[0].Summary != test.WantDiag {
   162  				t.Errorf("Wrong diagnostic summary\ngot:  %s\nwant: %s", diags[0].Summary, test.WantDiag)
   163  			}
   164  		})
   165  	}
   166  }
   167  
   168  // TestParseLoadConfigFileWarning is a test that verifies files from
   169  // testdata/warning-files produce particular warnings.
   170  //
   171  // This test does not verify that reading these files produces the correct
   172  // file element contents in spite of those warnings. More detailed assertions
   173  // may be made on some subset of these configuration files in other tests.
   174  func TestParserLoadConfigFileWarning(t *testing.T) {
   175  	files, err := os.ReadDir("testdata/warning-files")
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	for _, info := range files {
   181  		name := info.Name()
   182  		t.Run(name, func(t *testing.T) {
   183  			src, err := os.ReadFile(filepath.Join("testdata/warning-files", name))
   184  			if err != nil {
   185  				t.Fatal(err)
   186  			}
   187  
   188  			// First we'll scan the file to see what warnings are expected.
   189  			// That's declared inside the files themselves by using the
   190  			// string "WARNING: " somewhere on each line that is expected
   191  			// to produce a warning, followed by the expected warning summary
   192  			// text. A single-line comment (with #) is the main way to do that.
   193  			const marker = "WARNING: "
   194  			sc := bufio.NewScanner(bytes.NewReader(src))
   195  			wantWarnings := make(map[int]string)
   196  			lineNum := 1
   197  			for sc.Scan() {
   198  				lineText := sc.Text()
   199  				if idx := strings.Index(lineText, marker); idx != -1 {
   200  					summaryText := lineText[idx+len(marker):]
   201  					wantWarnings[lineNum] = summaryText
   202  				}
   203  				lineNum++
   204  			}
   205  
   206  			parser := testParser(map[string]string{
   207  				name: string(src),
   208  			})
   209  
   210  			_, diags := parser.LoadConfigFile(name)
   211  			if diags.HasErrors() {
   212  				t.Errorf("unexpected error diagnostics")
   213  				for _, diag := range diags {
   214  					t.Logf("- %s", diag)
   215  				}
   216  			}
   217  
   218  			gotWarnings := make(map[int]string)
   219  			for _, diag := range diags {
   220  				if diag.Severity != hcl.DiagWarning || diag.Subject == nil {
   221  					continue
   222  				}
   223  				gotWarnings[diag.Subject.Start.Line] = diag.Summary
   224  			}
   225  
   226  			if diff := cmp.Diff(wantWarnings, gotWarnings); diff != "" {
   227  				t.Errorf("wrong warnings\n%s", diff)
   228  			}
   229  		})
   230  	}
   231  }
   232  
   233  // TestParseLoadConfigFileError is a test that verifies files from
   234  // testdata/warning-files produce particular errors.
   235  //
   236  // This test does not verify that reading these files produces the correct
   237  // file element contents in spite of those errors. More detailed assertions
   238  // may be made on some subset of these configuration files in other tests.
   239  func TestParserLoadConfigFileError(t *testing.T) {
   240  	files, err := os.ReadDir("testdata/error-files")
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	for _, info := range files {
   246  		name := info.Name()
   247  		t.Run(name, func(t *testing.T) {
   248  			src, err := os.ReadFile(filepath.Join("testdata/error-files", name))
   249  			if err != nil {
   250  				t.Fatal(err)
   251  			}
   252  
   253  			// First we'll scan the file to see what warnings are expected.
   254  			// That's declared inside the files themselves by using the
   255  			// string "ERROR: " somewhere on each line that is expected
   256  			// to produce a warning, followed by the expected warning summary
   257  			// text. A single-line comment (with #) is the main way to do that.
   258  			const marker = "ERROR: "
   259  			sc := bufio.NewScanner(bytes.NewReader(src))
   260  			wantErrors := make(map[int]string)
   261  			lineNum := 1
   262  			for sc.Scan() {
   263  				lineText := sc.Text()
   264  				if idx := strings.Index(lineText, marker); idx != -1 {
   265  					summaryText := lineText[idx+len(marker):]
   266  					wantErrors[lineNum] = summaryText
   267  				}
   268  				lineNum++
   269  			}
   270  
   271  			parser := testParser(map[string]string{
   272  				name: string(src),
   273  			})
   274  
   275  			_, diags := parser.LoadConfigFile(name)
   276  
   277  			gotErrors := make(map[int]string)
   278  			for _, diag := range diags {
   279  				if diag.Severity != hcl.DiagError || diag.Subject == nil {
   280  					continue
   281  				}
   282  				gotErrors[diag.Subject.Start.Line] = diag.Summary
   283  			}
   284  
   285  			if diff := cmp.Diff(wantErrors, gotErrors); diff != "" {
   286  				t.Errorf("wrong errors\n%s", diff)
   287  			}
   288  		})
   289  	}
   290  }