Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/scanner/scanner_test.go
4096 views
1
package scanner
2
3
import (
4
"path/filepath"
5
"testing"
6
7
"github.com/grafana/agent/pkg/river/token"
8
"github.com/stretchr/testify/assert"
9
)
10
11
type tokenExample struct {
12
tok token.Token
13
lit string
14
}
15
16
var tokens = []tokenExample{
17
// Special tokens
18
{token.COMMENT, "/* a comment */"},
19
{token.COMMENT, "// a comment \n"},
20
{token.COMMENT, "/*\r*/"},
21
{token.COMMENT, "/**\r/*/"}, // golang/go#11151
22
{token.COMMENT, "/**\r\r/*/"},
23
{token.COMMENT, "//\r\n"},
24
25
// Identifiers and basic type literals
26
{token.IDENT, "foobar"},
27
{token.IDENT, "a۰۱۸"},
28
{token.IDENT, "foo६४"},
29
{token.IDENT, "bar9876"},
30
{token.IDENT, "ŝ"}, // golang/go#4000
31
{token.IDENT, "ŝfoo"}, // golang/go#4000
32
{token.NUMBER, "0"},
33
{token.NUMBER, "1"},
34
{token.NUMBER, "123456789012345678890"},
35
{token.NUMBER, "01234567"},
36
{token.FLOAT, "0."},
37
{token.FLOAT, ".0"},
38
{token.FLOAT, "3.14159265"},
39
{token.FLOAT, "1e0"},
40
{token.FLOAT, "1e+100"},
41
{token.FLOAT, "1e-100"},
42
{token.FLOAT, "2.71828e-1000"},
43
{token.STRING, `"Hello, world!"`},
44
45
// Operators and delimiters
46
{token.ADD, "+"},
47
{token.SUB, "-"},
48
{token.MUL, "*"},
49
{token.DIV, "/"},
50
{token.MOD, "%"},
51
{token.POW, "^"},
52
53
{token.AND, "&&"},
54
{token.OR, "||"},
55
56
{token.EQ, "=="},
57
{token.LT, "<"},
58
{token.GT, ">"},
59
{token.ASSIGN, "="},
60
{token.NOT, "!"},
61
62
{token.NEQ, "!="},
63
{token.LTE, "<="},
64
{token.GTE, ">="},
65
66
{token.LPAREN, "("},
67
{token.LBRACK, "["},
68
{token.LCURLY, "{"},
69
{token.COMMA, ","},
70
{token.DOT, "."},
71
72
{token.RPAREN, ")"},
73
{token.RBRACK, "]"},
74
{token.RCURLY, "}"},
75
76
// Keywords
77
{token.NULL, "null"},
78
{token.BOOL, "true"},
79
{token.BOOL, "false"},
80
}
81
82
const whitespace = " \t \n\n\n" // Various whitespace to separate tokens
83
84
var source = func() []byte {
85
var src []byte
86
for _, t := range tokens {
87
src = append(src, t.lit...)
88
src = append(src, whitespace...)
89
}
90
return src
91
}()
92
93
// FuzzScanner ensures that the scanner will always be able to reach EOF
94
// regardless of input.
95
func FuzzScanner(f *testing.F) {
96
// Add each token into the corpus
97
for _, t := range tokens {
98
f.Add([]byte(t.lit))
99
}
100
// Then add the entire source
101
f.Add(source)
102
103
f.Fuzz(func(t *testing.T, input []byte) {
104
f := token.NewFile(t.Name())
105
106
s := New(f, input, nil, IncludeComments)
107
108
for {
109
_, tok, _ := s.Scan()
110
if tok == token.EOF {
111
break
112
}
113
}
114
})
115
}
116
117
func TestScanner_Scan(t *testing.T) {
118
whitespaceLinecount := newlineCount(whitespace)
119
120
var eh ErrorHandler = func(_ token.Pos, msg string) {
121
t.Errorf("ErrorHandler called (msg = %s)", msg)
122
}
123
124
f := token.NewFile(t.Name())
125
s := New(f, source, eh, IncludeComments|dontInsertTerms)
126
127
// Configure expected position
128
expectPos := token.Position{
129
Filename: t.Name(),
130
Offset: 0,
131
Line: 1,
132
Column: 1,
133
}
134
135
index := 0
136
for {
137
pos, tok, lit := s.Scan()
138
139
// Check position
140
checkPos(t, lit, tok, pos, expectPos)
141
142
// Check token
143
e := tokenExample{token.EOF, ""}
144
if index < len(tokens) {
145
e = tokens[index]
146
index++
147
}
148
assert.Equal(t, e.tok, tok)
149
150
// Check literal
151
expectLit := ""
152
switch e.tok {
153
case token.COMMENT:
154
// no CRs in comments
155
expectLit = string(stripCR([]byte(e.lit), e.lit[1] == '*'))
156
if expectLit[1] == '/' {
157
// Line comment literals doesn't contain newline
158
expectLit = expectLit[0 : len(expectLit)-1]
159
}
160
case token.IDENT:
161
expectLit = e.lit
162
case token.NUMBER, token.FLOAT, token.STRING, token.NULL, token.BOOL:
163
expectLit = e.lit
164
}
165
assert.Equal(t, expectLit, lit)
166
167
if tok == token.EOF {
168
break
169
}
170
171
// Update position
172
expectPos.Offset += len(e.lit) + len(whitespace)
173
expectPos.Line += newlineCount(e.lit) + whitespaceLinecount
174
}
175
176
if s.NumErrors() != 0 {
177
assert.Zero(t, s.NumErrors(), "expected number of scanning errors to be 0")
178
}
179
}
180
181
func newlineCount(s string) int {
182
var n int
183
for i := 0; i < len(s); i++ {
184
if s[i] == '\n' {
185
n++
186
}
187
}
188
return n
189
}
190
191
func checkPos(t *testing.T, lit string, tok token.Token, p token.Pos, expected token.Position) {
192
t.Helper()
193
194
pos := p.Position()
195
196
// Check cleaned filenames so that we don't have to worry about different
197
// os.PathSeparator values.
198
if pos.Filename != expected.Filename && filepath.Clean(pos.Filename) != filepath.Clean(expected.Filename) {
199
assert.Equal(t, expected.Filename, pos.Filename, "Bad filename for %s (%q)", tok, lit)
200
}
201
202
assert.Equal(t, expected.Offset, pos.Offset, "Bad offset for %s (%q)", tok, lit)
203
assert.Equal(t, expected.Line, pos.Line, "Bad line for %s (%q)", tok, lit)
204
assert.Equal(t, expected.Column, pos.Column, "Bad column for %s (%q)", tok, lit)
205
}
206
207
var errorTests = []struct {
208
input string
209
tok token.Token
210
pos int
211
lit string
212
err string
213
}{
214
{"\a", token.ILLEGAL, 0, "", "illegal character U+0007"},
215
{`…`, token.ILLEGAL, 0, "", "illegal character U+2026 '…'"},
216
{"..", token.DOT, 0, "", ""}, // two periods, not invalid token (golang/go#28112)
217
{`'illegal string'`, token.ILLEGAL, 0, "", "illegal single-quoted string; use double quotes"},
218
{`""`, token.STRING, 0, `""`, ""},
219
{`"abc`, token.STRING, 0, `"abc`, "string literal not terminated"},
220
{"\"abc\n", token.STRING, 0, `"abc`, "string literal not terminated"},
221
{"\"abc\n ", token.STRING, 0, `"abc`, "string literal not terminated"},
222
{"\"abc\x00def\"", token.STRING, 4, "\"abc\x00def\"", "illegal character NUL"},
223
{"\"abc\x80def\"", token.STRING, 4, "\"abc\x80def\"", "illegal UTF-8 encoding"},
224
{"\ufeff\ufeff", token.ILLEGAL, 3, "\ufeff\ufeff", "illegal byte order mark"}, // only first BOM is ignored
225
{"//\ufeff", token.COMMENT, 2, "//\ufeff", "illegal byte order mark"}, // only first BOM is ignored
226
{`"` + "abc\ufeffdef" + `"`, token.STRING, 4, `"` + "abc\ufeffdef" + `"`, "illegal byte order mark"}, // only first BOM is ignored
227
{"abc\x00def", token.IDENT, 3, "abc", "illegal character NUL"},
228
{"abc\x00", token.IDENT, 3, "abc", "illegal character NUL"},
229
{"10E", token.FLOAT, 0, "10E", "exponent has no digits"},
230
}
231
232
func TestScanner_Scan_Errors(t *testing.T) {
233
for _, e := range errorTests {
234
checkError(t, e.input, e.tok, e.pos, e.lit, e.err)
235
}
236
}
237
238
func checkError(t *testing.T, src string, tok token.Token, pos int, lit, err string) {
239
t.Helper()
240
241
var (
242
actualErrors int
243
latestError string
244
latestPos token.Pos
245
)
246
247
eh := func(pos token.Pos, msg string) {
248
actualErrors++
249
latestError = msg
250
latestPos = pos
251
}
252
253
f := token.NewFile(t.Name())
254
s := New(f, []byte(src), eh, IncludeComments|dontInsertTerms)
255
256
_, actualTok, actualLit := s.Scan()
257
258
assert.Equal(t, tok, actualTok)
259
if actualTok != token.ILLEGAL {
260
assert.Equal(t, lit, actualLit)
261
}
262
263
expectErrors := 0
264
if err != "" {
265
expectErrors = 1
266
}
267
268
assert.Equal(t, expectErrors, actualErrors, "Unexpected error count in src %q", src)
269
assert.Equal(t, err, latestError, "Unexpected error message in src %q", src)
270
assert.Equal(t, pos, latestPos.Offset(), "Unexpected offset in src %q", src)
271
}
272
273