package configs import ( "bufio" "bytes" "io/ioutil" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" ) // TestParseLoadConfigFileSuccess is a simple test that just verifies that // a number of test configuration files (in testdata/valid-files) can // be parsed without raising any diagnostics. // // This test does not verify that reading these files produces the correct // file element contents. More detailed assertions may be made on some subset // of these configuration files in other tests. func TestParserLoadConfigFileSuccess(t *testing.T) { files, err := ioutil.ReadDir("testdata/valid-files") if err != nil { t.Fatal(err) } for _, info := range files { name := info.Name() t.Run(name, func(t *testing.T) { src, err := ioutil.ReadFile(filepath.Join("testdata/valid-files", name)) if err != nil { t.Fatal(err) } parser := testParser(map[string]string{ name: string(src), }) _, diags := parser.LoadConfigFile(name) if len(diags) != 0 { t.Errorf("unexpected diagnostics") for _, diag := range diags { t.Logf("- %s", diag) } } }) } } // TestParseLoadConfigFileFailure is a simple test that just verifies that // a number of test configuration files (in testdata/invalid-files) // produce errors as expected. // // This test does not verify specific error messages, so more detailed // assertions should be made on some subset of these configuration files in // other tests. func TestParserLoadConfigFileFailure(t *testing.T) { files, err := ioutil.ReadDir("testdata/invalid-files") if err != nil { t.Fatal(err) } for _, info := range files { name := info.Name() t.Run(name, func(t *testing.T) { src, err := ioutil.ReadFile(filepath.Join("testdata/invalid-files", name)) if err != nil { t.Fatal(err) } parser := testParser(map[string]string{ name: string(src), }) _, diags := parser.LoadConfigFile(name) if !diags.HasErrors() { t.Errorf("LoadConfigFile succeeded; want errors") } for _, diag := range diags { t.Logf("- %s", diag) } }) } } // This test uses a subset of the same fixture files as // TestParserLoadConfigFileFailure, but additionally verifies that each // file produces the expected diagnostic summary. func TestParserLoadConfigFileFailureMessages(t *testing.T) { tests := []struct { Filename string WantSeverity hcl.DiagnosticSeverity WantDiag string }{ { "invalid-files/data-resource-lifecycle.tf", hcl.DiagError, "Unsupported lifecycle block", }, { "invalid-files/variable-type-unknown.tf", hcl.DiagError, "Invalid type specification", }, { "invalid-files/unexpected-attr.tf", hcl.DiagError, "Unsupported argument", }, { "invalid-files/unexpected-block.tf", hcl.DiagError, "Unsupported block type", }, { "invalid-files/resource-count-and-for_each.tf", hcl.DiagError, `Invalid combination of "count" and "for_each"`, }, { "invalid-files/data-count-and-for_each.tf", hcl.DiagError, `Invalid combination of "count" and "for_each"`, }, { "invalid-files/resource-lifecycle-badbool.tf", hcl.DiagError, "Unsuitable value type", }, } for _, test := range tests { t.Run(test.Filename, func(t *testing.T) { src, err := ioutil.ReadFile(filepath.Join("testdata", test.Filename)) if err != nil { t.Fatal(err) } parser := testParser(map[string]string{ test.Filename: string(src), }) _, diags := parser.LoadConfigFile(test.Filename) if len(diags) != 1 { t.Errorf("Wrong number of diagnostics %d; want 1", len(diags)) for _, diag := range diags { t.Logf("- %s", diag) } return } if diags[0].Severity != test.WantSeverity { t.Errorf("Wrong diagnostic severity %#v; want %#v", diags[0].Severity, test.WantSeverity) } if diags[0].Summary != test.WantDiag { t.Errorf("Wrong diagnostic summary\ngot: %s\nwant: %s", diags[0].Summary, test.WantDiag) } }) } } // TestParseLoadConfigFileWarning is a test that verifies files from // testdata/warning-files produce particular warnings. // // This test does not verify that reading these files produces the correct // file element contents in spite of those warnings. More detailed assertions // may be made on some subset of these configuration files in other tests. func TestParserLoadConfigFileWarning(t *testing.T) { files, err := ioutil.ReadDir("testdata/warning-files") if err != nil { t.Fatal(err) } for _, info := range files { name := info.Name() t.Run(name, func(t *testing.T) { src, err := ioutil.ReadFile(filepath.Join("testdata/warning-files", name)) if err != nil { t.Fatal(err) } // First we'll scan the file to see what warnings are expected. // That's declared inside the files themselves by using the // string "WARNING: " somewhere on each line that is expected // to produce a warning, followed by the expected warning summary // text. A single-line comment (with #) is the main way to do that. const marker = "WARNING: " sc := bufio.NewScanner(bytes.NewReader(src)) wantWarnings := make(map[int]string) lineNum := 1 for sc.Scan() { lineText := sc.Text() if idx := strings.Index(lineText, marker); idx != -1 { summaryText := lineText[idx+len(marker):] wantWarnings[lineNum] = summaryText } lineNum++ } parser := testParser(map[string]string{ name: string(src), }) _, diags := parser.LoadConfigFile(name) if diags.HasErrors() { t.Errorf("unexpected error diagnostics") for _, diag := range diags { t.Logf("- %s", diag) } } gotWarnings := make(map[int]string) for _, diag := range diags { if diag.Severity != hcl.DiagWarning || diag.Subject == nil { continue } gotWarnings[diag.Subject.Start.Line] = diag.Summary } if diff := cmp.Diff(wantWarnings, gotWarnings); diff != "" { t.Errorf("wrong warnings\n%s", diff) } }) } } // TestParseLoadConfigFileError is a test that verifies files from // testdata/warning-files produce particular errors. // // This test does not verify that reading these files produces the correct // file element contents in spite of those errors. More detailed assertions // may be made on some subset of these configuration files in other tests. func TestParserLoadConfigFileError(t *testing.T) { files, err := ioutil.ReadDir("testdata/error-files") if err != nil { t.Fatal(err) } for _, info := range files { name := info.Name() t.Run(name, func(t *testing.T) { src, err := ioutil.ReadFile(filepath.Join("testdata/error-files", name)) if err != nil { t.Fatal(err) } // First we'll scan the file to see what warnings are expected. // That's declared inside the files themselves by using the // string "ERROR: " somewhere on each line that is expected // to produce a warning, followed by the expected warning summary // text. A single-line comment (with #) is the main way to do that. const marker = "ERROR: " sc := bufio.NewScanner(bytes.NewReader(src)) wantErrors := make(map[int]string) lineNum := 1 for sc.Scan() { lineText := sc.Text() if idx := strings.Index(lineText, marker); idx != -1 { summaryText := lineText[idx+len(marker):] wantErrors[lineNum] = summaryText } lineNum++ } parser := testParser(map[string]string{ name: string(src), }) _, diags := parser.LoadConfigFile(name) gotErrors := make(map[int]string) for _, diag := range diags { if diag.Severity != hcl.DiagError || diag.Subject == nil { continue } gotErrors[diag.Subject.Start.Line] = diag.Summary } if diff := cmp.Diff(wantErrors, gotErrors); diff != "" { t.Errorf("wrong errors\n%s", diff) } }) } }