Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/parser/error_test.go
4096 views
1
package parser
2
3
import (
4
"os"
5
"path/filepath"
6
"regexp"
7
"strings"
8
"testing"
9
10
"github.com/grafana/agent/pkg/river/diag"
11
"github.com/grafana/agent/pkg/river/scanner"
12
"github.com/grafana/agent/pkg/river/token"
13
"github.com/stretchr/testify/assert"
14
"github.com/stretchr/testify/require"
15
)
16
17
// This file implements a parser test harness. The files in the testdata
18
// directory are parsed and the errors reported are compared against the error
19
// messages expected in the test files.
20
//
21
// Expected errors are indicated in the test files by putting a comment of the
22
// form /* ERROR "rx" */ immediately following an offending token. The harness
23
// will verify that an error matching the regular expression rx is reported at
24
// that source position.
25
26
// ERROR comments must be of the form /* ERROR "rx" */ and rx is a regular
27
// expression that matches the expected error message. The special form
28
// /* ERROR HERE "rx" */ must be used for error messages that appear immediately
29
// after a token rather than at a token's position.
30
var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`)
31
32
// expectedErrors collects the regular expressions of ERROR comments found in
33
// files and returns them as a map of error positions to error messages.
34
func expectedErrors(file *token.File, src []byte) map[token.Pos]string {
35
errors := make(map[token.Pos]string)
36
37
s := scanner.New(file, src, nil, scanner.IncludeComments)
38
39
var (
40
prev token.Pos // Position of last non-comment, non-terminator token
41
here token.Pos // Position following after token at prev
42
)
43
44
for {
45
pos, tok, lit := s.Scan()
46
switch tok {
47
case token.EOF:
48
return errors
49
case token.COMMENT:
50
s := errRx.FindStringSubmatch(lit)
51
if len(s) == 3 {
52
pos := prev
53
if s[1] == "HERE" {
54
pos = here
55
}
56
errors[pos] = s[2]
57
}
58
case token.TERMINATOR:
59
if lit == "\n" {
60
break
61
}
62
fallthrough
63
default:
64
prev = pos
65
var l int // Token length
66
if isLiteral(tok) {
67
l = len(lit)
68
} else {
69
l = len(tok.String())
70
}
71
here = prev.Add(l)
72
}
73
}
74
}
75
76
func isLiteral(t token.Token) bool {
77
switch t {
78
case token.IDENT, token.NUMBER, token.FLOAT, token.STRING:
79
return true
80
}
81
return false
82
}
83
84
// compareErrors compares the map of expected error messages with the list of
85
// found errors and reports mismatches.
86
func compareErrors(t *testing.T, file *token.File, expected map[token.Pos]string, found diag.Diagnostics) {
87
t.Helper()
88
89
for _, checkError := range found {
90
pos := file.Pos(checkError.StartPos.Offset)
91
92
if msg, found := expected[pos]; found {
93
// We expect a message at pos; check if it matches
94
rx, err := regexp.Compile(msg)
95
if !assert.NoError(t, err) {
96
continue
97
}
98
assert.True(t,
99
rx.MatchString(checkError.Message),
100
"%s: %q does not match %q",
101
checkError.StartPos, checkError.Message, msg,
102
)
103
delete(expected, pos) // Eliminate consumed error
104
} else {
105
assert.Fail(t,
106
"Unexpected error",
107
"unexpected error: %s: %s", checkError.StartPos.String(), checkError.Message,
108
)
109
}
110
}
111
112
// There should be no expected errors left
113
if len(expected) > 0 {
114
t.Errorf("%d errors not reported:", len(expected))
115
for pos, msg := range expected {
116
t.Errorf("%s: %s\n", file.PositionFor(pos), msg)
117
}
118
}
119
}
120
121
func TestErrors(t *testing.T) {
122
list, err := os.ReadDir("testdata")
123
require.NoError(t, err)
124
125
for _, d := range list {
126
name := d.Name()
127
if d.IsDir() || !strings.HasSuffix(name, ".river") {
128
continue
129
}
130
131
t.Run(name, func(t *testing.T) {
132
checkErrors(t, filepath.Join("testdata", name))
133
})
134
}
135
}
136
137
func checkErrors(t *testing.T, filename string) {
138
t.Helper()
139
140
src, err := os.ReadFile(filename)
141
require.NoError(t, err)
142
143
p := newParser(filename, src)
144
_ = p.ParseFile()
145
146
expected := expectedErrors(p.file, src)
147
compareErrors(t, p.file, expected, p.diags)
148
}
149
150