Skip to content

Commit c75504c

Browse files
committed
Add test helpers
1 parent 6fc731c commit c75504c

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/terraform-linters/tflint-plugin-sdk
33
go 1.13
44

55
require (
6+
github.com/google/go-cmp v0.3.1
67
github.com/hashicorp/go-hclog v0.10.1
78
github.com/hashicorp/go-plugin v1.0.1
89
github.com/hashicorp/hcl/v2 v2.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
1717
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
1818
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
1919
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
20+
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
21+
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
2022
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
2123
github.com/hashicorp/go-hclog v0.10.1 h1:uyt/l0dWjJ879yiAu+T7FG3/6QX+zwm4bQ8P7XsYt3o=
2224
github.com/hashicorp/go-hclog v0.10.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=

helper/issue.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package helper
2+
3+
import (
4+
"github.com/hashicorp/hcl/v2"
5+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
6+
)
7+
8+
// Issue is a stub that has the same structure as the actually used issue object.
9+
// This is only used for testing, as the Runner client doesn't depend on the actual Issue structure.
10+
type Issue struct {
11+
Rule tflint.Rule
12+
Message string
13+
Range hcl.Range
14+
}
15+
16+
// Issues is a list of Issue
17+
type Issues []*Issue

helper/runner.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package helper
2+
3+
import (
4+
"github.com/hashicorp/hcl/v2"
5+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
6+
"github.com/zclconf/go-cty/cty/gocty"
7+
)
8+
9+
// Runner is a pseudo Runner client provided for plugin testing.
10+
// Actually, it is provided as an RPC client, but for the sake of simplicity,
11+
// only the methods that satisfy the minimum required Runner interface are implemented.
12+
// Specifically, there are restrictions on evaluation, annotation comments, module inspection, and so on.
13+
type Runner struct {
14+
Files map[string]*hcl.File
15+
Issues Issues
16+
}
17+
18+
// WalkResourceAttributes searches for resources and passes the appropriate attributes to the walker function
19+
func (r *Runner) WalkResourceAttributes(resourceType, attributeName string, walker func(*hcl.Attribute) error) error {
20+
for _, file := range r.Files {
21+
resources, _, diags := file.Body.PartialContent(&hcl.BodySchema{
22+
Blocks: []hcl.BlockHeaderSchema{
23+
{
24+
Type: "resource",
25+
LabelNames: []string{"type", "name"},
26+
},
27+
},
28+
})
29+
if diags.HasErrors() {
30+
return diags
31+
}
32+
33+
for _, resource := range resources.Blocks {
34+
if resource.Labels[0] != resourceType {
35+
continue
36+
}
37+
38+
body, _, diags := resource.Body.PartialContent(&hcl.BodySchema{
39+
Attributes: []hcl.AttributeSchema{
40+
{
41+
Name: attributeName,
42+
},
43+
},
44+
})
45+
if diags.HasErrors() {
46+
return diags
47+
}
48+
49+
if attribute, ok := body.Attributes[attributeName]; ok {
50+
err := walker(attribute)
51+
if err != nil {
52+
return err
53+
}
54+
}
55+
}
56+
}
57+
58+
return nil
59+
}
60+
61+
// EvaluateExpr returns a value of the passed expression.
62+
// Note that there is no evaluation, no type conversion, etc.
63+
func (r *Runner) EvaluateExpr(expr hcl.Expression, ret interface{}) error {
64+
val, diags := expr.Value(&hcl.EvalContext{})
65+
if diags.HasErrors() {
66+
return diags
67+
}
68+
return gocty.FromCtyValue(val, ret)
69+
}
70+
71+
// EmitIssue adds an issue into the self
72+
func (r *Runner) EmitIssue(rule tflint.Rule, message string, location hcl.Range, meta tflint.Metadata) error {
73+
r.Issues = append(r.Issues, &Issue{
74+
Rule: rule,
75+
Message: message,
76+
Range: location,
77+
})
78+
return nil
79+
}
80+
81+
// EnsureNoError is a method that simply run a function if there is no error
82+
func (r *Runner) EnsureNoError(err error, proc func() error) error {
83+
if err == nil {
84+
return proc()
85+
}
86+
return err
87+
}

helper/testing.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package helper
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
"github.com/google/go-cmp/cmp/cmpopts"
8+
"github.com/hashicorp/hcl/v2"
9+
"github.com/hashicorp/hcl/v2/hclparse"
10+
)
11+
12+
// TestRunner returns a pseudo Runner for testing
13+
func TestRunner(t *testing.T, files map[string]string) *Runner {
14+
runner := &Runner{Files: map[string]*hcl.File{}}
15+
parser := hclparse.NewParser()
16+
17+
for name, src := range files {
18+
file, diags := parser.ParseHCL([]byte(src), name)
19+
if diags.HasErrors() {
20+
t.Fatal(diags)
21+
}
22+
runner.Files[name] = file
23+
}
24+
25+
return runner
26+
}
27+
28+
// AssertIssues is an assertion helper for comparing issues
29+
func AssertIssues(t *testing.T, expected Issues, actual Issues) {
30+
opts := []cmp.Option{
31+
// Byte field will be ignored because it's not important in tests such as positions
32+
cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
33+
cmpopts.IgnoreFields(Issue{}, "Rule"),
34+
}
35+
if !cmp.Equal(expected, actual, opts...) {
36+
t.Fatalf("Expected issues are not matched:\n %s\n", cmp.Diff(expected, actual, opts...))
37+
}
38+
}
39+
40+
// AssertIssuesWithoutRange is an assertion helper for comparing issues
41+
func AssertIssuesWithoutRange(t *testing.T, expected Issues, actual Issues) {
42+
opts := []cmp.Option{
43+
cmpopts.IgnoreFields(Issue{}, "Range"),
44+
cmpopts.IgnoreFields(Issue{}, "Rule"),
45+
}
46+
if !cmp.Equal(expected, actual, opts...) {
47+
t.Fatalf("Expected issues are not matched:\n %s\n", cmp.Diff(expected, actual, opts...))
48+
}
49+
}

0 commit comments

Comments
 (0)