prometheus/promql/parser/parse_test.go

2846 lines
68 KiB
Go

// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package parser
import (
"math"
"strings"
"testing"
"time"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/prometheus/prometheus/pkg/labels"
)
var testExpr = []struct {
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.
{
input: "1",
expected: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
}, {
input: "+Inf",
expected: &NumberLiteral{
Val: math.Inf(1),
PosRange: PositionRange{Start: 0, End: 4},
},
}, {
input: "-Inf",
expected: &NumberLiteral{
Val: math.Inf(-1),
PosRange: PositionRange{Start: 0, End: 4},
},
}, {
input: ".5",
expected: &NumberLiteral{
Val: 0.5,
PosRange: PositionRange{Start: 0, End: 2},
},
}, {
input: "5.",
expected: &NumberLiteral{
Val: 5,
PosRange: PositionRange{Start: 0, End: 2},
},
}, {
input: "123.4567",
expected: &NumberLiteral{
Val: 123.4567,
PosRange: PositionRange{Start: 0, End: 8},
},
}, {
input: "5e-3",
expected: &NumberLiteral{
Val: 0.005,
PosRange: PositionRange{Start: 0, End: 4},
},
}, {
input: "5e3",
expected: &NumberLiteral{
Val: 5000,
PosRange: PositionRange{Start: 0, End: 3},
},
}, {
input: "0xc",
expected: &NumberLiteral{
Val: 12,
PosRange: PositionRange{Start: 0, End: 3},
},
}, {
input: "0755",
expected: &NumberLiteral{
Val: 493,
PosRange: PositionRange{Start: 0, End: 4},
},
}, {
input: "+5.5e-3",
expected: &NumberLiteral{
Val: 0.0055,
PosRange: PositionRange{Start: 0, End: 7},
},
}, {
input: "-0755",
expected: &NumberLiteral{
Val: -493,
PosRange: PositionRange{Start: 0, End: 5},
},
}, {
input: "1 + 1",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 4, End: 5},
},
},
}, {
input: "1 - 1",
expected: &BinaryExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 4, End: 5},
},
},
}, {
input: "1 * 1",
expected: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 4, End: 5},
},
},
}, {
input: "1 % 1",
expected: &BinaryExpr{
Op: MOD,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 4, End: 5},
},
},
}, {
input: "1 / 1",
expected: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 4, End: 5},
},
},
}, {
input: "1 == bool 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
}, {
input: "1 != bool 1",
expected: &BinaryExpr{
Op: NEQ,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
}, {
input: "1 > bool 1",
expected: &BinaryExpr{
Op: GTR,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 9, End: 10},
},
ReturnBool: true,
},
}, {
input: "1 >= bool 1",
expected: &BinaryExpr{
Op: GTE,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
}, {
input: "1 < bool 1",
expected: &BinaryExpr{
Op: LSS,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 9, End: 10},
},
ReturnBool: true,
},
}, {
input: "1 <= bool 1",
expected: &BinaryExpr{
Op: LTE,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 10, End: 11},
},
ReturnBool: true,
},
}, {
input: "-1^2",
expected: &UnaryExpr{
Op: SUB,
Expr: &BinaryExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 1, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 3, End: 4},
},
},
},
}, {
input: "-1*2",
expected: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: -1,
PosRange: PositionRange{Start: 0, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 3, End: 4},
},
},
}, {
input: "-1+2",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: -1,
PosRange: PositionRange{Start: 0, End: 2},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 3, End: 4},
},
},
}, {
input: "-1^-2",
expected: &UnaryExpr{
Op: SUB,
Expr: &BinaryExpr{
Op: POW,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 1, End: 2},
},
RHS: &NumberLiteral{
Val: -2,
PosRange: PositionRange{Start: 3, End: 5},
},
},
},
}, {
input: "+1 + -2 * 1",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 2},
},
RHS: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: -2,
PosRange: PositionRange{Start: 5, End: 7},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 10, End: 11},
},
},
},
}, {
input: "1 + 2/(3*1)",
expected: &BinaryExpr{
Op: ADD,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 4, End: 5},
},
RHS: &ParenExpr{
Expr: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 3,
PosRange: PositionRange{Start: 7, End: 8},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 9, End: 10},
},
},
PosRange: PositionRange{Start: 6, End: 11},
},
},
},
}, {
input: "1 < bool 2 - 1 * 2",
expected: &BinaryExpr{
Op: LSS,
ReturnBool: true,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 0, End: 1},
},
RHS: &BinaryExpr{
Op: SUB,
LHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 9, End: 10},
},
RHS: &BinaryExpr{
Op: MUL,
LHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 13, End: 14},
},
RHS: &NumberLiteral{
Val: 2,
PosRange: PositionRange{Start: 17, End: 18},
},
},
},
},
}, {
input: "-some_metric",
expected: &UnaryExpr{
Op: SUB,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 1,
End: 12,
},
},
},
}, {
input: "+some_metric",
expected: &UnaryExpr{
Op: ADD,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 1,
End: 12,
},
},
},
}, {
input: " +some_metric",
expected: &UnaryExpr{
Op: ADD,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 2,
End: 13,
},
},
StartPos: 1,
},
}, {
input: "",
fail: true,
errMsg: "no expression found in input",
}, {
input: "# just a comment\n\n",
fail: true,
errMsg: "no expression found in input",
}, {
input: "1+",
fail: true,
errMsg: "unexpected end of input",
}, {
input: ".",
fail: true,
errMsg: "unexpected character: '.'",
}, {
input: "2.5.",
fail: true,
errMsg: "unexpected character: '.'",
}, {
input: "100..4",
fail: true,
errMsg: `unexpected number ".4"`,
}, {
input: "0deadbeef",
fail: true,
errMsg: "bad number or duration syntax: \"0de\"",
}, {
input: "1 /",
fail: true,
errMsg: "unexpected end of input",
}, {
input: "*1",
fail: true,
errMsg: "unexpected <op:*>",
}, {
input: "(1))",
fail: true,
errMsg: "unexpected right parenthesis ')'",
}, {
input: "((1)",
fail: true,
errMsg: "unclosed left parenthesis",
}, {
input: "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
fail: true,
errMsg: "out of range",
}, {
input: "(",
fail: true,
errMsg: "unclosed left parenthesis",
}, {
input: "1 and 1",
fail: true,
errMsg: "set operator \"and\" not allowed in binary scalar expression",
}, {
input: "1 == 1",
fail: true,
errMsg: "1:3: parse error: comparisons between scalars must use BOOL modifier",
}, {
input: "1 or 1",
fail: true,
errMsg: "set operator \"or\" not allowed in binary scalar expression",
}, {
input: "1 unless 1",
fail: true,
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
}, {
input: "1 !~ 1",
fail: true,
errMsg: `unexpected character after '!': '~'`,
}, {
input: "1 =~ 1",
fail: true,
errMsg: `unexpected character after '=': '~'`,
}, {
input: `-"string"`,
fail: true,
errMsg: `unary expression only allowed on expressions of type scalar or instant vector, got "string"`,
}, {
input: `-test[5m]`,
fail: true,
errMsg: `unary expression only allowed on expressions of type scalar or instant vector, got "range vector"`,
}, {
input: `*test`,
fail: true,
errMsg: "unexpected <op:*>",
}, {
input: "1 offset 1d",
fail: true,
errMsg: "offset modifier must be preceded by an instant or range selector",
}, {
input: "foo offset 1s offset 2s",
fail: true,
errMsg: "offset may not be set multiple times",
}, {
input: "a - on(b) ignoring(c) d",
fail: true,
errMsg: "1:11: parse error: unexpected <ignoring>",
},
// Vector binary operations.
{
input: "foo * bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 6,
End: 9,
},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
}, {
input: "foo * sum",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "sum"),
},
PosRange: PositionRange{
Start: 6,
End: 9,
},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
}, {
input: "foo == 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 7, End: 8},
},
},
}, {
input: "foo == bool 1",
expected: &BinaryExpr{
Op: EQLC,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &NumberLiteral{
Val: 1,
PosRange: PositionRange{Start: 12, End: 13},
},
ReturnBool: true,
},
}, {
input: "2.5 / bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &NumberLiteral{
Val: 2.5,
PosRange: PositionRange{Start: 0, End: 3},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 6,
End: 9,
},
},
},
}, {
input: "foo and bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 8,
End: 11,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
}, {
input: "foo or bar",
expected: &BinaryExpr{
Op: LOR,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 7,
End: 10,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
}, {
input: "foo unless bar",
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 11,
End: 14,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
}, {
// Test and/or precedence and reassigning of operands.
input: "foo + bar or bla and blub",
expected: &BinaryExpr{
Op: LOR,
LHS: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 6,
End: 9,
},
},
VectorMatching: &VectorMatching{Card: CardOneToOne},
},
RHS: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "bla",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bla"),
},
PosRange: PositionRange{
Start: 13,
End: 16,
},
},
RHS: &VectorSelector{
Name: "blub",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "blub"),
},
PosRange: PositionRange{
Start: 21,
End: 25,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
}, {
// Test and/or/unless precedence.
input: "foo and bar unless baz or qux",
expected: &BinaryExpr{
Op: LOR,
LHS: &BinaryExpr{
Op: LUNLESS,
LHS: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 8,
End: 11,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
RHS: &VectorSelector{
Name: "baz",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "baz"),
},
PosRange: PositionRange{
Start: 19,
End: 22,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
RHS: &VectorSelector{
Name: "qux",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "qux"),
},
PosRange: PositionRange{
Start: 26,
End: 29,
},
},
VectorMatching: &VectorMatching{Card: CardManyToMany},
},
}, {
// Test precedence and reassigning of operands.
input: "bar + on(foo) bla / on(baz, buz) group_right(test) blub",
expected: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "bla",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bla"),
},
PosRange: PositionRange{
Start: 14,
End: 17,
},
},
RHS: &VectorSelector{
Name: "blub",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "blub"),
},
PosRange: PositionRange{
Start: 51,
End: 55,
},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"baz", "buz"},
On: true,
Include: []string{"test"},
},
},
VectorMatching: &VectorMatching{
Card: CardOneToOne,
MatchingLabels: []string{"foo"},
On: true,
},
},
}, {
input: "foo * on(test,blub) bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 20,
End: 23,
},
},
VectorMatching: &VectorMatching{
Card: CardOneToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
}, {
input: "foo * on(test,blub) group_left bar",
expected: &BinaryExpr{
Op: MUL,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 31,
End: 34,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
}, {
input: "foo and on(test,blub) bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 22,
End: 25,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"test", "blub"},
On: true,
},
},
}, {
input: "foo and on() bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 13,
End: 16,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{},
On: true,
},
},
}, {
input: "foo and ignoring(test,blub) bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 28,
End: 31,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"test", "blub"},
},
},
}, {
input: "foo and ignoring() bar",
expected: &BinaryExpr{
Op: LAND,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 19,
End: 22,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{},
},
},
}, {
input: "foo unless on(bar) baz",
expected: &BinaryExpr{
Op: LUNLESS,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "baz",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "baz"),
},
PosRange: PositionRange{
Start: 19,
End: 22,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToMany,
MatchingLabels: []string{"bar"},
On: true,
},
},
}, {
input: "foo / on(test,blub) group_left(bar) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 36,
End: 39,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
On: true,
Include: []string{"bar"},
},
},
}, {
input: "foo / ignoring(test,blub) group_left(blub) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 43,
End: 46,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
Include: []string{"blub"},
},
},
}, {
input: "foo / ignoring(test,blub) group_left(bar) bar",
expected: &BinaryExpr{
Op: DIV,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 42,
End: 45,
},
},
VectorMatching: &VectorMatching{
Card: CardManyToOne,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar"},
},
},
}, {
input: "foo - on(test,blub) group_right(bar,foo) bar",
expected: &BinaryExpr{
Op: SUB,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 41,
End: 44,
},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar", "foo"},
On: true,
},
},
}, {
input: "foo - ignoring(test,blub) group_right(bar,foo) bar",
expected: &BinaryExpr{
Op: SUB,
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 47,
End: 50,
},
},
VectorMatching: &VectorMatching{
Card: CardOneToMany,
MatchingLabels: []string{"test", "blub"},
Include: []string{"bar", "foo"},
},
},
}, {
input: "foo and 1",
fail: true,
errMsg: "set operator \"and\" not allowed in binary scalar expression",
}, {
input: "1 and foo",
fail: true,
errMsg: "set operator \"and\" not allowed in binary scalar expression",
}, {
input: "foo or 1",
fail: true,
errMsg: "set operator \"or\" not allowed in binary scalar expression",
}, {
input: "1 or foo",
fail: true,
errMsg: "set operator \"or\" not allowed in binary scalar expression",
}, {
input: "foo unless 1",
fail: true,
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
}, {
input: "1 unless foo",
fail: true,
errMsg: "set operator \"unless\" not allowed in binary scalar expression",
}, {
input: "1 or on(bar) foo",
fail: true,
errMsg: "vector matching only allowed between instant vectors",
}, {
input: "foo == on(bar) 10",
fail: true,
errMsg: "vector matching only allowed between instant vectors",
}, {
input: "foo + group_left(baz) bar",
fail: true,
errMsg: "unexpected <group_left>",
}, {
input: "foo and on(bar) group_left(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"and\" operation",
}, {
input: "foo and on(bar) group_right(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"and\" operation",
}, {
input: "foo or on(bar) group_left(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"or\" operation",
}, {
input: "foo or on(bar) group_right(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"or\" operation",
}, {
input: "foo unless on(bar) group_left(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"unless\" operation",
}, {
input: "foo unless on(bar) group_right(baz) bar",
fail: true,
errMsg: "no grouping allowed for \"unless\" operation",
}, {
input: `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`,
fail: true,
errMsg: "label \"instance\" must not occur in ON and GROUP clause at once",
}, {
input: "foo + bool bar",
fail: true,
errMsg: "bool modifier can only be used on comparison operators",
}, {
input: "foo + bool 10",
fail: true,
errMsg: "bool modifier can only be used on comparison operators",
}, {
input: "foo and bool 10",
fail: true,
errMsg: "bool modifier can only be used on comparison operators",
},
// Test Vector selector.
{
input: "foo",
expected: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
}, {
input: "min",
expected: &VectorSelector{
Name: "min",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "min"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
}, {
input: "foo offset 5m",
expected: &VectorSelector{
Name: "foo",
Offset: 5 * time.Minute,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 13,
},
},
}, {
input: `foo OFFSET 1h30m`,
expected: &VectorSelector{
Name: "foo",
Offset: 90 * time.Minute,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 16,
},
},
}, {
input: `foo OFFSET 1m30ms`,
expected: &VectorSelector{
Name: "foo",
Offset: time.Minute + 30*time.Millisecond,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 17,
},
},
}, {
input: `foo:bar{a="bc"}`,
expected: &VectorSelector{
Name: "foo:bar",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "a", "bc"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo:bar"),
},
PosRange: PositionRange{
Start: 0,
End: 15,
},
},
}, {
input: `foo{NaN='bc'}`,
expected: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "NaN", "bc"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 13,
},
},
}, {
input: `foo{bar='}'}`,
expected: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "}"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 12,
},
},
}, {
input: `foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`,
expected: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "a", "b"),
mustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
mustLabelMatcher(labels.MatchRegexp, "test", "test"),
mustLabelMatcher(labels.MatchNotRegexp, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 48,
},
},
}, {
input: `foo{a="b", foo!="bar", test=~"test", bar!~"baz",}`,
expected: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "a", "b"),
mustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
mustLabelMatcher(labels.MatchRegexp, "test", "test"),
mustLabelMatcher(labels.MatchNotRegexp, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 49,
},
},
}, {
input: `{`,
fail: true,
errMsg: "unexpected end of input inside braces",
}, {
input: `}`,
fail: true,
errMsg: "unexpected character: '}'",
}, {
input: `some{`,
fail: true,
errMsg: "unexpected end of input inside braces",
}, {
input: `some}`,
fail: true,
errMsg: "unexpected character: '}'",
}, {
input: `some_metric{a=b}`,
fail: true,
errMsg: "unexpected identifier \"b\" in label matching, expected string",
}, {
input: `some_metric{a:b="b"}`,
fail: true,
errMsg: "unexpected character inside braces: ':'",
}, {
input: `foo{a*"b"}`,
fail: true,
errMsg: "unexpected character inside braces: '*'",
}, {
input: `foo{a>="b"}`,
fail: true,
// TODO(fabxc): willingly lexing wrong tokens allows for more precise error
// messages from the parser - consider if this is an option.
errMsg: "unexpected character inside braces: '>'",
}, {
input: "some_metric{a=\"\xff\"}",
fail: true,
errMsg: "1:15: parse error: invalid UTF-8 rune",
}, {
input: `foo{gibberish}`,
fail: true,
errMsg: `unexpected "}" in label matching, expected label matching operator`,
}, {
input: `foo{1}`,
fail: true,
errMsg: "unexpected character inside braces: '1'",
}, {
input: `{}`,
fail: true,
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `{x=""}`,
fail: true,
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `{x=~".*"}`,
fail: true,
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `{x!~".+"}`,
fail: true,
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `{x!="a"}`,
fail: true,
errMsg: "vector selector must contain at least one non-empty matcher",
}, {
input: `foo{__name__="bar"}`,
fail: true,
errMsg: `metric name must not be set twice: "foo" or "bar"`,
}, {
input: `foo{__name__= =}`,
fail: true,
errMsg: `1:15: parse error: unexpected "=" in label matching, expected string`,
}, {
input: `foo{,}`,
fail: true,
errMsg: `unexpected "," in label matching, expected identifier or "}"`,
}, {
input: `foo{__name__ == "bar"}`,
fail: true,
errMsg: `1:15: parse error: unexpected "=" in label matching, expected string`,
}, {
input: `foo{__name__="bar" lol}`,
fail: true,
errMsg: `unexpected identifier "lol" in label matching, expected "," or "}"`,
},
// Test matrix selector.
{
input: "test[5s]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 4,
},
},
Range: 5 * time.Second,
EndPos: 8,
},
}, {
input: "test[5m]",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 4,
},
},
Range: 5 * time.Minute,
EndPos: 8,
},
}, {
input: `foo[5m30s]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
Offset: 0,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
Range: 5*time.Minute + 30*time.Second,
EndPos: 10,
},
}, {
input: "test[5h] OFFSET 5m",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 5 * time.Minute,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 4,
},
},
Range: 5 * time.Hour,
EndPos: 18,
},
}, {
input: "test[5d] OFFSET 10s",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 10 * time.Second,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 4,
},
},
Range: 5 * 24 * time.Hour,
EndPos: 19,
},
}, {
input: "test[5w] offset 2w",
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 14 * 24 * time.Hour,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 4,
},
},
Range: 5 * 7 * 24 * time.Hour,
EndPos: 18,
},
}, {
input: `test{a="b"}[5y] OFFSET 3d`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "test",
Offset: 3 * 24 * time.Hour,
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "a", "b"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "test"),
},
PosRange: PositionRange{
Start: 0,
End: 11,
},
},
Range: 5 * 365 * 24 * time.Hour,
EndPos: 25,
},
}, {
input: `foo[5mm]`,
fail: true,
errMsg: "bad duration syntax: \"5mm\"",
}, {
input: `foo[5m1]`,
fail: true,
errMsg: "bad duration syntax: \"5m1\"",
}, {
input: `foo[5m:1m1]`,
fail: true,
errMsg: "bad number or duration syntax: \"1m1\"",
}, {
input: `foo[5y1hs]`,
fail: true,
errMsg: "not a valid duration string: \"5y1hs\"",
}, {
input: `foo[5m1h]`,
fail: true,
errMsg: "not a valid duration string: \"5m1h\"",
}, {
input: `foo[5m1m]`,
fail: true,
errMsg: "not a valid duration string: \"5m1m\"",
}, {
input: `foo[0m]`,
fail: true,
errMsg: "duration must be greater than 0",
}, {
input: `foo["5m"]`,
fail: true,
}, {
input: `foo[]`,
fail: true,
errMsg: "missing unit character in duration",
}, {
input: `foo[1]`,
fail: true,
errMsg: "missing unit character in duration",
}, {
input: `some_metric[5m] OFFSET 1`,
fail: true,
errMsg: "unexpected number \"1\" in offset, expected duration",
}, {
input: `some_metric[5m] OFFSET 1mm`,
fail: true,
errMsg: "bad number or duration syntax: \"1mm\"",
}, {
input: `some_metric[5m] OFFSET`,
fail: true,
errMsg: "unexpected end of input in offset, expected duration",
}, {
input: `some_metric OFFSET 1m[5m]`,
fail: true,
errMsg: "1:22: parse error: no offset modifiers allowed before range",
}, {
input: `(foo + bar)[5m]`,
fail: true,
errMsg: "1:12: parse error: ranges only allowed for vector selectors",
},
// Test aggregation.
{
input: "sum by (foo)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 13,
End: 24,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 25,
},
},
}, {
input: "avg by (foo)(some_metric)",
expected: &AggregateExpr{
Op: AVG,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 13,
End: 24,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 25,
},
},
}, {
input: "max by (foo)(some_metric)",
expected: &AggregateExpr{
Op: MAX,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 13,
End: 24,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 25,
},
},
}, {
input: "sum without (foo) (some_metric)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 19,
End: 30,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 31,
},
},
}, {
input: "sum (some_metric) without (foo)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 5,
End: 16,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 31,
},
},
}, {
input: "stddev(some_metric)",
expected: &AggregateExpr{
Op: STDDEV,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 7,
End: 18,
},
},
PosRange: PositionRange{
Start: 0,
End: 19,
},
},
}, {
input: "stdvar by (foo)(some_metric)",
expected: &AggregateExpr{
Op: STDVAR,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 16,
End: 27,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 28,
},
},
}, {
input: "sum by ()(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 10,
End: 21,
},
},
Grouping: []string{},
PosRange: PositionRange{
Start: 0,
End: 22,
},
},
}, {
input: "sum by (foo,bar,)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 18,
End: 29,
},
},
Grouping: []string{"foo", "bar"},
PosRange: PositionRange{
Start: 0,
End: 30,
},
},
}, {
input: "sum by (foo,)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 14,
End: 25,
},
},
Grouping: []string{"foo"},
PosRange: PositionRange{
Start: 0,
End: 26,
},
},
}, {
input: "topk(5, some_metric)",
expected: &AggregateExpr{
Op: TOPK,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 8,
End: 19,
},
},
Param: &NumberLiteral{
Val: 5,
PosRange: PositionRange{
Start: 5,
End: 6,
},
},
PosRange: PositionRange{
Start: 0,
End: 20,
},
},
}, {
input: `count_values("value", some_metric)`,
expected: &AggregateExpr{
Op: COUNT_VALUES,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 22,
End: 33,
},
},
Param: &StringLiteral{
Val: "value",
PosRange: PositionRange{
Start: 13,
End: 20,
},
},
PosRange: PositionRange{
Start: 0,
End: 34,
},
},
}, {
// Test usage of keywords as label names.
input: "sum without(and, by, avg, count, alert, annotations)(some_metric)",
expected: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 53,
End: 64,
},
},
Grouping: []string{"and", "by", "avg", "count", "alert", "annotations"},
PosRange: PositionRange{
Start: 0,
End: 65,
},
},
}, {
input: "sum without(==)(some_metric)",
fail: true,
errMsg: "unexpected <op:==> in grouping opts, expected label",
}, {
input: "sum without(,)(some_metric)",
fail: true,
errMsg: `unexpected "," in grouping opts, expected label`,
}, {
input: "sum without(foo,,)(some_metric)",
fail: true,
errMsg: `unexpected "," in grouping opts, expected label`,
}, {
input: `sum some_metric by (test)`,
fail: true,
errMsg: "unexpected identifier \"some_metric\"",
}, {
input: `sum (some_metric) by test`,
fail: true,
errMsg: "unexpected identifier \"test\" in grouping opts",
}, {
input: `sum (some_metric) by test`,
fail: true,
errMsg: "unexpected identifier \"test\" in grouping opts",
}, {
input: `sum () by (test)`,
fail: true,
errMsg: "no arguments for aggregate expression provided",
}, {
input: "MIN keep_common (some_metric)",
fail: true,
errMsg: "1:5: parse error: unexpected identifier \"keep_common\"",
}, {
input: "MIN (some_metric) keep_common",
fail: true,
errMsg: `unexpected identifier "keep_common"`,
}, {
input: `sum (some_metric) without (test) by (test)`,
fail: true,
errMsg: "unexpected <by>",
}, {
input: `sum without (test) (some_metric) by (test)`,
fail: true,
errMsg: "unexpected <by>",
}, {
input: `topk(some_metric)`,
fail: true,
errMsg: "wrong number of arguments for aggregate expression provided, expected 2, got 1",
}, {
input: `topk(some_metric,)`,
fail: true,
errMsg: "trailing commas not allowed in function call args",
}, {
input: `topk(some_metric, other_metric)`,
fail: true,
errMsg: "1:6: parse error: expected type scalar in aggregation parameter, got instant vector",
}, {
input: `count_values(5, other_metric)`,
fail: true,
errMsg: "1:14: parse error: expected type string in aggregation parameter, got scalar",
},
// Test function calls.
{
input: "time()",
expected: &Call{
Func: mustGetFunction("time"),
Args: Expressions{},
PosRange: PositionRange{
Start: 0,
End: 6,
},
},
}, {
input: `floor(some_metric{foo!="bar"})`,
expected: &Call{
Func: mustGetFunction("floor"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchNotEqual, "foo", "bar"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 6,
End: 29,
},
},
},
PosRange: PositionRange{
Start: 0,
End: 30,
},
},
}, {
input: "rate(some_metric[5m])",
expected: &Call{
Func: mustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 5,
End: 16,
},
},
Range: 5 * time.Minute,
EndPos: 20,
},
},
PosRange: PositionRange{
Start: 0,
End: 21,
},
},
}, {
input: "round(some_metric)",
expected: &Call{
Func: mustGetFunction("round"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 6,
End: 17,
},
},
},
PosRange: PositionRange{
Start: 0,
End: 18,
},
},
}, {
input: "round(some_metric, 5)",
expected: &Call{
Func: mustGetFunction("round"),
Args: Expressions{
&VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 6,
End: 17,
},
},
&NumberLiteral{
Val: 5,
PosRange: PositionRange{
Start: 19,
End: 20,
},
},
},
PosRange: PositionRange{
Start: 0,
End: 21,
},
},
}, {
input: "floor()",
fail: true,
errMsg: "expected 1 argument(s) in call to \"floor\", got 0",
}, {
input: "floor(some_metric, other_metric)",
fail: true,
errMsg: "expected 1 argument(s) in call to \"floor\", got 2",
}, {
input: "floor(some_metric, 1)",
fail: true,
errMsg: "expected 1 argument(s) in call to \"floor\", got 2",
}, {
input: "floor(1)",
fail: true,
errMsg: "expected type instant vector in call to function \"floor\", got scalar",
}, {
input: "hour(some_metric, some_metric, some_metric)",
fail: true,
errMsg: "expected at most 1 argument(s) in call to \"hour\", got 3",
}, {
input: "time(some_metric)",
fail: true,
errMsg: "expected 0 argument(s) in call to \"time\", got 1",
}, {
input: "non_existent_function_far_bar()",
fail: true,
errMsg: "unknown function with name \"non_existent_function_far_bar\"",
}, {
input: "rate(some_metric)",
fail: true,
errMsg: "expected type range vector in call to function \"rate\", got instant vector",
}, {
input: "label_replace(a, `b`, `c\xff`, `d`, `.*`)",
fail: true,
errMsg: "1:23: parse error: invalid UTF-8 rune",
},
// Fuzzing regression tests.
{
input: "-=",
fail: true,
errMsg: `unexpected "="`,
}, {
input: "++-++-+-+-<",
fail: true,
errMsg: `unexpected <op:<>`,
}, {
input: "e-+=/(0)",
fail: true,
errMsg: `unexpected "="`,
}, {
input: "a>b()",
fail: true,
errMsg: `unknown function`,
}, {
input: "rate(avg)",
fail: true,
errMsg: `expected type range vector`,
}, {
input: "sum(sum)",
expected: &AggregateExpr{
Op: SUM,
Expr: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "sum"),
},
PosRange: PositionRange{
Start: 4,
End: 7,
},
},
PosRange: PositionRange{
Start: 0,
End: 8,
},
},
}, {
input: "a + sum",
expected: &BinaryExpr{
Op: ADD,
LHS: &VectorSelector{
Name: "a",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "a"),
},
PosRange: PositionRange{
Start: 0,
End: 1,
},
},
RHS: &VectorSelector{
Name: "sum",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "sum"),
},
PosRange: PositionRange{
Start: 4,
End: 7,
},
},
VectorMatching: &VectorMatching{},
},
},
// String quoting and escape sequence interpretation tests.
{
input: `"double-quoted string \" with escaped quote"`,
expected: &StringLiteral{
Val: "double-quoted string \" with escaped quote",
PosRange: PositionRange{Start: 0, End: 44},
},
}, {
input: `'single-quoted string \' with escaped quote'`,
expected: &StringLiteral{
Val: "single-quoted string ' with escaped quote",
PosRange: PositionRange{Start: 0, End: 44},
},
}, {
input: "`backtick-quoted string`",
expected: &StringLiteral{
Val: "backtick-quoted string",
PosRange: PositionRange{Start: 0, End: 24},
},
}, {
input: `"\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺"`,
expected: &StringLiteral{
Val: "\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺",
PosRange: PositionRange{Start: 0, End: 62},
},
}, {
input: `'\a\b\f\n\r\t\v\\\' - \xFF\377\u1234\U00010111\U0001011111☺'`,
expected: &StringLiteral{
Val: "\a\b\f\n\r\t\v\\' - \xFF\377\u1234\U00010111\U0001011111☺",
PosRange: PositionRange{Start: 0, End: 62},
},
}, {
input: "`" + `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺` + "`",
expected: &StringLiteral{
Val: `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺`,
PosRange: PositionRange{Start: 0, End: 64},
},
}, {
input: "`\\``",
fail: true,
errMsg: "unterminated raw string",
}, {
input: `"\`,
fail: true,
errMsg: "escape sequence not terminated",
}, {
input: `"\c"`,
fail: true,
errMsg: "unknown escape sequence U+0063 'c'",
}, {
input: `"\x."`,
fail: true,
errMsg: "illegal character U+002E '.' in escape sequence",
},
// Subquery.
{
input: `foo{bar="baz"}[10m:6s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 14,
},
},
Range: 10 * time.Minute,
Step: 6 * time.Second,
EndPos: 22,
},
},
{
input: `foo{bar="baz"}[10m5s:1h6ms]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 14,
},
},
Range: 10*time.Minute + 5*time.Second,
Step: time.Hour + 6*time.Millisecond,
EndPos: 27,
},
}, {
input: `foo[10m:]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 0,
End: 3,
},
},
Range: 10 * time.Minute,
EndPos: 9,
},
}, {
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:5s])`,
expected: &Call{
Func: mustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: mustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 19,
End: 33,
},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: PositionRange{
Start: 14,
End: 38,
},
},
Range: 5 * time.Minute,
Step: 5 * time.Second,
EndPos: 45,
},
},
PosRange: PositionRange{
Start: 0,
End: 46,
},
},
}, {
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: mustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: mustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 19,
End: 33,
},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: PositionRange{
Start: 14,
End: 38,
},
},
Range: 5 * time.Minute,
EndPos: 43,
},
},
PosRange: PositionRange{
Start: 0,
End: 44,
},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 51,
},
}, {
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:] offset 4m)[4m:3s]`,
expected: &SubqueryExpr{
Expr: &Call{
Func: mustGetFunction("min_over_time"),
Args: Expressions{
&SubqueryExpr{
Expr: &Call{
Func: mustGetFunction("rate"),
Args: Expressions{
&MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "bar", "baz"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 19,
End: 33,
},
},
Range: 2 * time.Second,
EndPos: 37,
},
},
PosRange: PositionRange{
Start: 14,
End: 38,
},
},
Range: 5 * time.Minute,
Offset: 4 * time.Minute,
EndPos: 53,
},
},
PosRange: PositionRange{
Start: 0,
End: 54,
},
},
Range: 4 * time.Minute,
Step: 3 * time.Second,
EndPos: 61,
},
}, {
input: "sum without(and, by, avg, count, alert, annotations)(some_metric) [30m:10s]",
expected: &SubqueryExpr{
Expr: &AggregateExpr{
Op: SUM,
Without: true,
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 53,
End: 64,
},
},
Grouping: []string{"and", "by", "avg", "count", "alert", "annotations"},
PosRange: PositionRange{
Start: 0,
End: 65,
},
},
Range: 30 * time.Minute,
Step: 10 * time.Second,
EndPos: 75,
},
}, {
input: `some_metric OFFSET 1m [10m:5s]`,
expected: &SubqueryExpr{
Expr: &VectorSelector{
Name: "some_metric",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "some_metric"),
},
PosRange: PositionRange{
Start: 0,
End: 21,
},
Offset: 1 * time.Minute,
},
Range: 10 * time.Minute,
Step: 5 * time.Second,
EndPos: 30,
},
}, {
input: `(foo + bar{nm="val"})[5m:]`,
expected: &SubqueryExpr{
Expr: &ParenExpr{
Expr: &BinaryExpr{
Op: ADD,
VectorMatching: &VectorMatching{
Card: CardOneToOne,
},
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 1,
End: 4,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "nm", "val"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 7,
End: 20,
},
},
},
PosRange: PositionRange{
Start: 0,
End: 21,
},
},
Range: 5 * time.Minute,
EndPos: 26,
},
}, {
input: `(foo + bar{nm="val"})[5m:] offset 10m`,
expected: &SubqueryExpr{
Expr: &ParenExpr{
Expr: &BinaryExpr{
Op: ADD,
VectorMatching: &VectorMatching{
Card: CardOneToOne,
},
LHS: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "foo"),
},
PosRange: PositionRange{
Start: 1,
End: 4,
},
},
RHS: &VectorSelector{
Name: "bar",
LabelMatchers: []*labels.Matcher{
mustLabelMatcher(labels.MatchEqual, "nm", "val"),
mustLabelMatcher(labels.MatchEqual, string(model.MetricNameLabel), "bar"),
},
PosRange: PositionRange{
Start: 7,
End: 20,
},
},
},
PosRange: PositionRange{
Start: 0,
End: 21,
},
},
Range: 5 * time.Minute,
Offset: 10 * time.Minute,
EndPos: 37,
},
}, {
input: "test[5d] OFFSET 10s [10m:5s]",
fail: true,
errMsg: "1:1: parse error: subquery is only allowed on instant vector, got matrix in \"test[5d] offset 10s[10m:5s]\"",
}, {
input: `(foo + bar{nm="val"})[5m:][10m:5s]`,
fail: true,
errMsg: `1:1: parse error: subquery is only allowed on instant vector, got matrix in "(foo + bar{nm=\"val\"})[5m:][10m:5s]" instead`,
},
}
func TestParseExpressions(t *testing.T) {
for _, test := range testExpr {
t.Run(test.input, func(t *testing.T) {
expr, err := ParseExpr(test.input)
// Unexpected errors are always caused by a bug.
assert.True(t, err != errUnexpected, "unexpected error occurred")
if !test.fail {
assert.NoError(t, err)
assert.Equal(t, test.expected, expr, "error on input '%s'", test.input)
} else {
assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), test.errMsg), "unexpected error on input '%s', expected '%s', got '%s'", test.input, test.errMsg, err.Error())
errorList, ok := err.(ParseErrors)
assert.True(t, ok, "unexpected error type")
for _, e := range errorList {
assert.True(t, 0 <= e.PositionRange.Start, "parse error has negative position\nExpression '%s'\nError: %v", test.input, e)
assert.True(t, e.PositionRange.Start <= e.PositionRange.End, "parse error has negative length\nExpression '%s'\nError: %v", test.input, e)
assert.True(t, e.PositionRange.End <= Pos(len(test.input)), "parse error is not contained in input\nExpression '%s'\nError: %v", test.input, e)
}
}
})
}
}
// NaN has no equality. Thus, we need a separate test for it.
func TestNaNExpression(t *testing.T) {
expr, err := ParseExpr("NaN")
assert.NoError(t, err)
nl, ok := expr.(*NumberLiteral)
assert.True(t, ok, "expected number literal but got %T", expr)
assert.True(t, math.IsNaN(float64(nl.Val)), "expected 'NaN' in number literal but got %v", nl.Val)
}
func mustLabelMatcher(mt labels.MatchType, name, val string) *labels.Matcher {
m, err := labels.NewMatcher(mt, name, val)
if err != nil {
panic(err)
}
return m
}
func mustGetFunction(name string) *Function {
f, ok := getFunction(name)
if !ok {
panic(errors.Errorf("function %q does not exist", name))
}
return f
}
var testSeries = []struct {
input string
expectedMetric labels.Labels
expectedValues []SequenceValue
fail bool
}{
{
input: `{} 1 2 3`,
expectedMetric: labels.Labels{},
expectedValues: newSeq(1, 2, 3),
}, {
input: `{a="b"} -1 2 3`,
expectedMetric: labels.FromStrings("a", "b"),
expectedValues: newSeq(-1, 2, 3),
}, {
input: `my_metric 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} 1 2 3`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} 1 2 3-10x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3, -7, -17, -27, -37),
}, {
input: `my_metric{a="b"} 1 2 3-0x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3, 3, 3, 3, 3),
}, {
input: `my_metric{a="b"} 1 3 _ 5 _x4`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 3, none, 5, none, none, none, none),
}, {
input: `my_metric{a="b"} 1 3 _ 5 _a4`,
fail: true,
}, {
input: `my_metric{a="b"} 1 -1`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, -1),
}, {
input: `my_metric{a="b"} 1 +1`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 1),
}, {
input: `my_metric{a="b"} 1 -1 -3-10x4 7 9 +5`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, -1, -3, -13, -23, -33, -43, 7, 9, 5),
}, {
input: `my_metric{a="b"} 1 +1 +4 -6 -2 8`,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 1, 4, -6, -2, 8),
}, {
// Trailing spaces should be correctly handles.
input: `my_metric{a="b"} 1 2 3 `,
expectedMetric: labels.FromStrings(labels.MetricName, "my_metric", "a", "b"),
expectedValues: newSeq(1, 2, 3),
}, {
input: `my_metric{a="b"} -3-3 -3`,
fail: true,
}, {
input: `my_metric{a="b"} -3 -3-3`,
fail: true,
}, {
input: `my_metric{a="b"} -3 _-2`,
fail: true,
}, {
input: `my_metric{a="b"} -3 3+3x4-4`,
fail: true,
},
}
// For these tests only, we use the smallest float64 to signal an omitted value.
const none = math.SmallestNonzeroFloat64
func newSeq(vals ...float64) (res []SequenceValue) {
for _, v := range vals {
if v == none {
res = append(res, SequenceValue{Omitted: true})
} else {
res = append(res, SequenceValue{Value: v})
}
}
return res
}
func TestParseSeries(t *testing.T) {
for _, test := range testSeries {
metric, vals, err := ParseSeriesDesc(test.input)
// Unexpected errors are always caused by a bug.
assert.True(t, err != errUnexpected, "unexpected error occurred")
if !test.fail {
assert.NoError(t, err)
assert.Equal(t, test.expectedMetric, metric, "error on input '%s'", test.input)
assert.Equal(t, test.expectedValues, vals, "error in input '%s'", test.input)
} else {
assert.Error(t, err)
}
}
}
func TestRecoverParserRuntime(t *testing.T) {
p := newParser("foo bar")
var err error
defer func() {
assert.Equal(t, errUnexpected, err)
}()
defer p.recover(&err)
// Cause a runtime panic.
var a []int
//nolint:govet
a[123] = 1
}
func TestRecoverParserError(t *testing.T) {
p := newParser("foo bar")
var err error
e := errors.New("custom error")
defer func() {
assert.Equal(t, e.Error(), err.Error())
}()
defer p.recover(&err)
panic(e)
}