package parser
import (
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/grafana/agent/pkg/river/diag"
"github.com/grafana/agent/pkg/river/scanner"
"github.com/grafana/agent/pkg/river/token"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`)
func expectedErrors(file *token.File, src []byte) map[token.Pos]string {
errors := make(map[token.Pos]string)
s := scanner.New(file, src, nil, scanner.IncludeComments)
var (
prev token.Pos
here token.Pos
)
for {
pos, tok, lit := s.Scan()
switch tok {
case token.EOF:
return errors
case token.COMMENT:
s := errRx.FindStringSubmatch(lit)
if len(s) == 3 {
pos := prev
if s[1] == "HERE" {
pos = here
}
errors[pos] = s[2]
}
case token.TERMINATOR:
if lit == "\n" {
break
}
fallthrough
default:
prev = pos
var l int
if isLiteral(tok) {
l = len(lit)
} else {
l = len(tok.String())
}
here = prev.Add(l)
}
}
}
func isLiteral(t token.Token) bool {
switch t {
case token.IDENT, token.NUMBER, token.FLOAT, token.STRING:
return true
}
return false
}
func compareErrors(t *testing.T, file *token.File, expected map[token.Pos]string, found diag.Diagnostics) {
t.Helper()
for _, checkError := range found {
pos := file.Pos(checkError.StartPos.Offset)
if msg, found := expected[pos]; found {
rx, err := regexp.Compile(msg)
if !assert.NoError(t, err) {
continue
}
assert.True(t,
rx.MatchString(checkError.Message),
"%s: %q does not match %q",
checkError.StartPos, checkError.Message, msg,
)
delete(expected, pos)
} else {
assert.Fail(t,
"Unexpected error",
"unexpected error: %s: %s", checkError.StartPos.String(), checkError.Message,
)
}
}
if len(expected) > 0 {
t.Errorf("%d errors not reported:", len(expected))
for pos, msg := range expected {
t.Errorf("%s: %s\n", file.PositionFor(pos), msg)
}
}
}
func TestErrors(t *testing.T) {
list, err := os.ReadDir("testdata")
require.NoError(t, err)
for _, d := range list {
name := d.Name()
if d.IsDir() || !strings.HasSuffix(name, ".river") {
continue
}
t.Run(name, func(t *testing.T) {
checkErrors(t, filepath.Join("testdata", name))
})
}
}
func checkErrors(t *testing.T, filename string) {
t.Helper()
src, err := os.ReadFile(filename)
require.NoError(t, err)
p := newParser(filename, src)
_ = p.ParseFile()
expected := expectedErrors(p.file, src)
compareErrors(t, p.file, expected, p.diags)
}