github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/helper/testing.go (about) 1 package helper 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 "github.com/google/go-cmp/cmp" 10 "github.com/google/go-cmp/cmp/cmpopts" 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/gohcl" 13 "github.com/hashicorp/hcl/v2/hclparse" 14 "github.com/terraform-linters/tflint-plugin-sdk/tflint" 15 ) 16 17 // TestRunner returns a mock Runner for testing. 18 // You can pass the map of file names and their contents in the second argument. 19 func TestRunner(t *testing.T, files map[string]string) *Runner { 20 t.Helper() 21 22 runner := newLocalRunner(map[string]*hcl.File{}, Issues{}) 23 parser := hclparse.NewParser() 24 25 for name, src := range files { 26 var file *hcl.File 27 var diags hcl.Diagnostics 28 if strings.HasSuffix(name, ".json") { 29 file, diags = parser.ParseJSON([]byte(src), name) 30 } else { 31 file, diags = parser.ParseHCL([]byte(src), name) 32 } 33 if diags.HasErrors() { 34 t.Fatal(diags) 35 } 36 37 if name == ".tflint.hcl" { 38 var config Config 39 if diags := gohcl.DecodeBody(file.Body, nil, &config); diags.HasErrors() { 40 t.Fatal(diags) 41 } 42 runner.config = config 43 } else { 44 runner.addLocalFile(name, file) 45 } 46 } 47 48 if err := runner.initFromFiles(); err != nil { 49 panic(fmt.Sprintf("Failed to initialize runner: %s", err)) 50 } 51 return runner 52 } 53 54 // AssertIssues is an assertion helper for comparing issues. 55 func AssertIssues(t *testing.T, want Issues, got Issues) { 56 t.Helper() 57 58 opts := []cmp.Option{ 59 // Byte field will be ignored because it's not important in tests such as positions 60 cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), 61 // Issues will be sorted and output in the end, so ignore the order. 62 ignoreIssuesOrder(), 63 ruleComparer(), 64 } 65 if diff := cmp.Diff(want, got, opts...); diff != "" { 66 t.Fatalf("Expected issues are not matched:\n %s\n", diff) 67 } 68 } 69 70 // AssertIssuesWithoutRange is an assertion helper for comparing issues except for range. 71 func AssertIssuesWithoutRange(t *testing.T, want Issues, got Issues) { 72 t.Helper() 73 74 opts := []cmp.Option{ 75 cmpopts.IgnoreFields(Issue{}, "Range"), 76 ignoreIssuesOrder(), 77 ruleComparer(), 78 } 79 if diff := cmp.Diff(want, got, opts...); diff != "" { 80 t.Fatalf("Expected issues are not matched:\n %s\n", diff) 81 } 82 } 83 84 // AssertChanges is an assertion helper for comparing autofix changes. 85 func AssertChanges(t *testing.T, want map[string]string, got map[string][]byte) { 86 t.Helper() 87 88 sources := make(map[string]string) 89 for name, src := range got { 90 sources[name] = string(src) 91 } 92 if diff := cmp.Diff(want, sources); diff != "" { 93 t.Fatalf("Expected changes are not matched:\n %s\n", diff) 94 } 95 } 96 97 // ruleComparer returns a Comparer func that checks that two rule interfaces 98 // have the same underlying type. It does not compare struct fields. 99 func ruleComparer() cmp.Option { 100 return cmp.Comparer(func(x, y tflint.Rule) bool { 101 return reflect.TypeOf(x) == reflect.TypeOf(y) 102 }) 103 } 104 105 func ignoreIssuesOrder() cmp.Option { 106 return cmpopts.SortSlices(func(i, j *Issue) bool { 107 if i.Range.Filename != j.Range.Filename { 108 return i.Range.Filename < j.Range.Filename 109 } 110 if i.Range.Start.Line != j.Range.Start.Line { 111 return i.Range.Start.Line < j.Range.Start.Line 112 } 113 if i.Range.Start.Column != j.Range.Start.Column { 114 return i.Range.Start.Column < j.Range.Start.Column 115 } 116 if i.Range.End.Line != j.Range.End.Line { 117 return i.Range.End.Line > j.Range.End.Line 118 } 119 if i.Range.End.Column != j.Range.End.Column { 120 return i.Range.End.Column > j.Range.End.Column 121 } 122 return i.Message < j.Message 123 }) 124 }