1 package testrunner
2
3 import (
4 "os"
5 "strconv"
6 "strings"
7 "testing"
8
9 "github.com/andreyvit/diff"
10 "github.com/vektah/gqlparser/v2/gqlerror"
11 "gopkg.in/yaml.v2"
12 )
13
14 type Features map[string][]Spec
15
16 type Spec struct {
17 Name string
18 Input string
19 Error *gqlerror.Error
20 Tokens []Token
21 AST string
22 }
23
24 type Token struct {
25 Kind string
26 Value string
27 Start int
28 End int
29 Line int
30 Column int
31 Src string
32 }
33
34 func (t Token) String() string {
35 return t.Kind + " " + strconv.Quote(t.Value)
36 }
37
38 func Test(t *testing.T, filename string, f func(t *testing.T, input string) Spec) {
39 b, err := os.ReadFile(filename)
40 if err != nil {
41 panic(err)
42 }
43 var tests Features
44 err = yaml.Unmarshal(b, &tests)
45 if err != nil {
46 t.Errorf("unable to load %s: %s", filename, err.Error())
47 return
48 }
49
50 for name, specs := range tests {
51 t.Run(name, func(t *testing.T) {
52 for _, spec := range specs {
53 t.Run(spec.Name, func(t *testing.T) {
54 result := f(t, spec.Input)
55
56 if spec.Error == nil {
57 if result.Error != nil {
58 t.Errorf("unexpected error %s", result.Error.Message)
59 }
60 } else if result.Error == nil {
61 t.Errorf("expected error but got none")
62 } else {
63 if result.Error.Message != spec.Error.Message {
64 t.Errorf("wrong error returned\nexpected: %s\ngot: %s", spec.Error.Message, result.Error.Message)
65 }
66
67 if result.Error.Locations[0].Column != spec.Error.Locations[0].Column || result.Error.Locations[0].Line != spec.Error.Locations[0].Line {
68 t.Errorf(
69 "wrong error location:\nexpected: line %d column %d\ngot: line %d column %d",
70 spec.Error.Locations[0].Line,
71 spec.Error.Locations[0].Column,
72 result.Error.Locations[0].Line,
73 result.Error.Locations[0].Column,
74 )
75 }
76 }
77
78 if len(spec.Tokens) != len(result.Tokens) {
79 var tokensStr []string
80 for _, t := range result.Tokens {
81 tokensStr = append(tokensStr, t.String())
82 }
83 t.Errorf("token count mismatch, got: \n%s", strings.Join(tokensStr, "\n"))
84 } else {
85 for i, tok := range result.Tokens {
86 expected := spec.Tokens[i]
87
88 if !strings.EqualFold(strings.Replace(expected.Kind, "_", "", -1), tok.Kind) {
89 t.Errorf("token[%d].kind should be %s, was %s", i, expected.Kind, tok.Kind)
90 }
91 if expected.Value != "undefined" && expected.Value != tok.Value {
92 t.Errorf("token[%d].value incorrect\nexpected: %s\ngot: %s", i, strconv.Quote(expected.Value), strconv.Quote(tok.Value))
93 }
94 if expected.Start != 0 && expected.Start != tok.Start {
95 t.Errorf("token[%d].start should be %d, was %d", i, expected.Start, tok.Start)
96 }
97 if expected.End != 0 && expected.End != tok.End {
98 t.Errorf("token[%d].end should be %d, was %d", i, expected.End, tok.End)
99 }
100 if expected.Line != 0 && expected.Line != tok.Line {
101 t.Errorf("token[%d].line should be %d, was %d", i, expected.Line, tok.Line)
102 }
103 if expected.Column != 0 && expected.Column != tok.Column {
104 t.Errorf("token[%d].column should be %d, was %d", i, expected.Column, tok.Column)
105 }
106 if tok.Src != "spec" {
107 t.Errorf("token[%d].source.name should be spec, was %s", i, strconv.Quote(tok.Src))
108 }
109 }
110 }
111
112 spec.AST = strings.TrimSpace(spec.AST)
113 result.AST = strings.TrimSpace(result.AST)
114
115 if spec.AST != "" && spec.AST != result.AST {
116 diffStr := diff.LineDiff(spec.AST, result.AST)
117 if diffStr != "" {
118 t.Errorf("AST mismatch:\n%s", diffStr)
119 }
120 }
121
122 if t.Failed() {
123 t.Logf("input: %s", strconv.Quote(spec.Input))
124 if result.Error != nil {
125 t.Logf("error: %s", result.Error.Message)
126 }
127 t.Log("tokens: ")
128 for _, tok := range result.Tokens {
129 t.Logf(" - %s", tok.String())
130 }
131 t.Logf(" - <EOF>")
132 }
133 })
134 }
135 })
136 }
137
138 }
139
View as plain text