mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Merge pull request #660 from prometheus/fabxc/pql/parse-errs
Fix and improve parsing error output.
This commit is contained in:
commit
6649306e63
|
@ -15,7 +15,6 @@ package promql
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
@ -35,6 +34,8 @@ func (i item) String() string {
|
|||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ == itemIdentifier || i.typ == itemMetricIdentifier:
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
case i.typ.isKeyword():
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case i.typ.isOperator():
|
||||
|
@ -183,6 +184,16 @@ var key = map[string]itemType{
|
|||
// These are the default string representations for common items. It does not
|
||||
// imply that those are the only character sequences that can be lexed to such an item.
|
||||
var itemTypeStr = map[itemType]string{
|
||||
itemLeftParen: "(",
|
||||
itemRightParen: ")",
|
||||
itemLeftBrace: "{",
|
||||
itemRightBrace: "}",
|
||||
itemLeftBracket: "[",
|
||||
itemRightBracket: "]",
|
||||
itemComma: ",",
|
||||
itemAssign: "=",
|
||||
itemSemicolon: ";",
|
||||
|
||||
itemSUB: "-",
|
||||
itemADD: "+",
|
||||
itemMUL: "*",
|
||||
|
@ -209,7 +220,39 @@ func (t itemType) String() string {
|
|||
if s, ok := itemTypeStr[t]; ok {
|
||||
return s
|
||||
}
|
||||
return reflect.TypeOf(t).Name()
|
||||
return fmt.Sprintf("<item %d>", t)
|
||||
}
|
||||
|
||||
func (i item) desc() string {
|
||||
if _, ok := itemTypeStr[i.typ]; ok {
|
||||
return i.String()
|
||||
}
|
||||
if i.typ == itemEOF {
|
||||
return i.typ.desc()
|
||||
}
|
||||
return fmt.Sprintf("%s %s", i.typ.desc(), i)
|
||||
}
|
||||
|
||||
func (t itemType) desc() string {
|
||||
switch t {
|
||||
case itemError:
|
||||
return "error"
|
||||
case itemEOF:
|
||||
return "end of input"
|
||||
case itemComment:
|
||||
return "comment"
|
||||
case itemIdentifier:
|
||||
return "identifier"
|
||||
case itemMetricIdentifier:
|
||||
return "metric identifier"
|
||||
case itemString:
|
||||
return "string"
|
||||
case itemNumber:
|
||||
return "number"
|
||||
case itemDuration:
|
||||
return "duration"
|
||||
}
|
||||
return fmt.Sprintf("%q", t)
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
@ -377,7 +420,7 @@ func lexStatements(l *lexer) stateFn {
|
|||
l.next()
|
||||
l.emit(itemEQL)
|
||||
} else if t == '~' {
|
||||
return l.errorf("unrecognized character after '=': %#U", t)
|
||||
return l.errorf("unexpected character after '=': %q", t)
|
||||
} else {
|
||||
l.emit(itemAssign)
|
||||
}
|
||||
|
@ -385,7 +428,7 @@ func lexStatements(l *lexer) stateFn {
|
|||
if t := l.next(); t == '=' {
|
||||
l.emit(itemNEQ)
|
||||
} else {
|
||||
return l.errorf("unrecognized character after '!': %#U", t)
|
||||
return l.errorf("unexpected character after '!': %q", t)
|
||||
}
|
||||
case r == '<':
|
||||
if t := l.peek(); t == '=' {
|
||||
|
@ -401,7 +444,7 @@ func lexStatements(l *lexer) stateFn {
|
|||
} else {
|
||||
l.emit(itemGTR)
|
||||
}
|
||||
case '0' <= r && r <= '9' || r == '.':
|
||||
case unicode.IsDigit(r) || (r == '.' && unicode.IsDigit(l.peek())):
|
||||
l.backup()
|
||||
return lexNumberOrDuration
|
||||
case r == '"' || r == '\'':
|
||||
|
@ -422,7 +465,7 @@ func lexStatements(l *lexer) stateFn {
|
|||
}
|
||||
}
|
||||
fallthrough
|
||||
case isAlphaNumeric(r):
|
||||
case isAlphaNumeric(r) || r == ':':
|
||||
l.backup()
|
||||
return lexKeywordOrIdentifier
|
||||
case r == '(':
|
||||
|
@ -433,7 +476,7 @@ func lexStatements(l *lexer) stateFn {
|
|||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right parenthesis %#U", r)
|
||||
return l.errorf("unexpected right parenthesis %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
case r == '{':
|
||||
|
@ -442,20 +485,20 @@ func lexStatements(l *lexer) stateFn {
|
|||
return lexInsideBraces(l)
|
||||
case r == '[':
|
||||
if l.bracketOpen {
|
||||
return l.errorf("unexpected left bracket %#U", r)
|
||||
return l.errorf("unexpected left bracket %q", r)
|
||||
}
|
||||
l.emit(itemLeftBracket)
|
||||
l.bracketOpen = true
|
||||
return lexDuration
|
||||
case r == ']':
|
||||
if !l.bracketOpen {
|
||||
return l.errorf("unexpected right bracket %#U", r)
|
||||
return l.errorf("unexpected right bracket %q", r)
|
||||
}
|
||||
l.emit(itemRightBracket)
|
||||
l.bracketOpen = false
|
||||
|
||||
default:
|
||||
return l.errorf("unrecognized character in statement: %#U", r)
|
||||
return l.errorf("unexpected character: %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
@ -469,10 +512,10 @@ func lexInsideBraces(l *lexer) stateFn {
|
|||
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return l.errorf("unexpected EOF inside braces")
|
||||
return l.errorf("unexpected end of input inside braces")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case isAlphaNumeric(r):
|
||||
case unicode.IsLetter(r) || r == '_':
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == ',':
|
||||
|
@ -494,16 +537,16 @@ func lexInsideBraces(l *lexer) stateFn {
|
|||
case nr == '=':
|
||||
l.emit(itemNEQ)
|
||||
default:
|
||||
return l.errorf("unrecognized character after '!' inside braces: %#U", nr)
|
||||
return l.errorf("unexpected character after '!' inside braces: %q", nr)
|
||||
}
|
||||
case r == '{':
|
||||
return l.errorf("unexpected left brace %#U", r)
|
||||
return l.errorf("unexpected left brace %q", r)
|
||||
case r == '}':
|
||||
l.emit(itemRightBrace)
|
||||
l.braceOpen = false
|
||||
return lexStatements
|
||||
default:
|
||||
return l.errorf("unrecognized character inside braces: %#U", r)
|
||||
return l.errorf("unexpected character inside braces: %q", r)
|
||||
}
|
||||
return lexInsideBraces
|
||||
}
|
||||
|
@ -553,7 +596,11 @@ func lexDuration(l *lexer) stateFn {
|
|||
return l.errorf("missing unit character in duration")
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") && !isAlphaNumeric(l.peek()) {
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
|
@ -576,7 +623,11 @@ func lexNumberOrDuration(l *lexer) stateFn {
|
|||
return lexStatements
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") && !isAlphaNumeric(l.peek()) {
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad number or duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
|
@ -605,7 +656,8 @@ func (l *lexer) scanNumber() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric identifier.
|
||||
// lexIdentifier scans an alphanumeric identifier. The next character
|
||||
// is known to be a letter.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
for isAlphaNumeric(l.next()) {
|
||||
// absorb
|
||||
|
@ -651,5 +703,5 @@ func isEndOfLine(r rune) bool {
|
|||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
return r == '_' || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') || unicode.IsDigit(r)
|
||||
}
|
||||
|
|
|
@ -245,6 +245,9 @@ var tests = []struct {
|
|||
},
|
||||
// Test Selector.
|
||||
{
|
||||
input: `台北`,
|
||||
fail: true,
|
||||
}, {
|
||||
input: `{foo="bar"}`,
|
||||
expected: []item{
|
||||
{itemLeftBrace, 0, `{`},
|
||||
|
|
|
@ -101,7 +101,7 @@ func (p *parser) parseExpr() (expr Expr, err error) {
|
|||
continue
|
||||
}
|
||||
if expr != nil {
|
||||
p.errorf("expression read but input remaining")
|
||||
p.errorf("could not parse remaining input %.15q...", p.lex.input[p.lex.lastPos:])
|
||||
}
|
||||
expr = p.expr()
|
||||
}
|
||||
|
@ -132,6 +132,9 @@ func (p *parser) next() item {
|
|||
}
|
||||
p.token[0] = t
|
||||
}
|
||||
if p.token[p.peekCount].typ == itemError {
|
||||
p.errorf("%s", p.token[p.peekCount].val)
|
||||
}
|
||||
return p.token[p.peekCount]
|
||||
}
|
||||
|
||||
|
@ -175,28 +178,23 @@ func (p *parser) error(err error) {
|
|||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (p *parser) expect(expected itemType, context string) item {
|
||||
func (p *parser) expect(exp itemType, context string) item {
|
||||
token := p.next()
|
||||
if token.typ != expected {
|
||||
p.unexpected(token, context)
|
||||
if token.typ != exp {
|
||||
p.errorf("unexpected %s in %s, expected %s", token.desc(), context, exp.desc())
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
func (p *parser) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
func (p *parser) expectOneOf(exp1, exp2 itemType, context string) item {
|
||||
token := p.next()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
p.unexpected(token, context)
|
||||
if token.typ != exp1 && token.typ != exp2 {
|
||||
p.errorf("unexpected %s in %s, expected %s or %s", token.desc(), context, exp1.desc(), exp2.desc())
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (p *parser) unexpected(token item, context string) {
|
||||
p.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (p *parser) recover(errp *error) {
|
||||
e := recover()
|
||||
|
@ -303,10 +301,11 @@ func (p *parser) recordStmt() *RecordStmt {
|
|||
|
||||
// expr parses any expression.
|
||||
func (p *parser) expr() Expr {
|
||||
const ctx = "binary expression"
|
||||
|
||||
// Parse the starting expression.
|
||||
expr := p.unaryExpr()
|
||||
if expr == nil {
|
||||
p.errorf("no valid expression found")
|
||||
}
|
||||
|
||||
// Loop through the operations and construct a binary operation tree based
|
||||
// on the operators' precedence.
|
||||
|
@ -354,6 +353,9 @@ func (p *parser) expr() Expr {
|
|||
|
||||
// Parse the next operand.
|
||||
rhs := p.unaryExpr()
|
||||
if rhs == nil {
|
||||
p.errorf("missing right-hand side in binary expression")
|
||||
}
|
||||
|
||||
// Assign the new root based on the precendence of the LHS and RHS operators.
|
||||
if lhs, ok := expr.(*BinaryExpr); ok && lhs.Op.precedence() < op.precedence() {
|
||||
|
@ -497,7 +499,6 @@ func (p *parser) primaryExpr() Expr {
|
|||
p.backup()
|
||||
return p.aggrExpr()
|
||||
}
|
||||
p.errorf("invalid primary expression")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -535,7 +536,7 @@ func (p *parser) aggrExpr() *AggregateExpr {
|
|||
|
||||
agop := p.next()
|
||||
if !agop.typ.isAggregator() {
|
||||
p.errorf("%s is not an aggregation operator", agop)
|
||||
p.errorf("expected aggregation operator but got %s", agop)
|
||||
}
|
||||
var grouping clientmodel.LabelNames
|
||||
var keepExtra bool
|
||||
|
@ -650,7 +651,7 @@ func (p *parser) labelMatchers(operators ...itemType) metric.LabelMatchers {
|
|||
|
||||
op := p.next().typ
|
||||
if !op.isOperator() {
|
||||
p.errorf("item %s is not a valid operator for label matching", op)
|
||||
p.errorf("expected label matching operator but got %s", op)
|
||||
}
|
||||
var validOp = false
|
||||
for _, allowedOp := range operators {
|
||||
|
@ -849,10 +850,10 @@ func (p *parser) checkType(node Node) (typ ExprType) {
|
|||
case *Call:
|
||||
nargs := len(n.Func.ArgTypes)
|
||||
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) {
|
||||
p.errorf("expected at least %d arguments in call to %q, got %d", na, n.Func.Name, len(n.Args))
|
||||
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
|
||||
}
|
||||
if nargs < len(n.Args) {
|
||||
p.errorf("expected at most %d arguments in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
|
||||
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
|
||||
}
|
||||
for i, arg := range n.Args {
|
||||
p.expectType(arg, n.Func.ArgTypes[i], fmt.Sprintf("call to function %q", n.Func.Name))
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -25,9 +26,10 @@ import (
|
|||
)
|
||||
|
||||
var testExpr = []struct {
|
||||
input string
|
||||
expected Expr
|
||||
fail bool
|
||||
input string // The input to be parsed.
|
||||
expected Expr // The expected expression AST.
|
||||
fail bool // Whether parsing is supposed to fail.
|
||||
errMsg string // If not empty the parsing error has to contain this string.
|
||||
}{
|
||||
// Scalars and scalar-to-scalar operations.
|
||||
{
|
||||
|
@ -122,39 +124,77 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
}, {
|
||||
input: "", fail: true,
|
||||
input: "",
|
||||
fail: true,
|
||||
errMsg: "no expression found in input",
|
||||
}, {
|
||||
input: "# just a comment\n\n", fail: true,
|
||||
input: "# just a comment\n\n",
|
||||
fail: true,
|
||||
errMsg: "no expression found in input",
|
||||
}, {
|
||||
input: "1+", fail: true,
|
||||
input: "1+",
|
||||
fail: true,
|
||||
errMsg: "missing right-hand side in binary expression",
|
||||
}, {
|
||||
input: "2.5.", fail: true,
|
||||
input: ".",
|
||||
fail: true,
|
||||
errMsg: "unexpected character: '.'",
|
||||
}, {
|
||||
input: "100..4", fail: true,
|
||||
input: "2.5.",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \".\"...",
|
||||
}, {
|
||||
input: "0deadbeef", fail: true,
|
||||
input: "100..4",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \".4\"...",
|
||||
}, {
|
||||
input: "1 /", fail: true,
|
||||
input: "0deadbeef",
|
||||
fail: true,
|
||||
errMsg: "bad number or duration syntax: \"0de\"",
|
||||
}, {
|
||||
input: "*1", fail: true,
|
||||
input: "1 /",
|
||||
fail: true,
|
||||
errMsg: "missing right-hand side in binary expression",
|
||||
}, {
|
||||
input: "(1))", fail: true,
|
||||
input: "*1",
|
||||
fail: true,
|
||||
errMsg: "no valid expression found",
|
||||
}, {
|
||||
input: "((1)", fail: true,
|
||||
input: "(1))",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \")\"...",
|
||||
}, {
|
||||
input: "(", fail: true,
|
||||
input: "((1)",
|
||||
fail: true,
|
||||
errMsg: "unclosed left parenthesis",
|
||||
}, {
|
||||
input: "1 and 1", fail: true,
|
||||
input: "(",
|
||||
fail: true,
|
||||
errMsg: "unclosed left parenthesis",
|
||||
}, {
|
||||
input: "1 or 1", fail: true,
|
||||
input: "1 and 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 !~ 1", fail: true,
|
||||
input: "1 or 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 =~ 1", fail: true,
|
||||
input: "1 !~ 1",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"!~ 1\"...",
|
||||
}, {
|
||||
input: "-some_metric", fail: true,
|
||||
input: "1 =~ 1",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"=~ 1\"...",
|
||||
}, {
|
||||
input: `-"string"`, fail: true,
|
||||
input: "-some_metric",
|
||||
fail: true,
|
||||
errMsg: "expected type scalar in unary expression, got vector",
|
||||
}, {
|
||||
input: `-"string"`,
|
||||
fail: true,
|
||||
errMsg: "expected type scalar in unary expression, got string",
|
||||
},
|
||||
// Vector binary operations.
|
||||
{
|
||||
|
@ -397,25 +437,45 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
}, {
|
||||
input: "foo and 1", fail: true,
|
||||
input: "foo and 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 and foo", fail: true,
|
||||
input: "1 and foo",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "foo or 1", fail: true,
|
||||
input: "foo or 1",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 or foo", fail: true,
|
||||
input: "1 or foo",
|
||||
fail: true,
|
||||
errMsg: "AND and OR not allowed in binary scalar expression",
|
||||
}, {
|
||||
input: "1 or on(bar) foo", fail: true,
|
||||
input: "1 or on(bar) foo",
|
||||
fail: true,
|
||||
errMsg: "vector matching only allowed between vectors",
|
||||
}, {
|
||||
input: "foo == on(bar) 10", fail: true,
|
||||
input: "foo == on(bar) 10",
|
||||
fail: true,
|
||||
errMsg: "vector matching only allowed between vectors",
|
||||
}, {
|
||||
input: "foo and on(bar) group_left(baz) bar", fail: true,
|
||||
input: "foo and on(bar) group_left(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
}, {
|
||||
input: "foo and on(bar) group_right(baz) bar", fail: true,
|
||||
input: "foo and on(bar) group_right(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
}, {
|
||||
input: "foo or on(bar) group_left(baz) bar", fail: true,
|
||||
input: "foo or on(bar) group_left(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
}, {
|
||||
input: "foo or on(bar) group_right(baz) bar", fail: true,
|
||||
input: "foo or on(bar) group_right(baz) bar",
|
||||
fail: true,
|
||||
errMsg: "no grouping allowed for AND and OR operations",
|
||||
},
|
||||
// Test vector selector.
|
||||
{
|
||||
|
@ -470,31 +530,59 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
}, {
|
||||
input: `{`, fail: true,
|
||||
input: `{`,
|
||||
fail: true,
|
||||
errMsg: "unexpected end of input inside braces",
|
||||
}, {
|
||||
input: `}`, fail: true,
|
||||
input: `}`,
|
||||
fail: true,
|
||||
errMsg: "unexpected character: '}'",
|
||||
}, {
|
||||
input: `some{`, fail: true,
|
||||
input: `some{`,
|
||||
fail: true,
|
||||
errMsg: "unexpected end of input inside braces",
|
||||
}, {
|
||||
input: `some}`, fail: true,
|
||||
input: `some}`,
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"}\"...",
|
||||
}, {
|
||||
input: `some_metric{a=b}`, fail: true,
|
||||
input: `some_metric{a=b}`,
|
||||
fail: true,
|
||||
errMsg: "unexpected identifier \"b\" in label matching, expected string",
|
||||
}, {
|
||||
input: `some_metric{a:b="b"}`, fail: true,
|
||||
input: `some_metric{a:b="b"}`,
|
||||
fail: true,
|
||||
errMsg: "unexpected character inside braces: ':'",
|
||||
}, {
|
||||
input: `foo{a*"b"}`, fail: true,
|
||||
input: `foo{a*"b"}`,
|
||||
fail: true,
|
||||
errMsg: "unexpected character inside braces: '*'",
|
||||
}, {
|
||||
input: `foo{a>="b"}`, fail: true,
|
||||
input: `foo{a>="b"}`,
|
||||
fail: true,
|
||||
// TODO(fabxc): willingly lexing wrong tokens allows for more precrise error
|
||||
// messages from the parser - consider if this is an option.
|
||||
errMsg: "unexpected character inside braces: '>'",
|
||||
}, {
|
||||
input: `foo{gibberish}`, fail: true,
|
||||
input: `foo{gibberish}`,
|
||||
fail: true,
|
||||
errMsg: "expected label matching operator but got }",
|
||||
}, {
|
||||
input: `foo{1}`, fail: true,
|
||||
input: `foo{1}`,
|
||||
fail: true,
|
||||
errMsg: "unexpected character inside braces: '1'",
|
||||
}, {
|
||||
input: `{}`, fail: true,
|
||||
input: `{}`,
|
||||
fail: true,
|
||||
errMsg: "vector selector must contain label matchers or metric name",
|
||||
}, {
|
||||
input: `foo{__name__="bar"}`, fail: true,
|
||||
}, {
|
||||
input: `:foo`, fail: true,
|
||||
input: `foo{__name__="bar"}`,
|
||||
fail: true,
|
||||
errMsg: "metric name must not be set twice: \"foo\" or \"bar\"",
|
||||
// }, {
|
||||
// input: `:foo`,
|
||||
// fail: true,
|
||||
// errMsg: "bla",
|
||||
},
|
||||
// Test matrix selector.
|
||||
{
|
||||
|
@ -559,25 +647,45 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
}, {
|
||||
input: `foo[5mm]`, fail: true,
|
||||
input: `foo[5mm]`,
|
||||
fail: true,
|
||||
errMsg: "bad duration syntax: \"5mm\"",
|
||||
}, {
|
||||
input: `foo[0m]`, fail: true,
|
||||
input: `foo[0m]`,
|
||||
fail: true,
|
||||
errMsg: "duration must be greater than 0",
|
||||
}, {
|
||||
input: `foo[5m30s]`, fail: true,
|
||||
input: `foo[5m30s]`,
|
||||
fail: true,
|
||||
errMsg: "bad duration syntax: \"5m3\"",
|
||||
}, {
|
||||
input: `foo[5m] OFFSET 1h30m`, fail: true,
|
||||
input: `foo[5m] OFFSET 1h30m`,
|
||||
fail: true,
|
||||
errMsg: "bad number or duration syntax: \"1h3\"",
|
||||
}, {
|
||||
input: `foo[]`, fail: true,
|
||||
input: `foo[]`,
|
||||
fail: true,
|
||||
errMsg: "missing unit character in duration",
|
||||
}, {
|
||||
input: `foo[1]`, fail: true,
|
||||
input: `foo[1]`,
|
||||
fail: true,
|
||||
errMsg: "missing unit character in duration",
|
||||
}, {
|
||||
input: `some_metric[5m] OFFSET 1`, fail: true,
|
||||
input: `some_metric[5m] OFFSET 1`,
|
||||
fail: true,
|
||||
errMsg: "unexpected number \"1\" in matrix selector, expected duration",
|
||||
}, {
|
||||
input: `some_metric[5m] OFFSET 1mm`, fail: true,
|
||||
input: `some_metric[5m] OFFSET 1mm`,
|
||||
fail: true,
|
||||
errMsg: "bad number or duration syntax: \"1mm\"",
|
||||
}, {
|
||||
input: `some_metric[5m] OFFSET`, fail: true,
|
||||
input: `some_metric[5m] OFFSET`,
|
||||
fail: true,
|
||||
errMsg: "unexpected end of input in matrix selector, expected duration",
|
||||
}, {
|
||||
input: `(foo + bar)[5m]`, fail: true,
|
||||
input: `(foo + bar)[5m]`,
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"[5m]\"...",
|
||||
},
|
||||
// Test aggregation.
|
||||
{
|
||||
|
@ -692,21 +800,37 @@ var testExpr = []struct {
|
|||
Grouping: clientmodel.LabelNames{"foo"},
|
||||
},
|
||||
}, {
|
||||
input: `sum some_metric by (test)`, fail: true,
|
||||
input: `sum some_metric by (test)`,
|
||||
fail: true,
|
||||
errMsg: "unexpected identifier \"some_metric\" in aggregation, expected \"(\"",
|
||||
}, {
|
||||
input: `sum (some_metric) by test`, fail: true,
|
||||
input: `sum (some_metric) by test`,
|
||||
fail: true,
|
||||
errMsg: "unexpected identifier \"test\" in grouping opts, expected \"(\"",
|
||||
}, {
|
||||
input: `sum (some_metric) by ()`, fail: true,
|
||||
input: `sum (some_metric) by ()`,
|
||||
fail: true,
|
||||
errMsg: "unexpected \")\" in grouping opts, expected identifier",
|
||||
}, {
|
||||
input: `sum (some_metric) by test`, fail: true,
|
||||
input: `sum (some_metric) by test`,
|
||||
fail: true,
|
||||
errMsg: "unexpected identifier \"test\" in grouping opts, expected \"(\"",
|
||||
}, {
|
||||
input: `some_metric[5m] OFFSET`, fail: true,
|
||||
input: `some_metric[5m] OFFSET`,
|
||||
fail: true,
|
||||
errMsg: "unexpected end of input in matrix selector, expected duration",
|
||||
}, {
|
||||
input: `sum () by (test)`, fail: true,
|
||||
input: `sum () by (test)`,
|
||||
fail: true,
|
||||
errMsg: "no valid expression found",
|
||||
}, {
|
||||
input: "MIN keeping_extra (some_metric) by (foo)", fail: true,
|
||||
input: "MIN keeping_extra (some_metric) by (foo)",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"by (foo)\"...",
|
||||
}, {
|
||||
input: "MIN by(test) (some_metric) keeping_extra", fail: true,
|
||||
input: "MIN by(test) (some_metric) keeping_extra",
|
||||
fail: true,
|
||||
errMsg: "could not parse remaining input \"keeping_extra\"...",
|
||||
},
|
||||
// Test function calls.
|
||||
{
|
||||
|
@ -770,21 +894,30 @@ var testExpr = []struct {
|
|||
},
|
||||
},
|
||||
}, {
|
||||
input: "floor()", fail: true,
|
||||
input: "floor()",
|
||||
fail: true,
|
||||
errMsg: "expected at least 1 argument(s) in call to \"floor\", got 0",
|
||||
}, {
|
||||
input: "floor(some_metric, other_metric)", fail: true,
|
||||
input: "floor(some_metric, other_metric)",
|
||||
fail: true,
|
||||
errMsg: "expected at most 1 argument(s) in call to \"floor\", got 2",
|
||||
}, {
|
||||
input: "floor(1)", fail: true,
|
||||
input: "floor(1)",
|
||||
fail: true,
|
||||
errMsg: "expected type vector in call to function \"floor\", got scalar",
|
||||
}, {
|
||||
input: "non_existant_function_far_bar()", fail: true,
|
||||
input: "non_existant_function_far_bar()",
|
||||
fail: true,
|
||||
errMsg: "unknown function with name \"non_existant_function_far_bar\"",
|
||||
}, {
|
||||
input: "rate(some_metric)", fail: true,
|
||||
input: "rate(some_metric)",
|
||||
fail: true,
|
||||
errMsg: "expected type matrix in call to function \"rate\", got vector",
|
||||
},
|
||||
}
|
||||
|
||||
func TestParseExpressions(t *testing.T) {
|
||||
for _, test := range testExpr {
|
||||
|
||||
parser := newParser(test.input)
|
||||
|
||||
expr, err := parser.parseExpr()
|
||||
|
@ -793,6 +926,10 @@ func TestParseExpressions(t *testing.T) {
|
|||
t.Fatalf("could not parse: %s", err)
|
||||
}
|
||||
if test.fail && err != nil {
|
||||
if !strings.Contains(err.Error(), test.errMsg) {
|
||||
t.Errorf("unexpected error on input '%s'", test.input)
|
||||
t.Fatalf("expected error to contain %q but got %q", test.errMsg, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -804,6 +941,10 @@ func TestParseExpressions(t *testing.T) {
|
|||
|
||||
if test.fail {
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), test.errMsg) {
|
||||
t.Errorf("unexpected error on input '%s'", test.input)
|
||||
t.Fatalf("expected error to contain %q but got %q", test.errMsg, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.Errorf("error on input '%s'", test.input)
|
||||
|
|
|
@ -72,6 +72,9 @@ func Tree(node Node) string {
|
|||
}
|
||||
|
||||
func tree(node Node, level string) string {
|
||||
if node == nil {
|
||||
return fmt.Sprintf("%s |---- %T\n", level, node)
|
||||
}
|
||||
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
|
||||
|
||||
var t string
|
||||
|
|
|
@ -77,7 +77,7 @@ func TestQuery(t *testing.T) {
|
|||
{
|
||||
queryStr: "expr=(badexpression",
|
||||
status: http.StatusOK,
|
||||
bodyRe: `{"type":"error","value":"Parse error at char 15: unexpected unclosed left parenthesis in paren expression","version":1}`,
|
||||
bodyRe: `{"type":"error","value":"Parse error at char 15: unclosed left parenthesis","version":1}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue