prometheus/promql/parser/lex_test.go
SuperQ 6043298a9f
Apply struct alignment to promql package
Apply struct alignment with betteralign to the promql packages.

Signed-off-by: SuperQ <superq@gmail.com>
2024-07-21 09:02:38 +02:00

939 lines
25 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 (
"testing"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/promql/parser/posrange"
)
type testCase struct {
input string
expected []Item
fail bool
seriesDesc bool // Whether to lex a series description.
}
var tests = []struct {
name string
tests []testCase
}{
{
name: "common",
tests: []testCase{
{
input: ",",
expected: []Item{{Typ: COMMA, Pos: 0, Val: ","}},
}, {
input: "()",
expected: []Item{{Typ: LEFT_PAREN, Pos: 0, Val: `(`}, {Typ: RIGHT_PAREN, Pos: 1, Val: `)`}},
}, {
input: "{}",
expected: []Item{{Typ: LEFT_BRACE, Pos: 0, Val: `{`}, {Typ: RIGHT_BRACE, Pos: 1, Val: `}`}},
}, {
input: "[5m]",
expected: []Item{
{Typ: LEFT_BRACKET, Pos: 0, Val: `[`},
{Typ: DURATION, Pos: 1, Val: `5m`},
{Typ: RIGHT_BRACKET, Pos: 3, Val: `]`},
},
}, {
input: "[ 5m]",
expected: []Item{
{Typ: LEFT_BRACKET, Pos: 0, Val: `[`},
{Typ: DURATION, Pos: 2, Val: `5m`},
{Typ: RIGHT_BRACKET, Pos: 4, Val: `]`},
},
}, {
input: "[ 5m]",
expected: []Item{
{Typ: LEFT_BRACKET, Pos: 0, Val: `[`},
{Typ: DURATION, Pos: 3, Val: `5m`},
{Typ: RIGHT_BRACKET, Pos: 5, Val: `]`},
},
}, {
input: "[ 5m ]",
expected: []Item{
{Typ: LEFT_BRACKET, Pos: 0, Val: `[`},
{Typ: DURATION, Pos: 3, Val: `5m`},
{Typ: RIGHT_BRACKET, Pos: 6, Val: `]`},
},
}, {
input: "\r\n\r",
expected: []Item{},
},
},
},
{
name: "numbers",
tests: []testCase{
{
input: "1",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "1"}},
}, {
input: "4.23",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "4.23"}},
}, {
input: ".3",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: ".3"}},
}, {
input: "5.",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "5."}},
}, {
input: "NaN",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "NaN"}},
}, {
input: "nAN",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "nAN"}},
}, {
input: "NaN 123",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "NaN"}, {Typ: NUMBER, Pos: 4, Val: "123"}},
}, {
input: "NaN123",
expected: []Item{{Typ: IDENTIFIER, Pos: 0, Val: "NaN123"}},
}, {
input: "iNf",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "iNf"}},
}, {
input: "Inf",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "Inf"}},
}, {
input: "+Inf",
expected: []Item{{Typ: ADD, Pos: 0, Val: "+"}, {Typ: NUMBER, Pos: 1, Val: "Inf"}},
}, {
input: "+Inf 123",
expected: []Item{{Typ: ADD, Pos: 0, Val: "+"}, {Typ: NUMBER, Pos: 1, Val: "Inf"}, {Typ: NUMBER, Pos: 5, Val: "123"}},
}, {
input: "-Inf",
expected: []Item{{Typ: SUB, Pos: 0, Val: "-"}, {Typ: NUMBER, Pos: 1, Val: "Inf"}},
}, {
input: "Infoo",
expected: []Item{{Typ: IDENTIFIER, Pos: 0, Val: "Infoo"}},
}, {
input: "-Infoo",
expected: []Item{{Typ: SUB, Pos: 0, Val: "-"}, {Typ: IDENTIFIER, Pos: 1, Val: "Infoo"}},
}, {
input: "-Inf 123",
expected: []Item{{Typ: SUB, Pos: 0, Val: "-"}, {Typ: NUMBER, Pos: 1, Val: "Inf"}, {Typ: NUMBER, Pos: 5, Val: "123"}},
}, {
input: "0x123",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "0x123"}},
}, {
input: "1..2",
fail: true,
}, {
input: "1.2.",
fail: true,
}, {
input: "00_1_23_4.56_7_8",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "00_1_23_4.56_7_8"}},
}, {
input: "00_1_23__4.56_7_8",
fail: true,
}, {
input: "00_1_23_4._56_7_8",
fail: true,
}, {
input: "00_1_23_4_.56_7_8",
fail: true,
}, {
input: "0x1_2_34",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "0x1_2_34"}},
}, {
input: "0x1_2__34",
fail: true,
}, {
input: "0x1_2__34.5_6p1", // "0x1.1p1"-based formats are not supported yet.
fail: true,
}, {
input: "0x1_2__34.5_6",
fail: true,
}, {
input: "0x1_2__34.56",
fail: true,
}, {
input: "1_e2",
fail: true,
}, {
input: "1.e2",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "1.e2"}},
}, {
input: "1e.2",
fail: true,
}, {
input: "1e+.2",
fail: true,
}, {
input: "1ee2",
fail: true,
}, {
input: "1e+e2",
fail: true,
}, {
input: "1e",
fail: true,
}, {
input: "1e+",
fail: true,
}, {
input: "1e1_2_34",
expected: []Item{{Typ: NUMBER, Pos: 0, Val: "1e1_2_34"}},
}, {
input: "1e_1_2_34",
fail: true,
}, {
input: "1e1_2__34",
fail: true,
}, {
input: "1e+_1_2_34",
fail: true,
}, {
input: "1e-_1_2_34",
fail: true,
}, {
input: "12_",
fail: true,
}, {
input: "_1_2",
expected: []Item{{Typ: IDENTIFIER, Pos: 0, Val: "_1_2"}},
},
},
},
{
name: "strings",
tests: []testCase{
{
input: "\"test\\tsequence\"",
expected: []Item{{Typ: STRING, Pos: 0, Val: `"test\tsequence"`}},
},
{
input: "\"test\\\\.expression\"",
expected: []Item{{Typ: STRING, Pos: 0, Val: `"test\\.expression"`}},
},
{
input: "\"test\\.expression\"",
expected: []Item{
{Typ: ERROR, Pos: 0, Val: "unknown escape sequence U+002E '.'"},
{Typ: STRING, Pos: 0, Val: `"test\.expression"`},
},
},
{
input: "`test\\.expression`",
expected: []Item{{Typ: STRING, Pos: 0, Val: "`test\\.expression`"}},
},
{
// See https://github.com/prometheus/prometheus/issues/939.
input: ".٩",
fail: true,
},
},
},
{
name: "durations",
tests: []testCase{
{
input: "5s",
expected: []Item{{Typ: DURATION, Pos: 0, Val: "5s"}},
}, {
input: "123m",
expected: []Item{{Typ: DURATION, Pos: 0, Val: "123m"}},
}, {
input: "1h",
expected: []Item{{Typ: DURATION, Pos: 0, Val: "1h"}},
}, {
input: "3w",
expected: []Item{{Typ: DURATION, Pos: 0, Val: "3w"}},
}, {
input: "1y",
expected: []Item{{Typ: DURATION, Pos: 0, Val: "1y"}},
},
},
},
{
name: "identifiers",
tests: []testCase{
{
input: "abc",
expected: []Item{{Typ: IDENTIFIER, Pos: 0, Val: "abc"}},
}, {
input: "a:bc",
expected: []Item{{Typ: METRIC_IDENTIFIER, Pos: 0, Val: "a:bc"}},
}, {
input: "abc d",
expected: []Item{{Typ: IDENTIFIER, Pos: 0, Val: "abc"}, {Typ: IDENTIFIER, Pos: 4, Val: "d"}},
}, {
input: ":bc",
expected: []Item{{Typ: METRIC_IDENTIFIER, Pos: 0, Val: ":bc"}},
}, {
input: "0a:bc",
fail: true,
},
},
},
{
name: "comments",
tests: []testCase{
{
input: "# some comment",
expected: []Item{{Typ: COMMENT, Pos: 0, Val: "# some comment"}},
}, {
input: "5 # 1+1\n5",
expected: []Item{
{Typ: NUMBER, Pos: 0, Val: "5"},
{Typ: COMMENT, Pos: 2, Val: "# 1+1"},
{Typ: NUMBER, Pos: 8, Val: "5"},
},
},
},
},
{
name: "operators",
tests: []testCase{
{
input: `=`,
expected: []Item{{Typ: EQL, Pos: 0, Val: `=`}},
}, {
// Inside braces equality is a single '=' character but in terms of a token
// it should be treated as ASSIGN.
input: `{=}`,
expected: []Item{{Typ: LEFT_BRACE, Pos: 0, Val: `{`}, {Typ: EQL, Pos: 1, Val: `=`}, {Typ: RIGHT_BRACE, Pos: 2, Val: `}`}},
}, {
input: `==`,
expected: []Item{{Typ: EQLC, Pos: 0, Val: `==`}},
}, {
input: `!=`,
expected: []Item{{Typ: NEQ, Pos: 0, Val: `!=`}},
}, {
input: `<`,
expected: []Item{{Typ: LSS, Pos: 0, Val: `<`}},
}, {
input: `>`,
expected: []Item{{Typ: GTR, Pos: 0, Val: `>`}},
}, {
input: `>=`,
expected: []Item{{Typ: GTE, Pos: 0, Val: `>=`}},
}, {
input: `<=`,
expected: []Item{{Typ: LTE, Pos: 0, Val: `<=`}},
}, {
input: `+`,
expected: []Item{{Typ: ADD, Pos: 0, Val: `+`}},
}, {
input: `-`,
expected: []Item{{Typ: SUB, Pos: 0, Val: `-`}},
}, {
input: `*`,
expected: []Item{{Typ: MUL, Pos: 0, Val: `*`}},
}, {
input: `/`,
expected: []Item{{Typ: DIV, Pos: 0, Val: `/`}},
}, {
input: `^`,
expected: []Item{{Typ: POW, Pos: 0, Val: `^`}},
}, {
input: `%`,
expected: []Item{{Typ: MOD, Pos: 0, Val: `%`}},
}, {
input: `AND`,
expected: []Item{{Typ: LAND, Pos: 0, Val: `AND`}},
}, {
input: `or`,
expected: []Item{{Typ: LOR, Pos: 0, Val: `or`}},
}, {
input: `unless`,
expected: []Item{{Typ: LUNLESS, Pos: 0, Val: `unless`}},
}, {
input: `@`,
expected: []Item{{Typ: AT, Pos: 0, Val: `@`}},
},
},
},
{
name: "aggregators",
tests: []testCase{
{
input: `sum`,
expected: []Item{{Typ: SUM, Pos: 0, Val: `sum`}},
}, {
input: `AVG`,
expected: []Item{{Typ: AVG, Pos: 0, Val: `AVG`}},
}, {
input: `GROUP`,
expected: []Item{{Typ: GROUP, Pos: 0, Val: `GROUP`}},
}, {
input: `MAX`,
expected: []Item{{Typ: MAX, Pos: 0, Val: `MAX`}},
}, {
input: `min`,
expected: []Item{{Typ: MIN, Pos: 0, Val: `min`}},
}, {
input: `count`,
expected: []Item{{Typ: COUNT, Pos: 0, Val: `count`}},
}, {
input: `stdvar`,
expected: []Item{{Typ: STDVAR, Pos: 0, Val: `stdvar`}},
}, {
input: `stddev`,
expected: []Item{{Typ: STDDEV, Pos: 0, Val: `stddev`}},
},
},
},
{
name: "keywords",
tests: []testCase{
{
input: "offset",
expected: []Item{{Typ: OFFSET, Pos: 0, Val: "offset"}},
},
{
input: "by",
expected: []Item{{Typ: BY, Pos: 0, Val: "by"}},
},
{
input: "without",
expected: []Item{{Typ: WITHOUT, Pos: 0, Val: "without"}},
},
{
input: "on",
expected: []Item{{Typ: ON, Pos: 0, Val: "on"}},
},
{
input: "ignoring",
expected: []Item{{Typ: IGNORING, Pos: 0, Val: "ignoring"}},
},
{
input: "group_left",
expected: []Item{{Typ: GROUP_LEFT, Pos: 0, Val: "group_left"}},
},
{
input: "group_right",
expected: []Item{{Typ: GROUP_RIGHT, Pos: 0, Val: "group_right"}},
},
{
input: "bool",
expected: []Item{{Typ: BOOL, Pos: 0, Val: "bool"}},
},
{
input: "atan2",
expected: []Item{{Typ: ATAN2, Pos: 0, Val: "atan2"}},
},
},
},
{
name: "preprocessors",
tests: []testCase{
{
input: `start`,
expected: []Item{{Typ: START, Pos: 0, Val: `start`}},
},
{
input: `end`,
expected: []Item{{Typ: END, Pos: 0, Val: `end`}},
},
},
},
{
name: "selectors",
tests: []testCase{
{
input: `台北`,
fail: true,
}, {
input: `{台北='a'}`,
fail: true,
}, {
input: `{0a='a'}`,
fail: true,
}, {
input: `{foo='bar'}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `foo`},
{Typ: EQL, Pos: 4, Val: `=`},
{Typ: STRING, Pos: 5, Val: `'bar'`},
{Typ: RIGHT_BRACE, Pos: 10, Val: `}`},
},
}, {
input: `{foo="bar"}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `foo`},
{Typ: EQL, Pos: 4, Val: `=`},
{Typ: STRING, Pos: 5, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 10, Val: `}`},
},
}, {
input: `{foo="bar\"bar"}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `foo`},
{Typ: EQL, Pos: 4, Val: `=`},
{Typ: STRING, Pos: 5, Val: `"bar\"bar"`},
{Typ: RIGHT_BRACE, Pos: 15, Val: `}`},
},
}, {
input: `{NaN != "bar" }`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `NaN`},
{Typ: NEQ, Pos: 5, Val: `!=`},
{Typ: STRING, Pos: 8, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 14, Val: `}`},
},
}, {
input: `{alert=~"bar" }`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `alert`},
{Typ: EQL_REGEX, Pos: 6, Val: `=~`},
{Typ: STRING, Pos: 8, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 14, Val: `}`},
},
}, {
input: `{on!~"bar"}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: IDENTIFIER, Pos: 1, Val: `on`},
{Typ: NEQ_REGEX, Pos: 3, Val: `!~`},
{Typ: STRING, Pos: 5, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 10, Val: `}`},
},
}, {
input: `{alert!#"bar"}`, fail: true,
}, {
input: `{foo:a="bar"}`, fail: true,
},
},
},
{
name: "common errors",
tests: []testCase{
{
input: `=~`, fail: true,
}, {
input: `!~`, fail: true,
}, {
input: `!(`, fail: true,
}, {
input: "1a", fail: true,
},
},
},
{
name: "mismatched parentheses",
tests: []testCase{
{
input: `(`, fail: true,
}, {
input: `())`, fail: true,
}, {
input: `(()`, fail: true,
}, {
input: `{`, fail: true,
}, {
input: `}`, fail: true,
}, {
input: "{{", fail: true,
}, {
input: "{{}}", fail: true,
}, {
input: `[`, fail: true,
}, {
input: `[[`, fail: true,
}, {
input: `[]]`, fail: true,
}, {
input: `[[]]`, fail: true,
}, {
input: `]`, fail: true,
},
},
},
{
name: "encoding issues",
tests: []testCase{
{
input: "\"\xff\"", fail: true,
},
{
input: "`\xff`", fail: true,
},
},
},
{
name: "histogram series descriptions",
tests: []testCase{
{
input: `{} {{buckets:[5]}}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: RIGHT_BRACE, Pos: 1, Val: `}`},
{Typ: SPACE, Pos: 2, Val: ` `},
{Typ: OPEN_HIST, Pos: 3, Val: `{{`},
{Typ: BUCKETS_DESC, Pos: 5, Val: `buckets`},
{Typ: COLON, Pos: 12, Val: `:`},
{Typ: LEFT_BRACKET, Pos: 13, Val: `[`},
{Typ: NUMBER, Pos: 14, Val: `5`},
{Typ: RIGHT_BRACKET, Pos: 15, Val: `]`},
{Typ: CLOSE_HIST, Pos: 16, Val: `}}`},
},
seriesDesc: true,
},
{
input: `{} {{buckets: [5 10 7]}}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: RIGHT_BRACE, Pos: 1, Val: `}`},
{Typ: SPACE, Pos: 2, Val: ` `},
{Typ: OPEN_HIST, Pos: 3, Val: `{{`},
{Typ: BUCKETS_DESC, Pos: 5, Val: `buckets`},
{Typ: COLON, Pos: 12, Val: `:`},
{Typ: SPACE, Pos: 13, Val: ` `},
{Typ: LEFT_BRACKET, Pos: 14, Val: `[`},
{Typ: NUMBER, Pos: 15, Val: `5`},
{Typ: SPACE, Pos: 16, Val: ` `},
{Typ: NUMBER, Pos: 17, Val: `10`},
{Typ: SPACE, Pos: 19, Val: ` `},
{Typ: NUMBER, Pos: 20, Val: `7`},
{Typ: RIGHT_BRACKET, Pos: 21, Val: `]`},
{Typ: CLOSE_HIST, Pos: 22, Val: `}}`},
},
seriesDesc: true,
},
{
input: `{} {{buckets: [5 10 7] schema:1}}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: RIGHT_BRACE, Pos: 1, Val: `}`},
{Typ: SPACE, Pos: 2, Val: ` `},
{Typ: OPEN_HIST, Pos: 3, Val: `{{`},
{Typ: BUCKETS_DESC, Pos: 5, Val: `buckets`},
{Typ: COLON, Pos: 12, Val: `:`},
{Typ: SPACE, Pos: 13, Val: ` `},
{Typ: LEFT_BRACKET, Pos: 14, Val: `[`},
{Typ: NUMBER, Pos: 15, Val: `5`},
{Typ: SPACE, Pos: 16, Val: ` `},
{Typ: NUMBER, Pos: 17, Val: `10`},
{Typ: SPACE, Pos: 19, Val: ` `},
{Typ: NUMBER, Pos: 20, Val: `7`},
{Typ: RIGHT_BRACKET, Pos: 21, Val: `]`},
{Typ: SPACE, Pos: 22, Val: ` `},
{Typ: SCHEMA_DESC, Pos: 23, Val: `schema`},
{Typ: COLON, Pos: 29, Val: `:`},
{Typ: NUMBER, Pos: 30, Val: `1`},
{Typ: CLOSE_HIST, Pos: 31, Val: `}}`},
},
seriesDesc: true,
},
{ // Series with sum as -Inf and count as NaN.
input: `{} {{buckets: [5 10 7] sum:Inf count:NaN}}`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: RIGHT_BRACE, Pos: 1, Val: `}`},
{Typ: SPACE, Pos: 2, Val: ` `},
{Typ: OPEN_HIST, Pos: 3, Val: `{{`},
{Typ: BUCKETS_DESC, Pos: 5, Val: `buckets`},
{Typ: COLON, Pos: 12, Val: `:`},
{Typ: SPACE, Pos: 13, Val: ` `},
{Typ: LEFT_BRACKET, Pos: 14, Val: `[`},
{Typ: NUMBER, Pos: 15, Val: `5`},
{Typ: SPACE, Pos: 16, Val: ` `},
{Typ: NUMBER, Pos: 17, Val: `10`},
{Typ: SPACE, Pos: 19, Val: ` `},
{Typ: NUMBER, Pos: 20, Val: `7`},
{Typ: RIGHT_BRACKET, Pos: 21, Val: `]`},
{Typ: SPACE, Pos: 22, Val: ` `},
{Typ: SUM_DESC, Pos: 23, Val: `sum`},
{Typ: COLON, Pos: 26, Val: `:`},
{Typ: NUMBER, Pos: 27, Val: `Inf`},
{Typ: SPACE, Pos: 30, Val: ` `},
{Typ: COUNT_DESC, Pos: 31, Val: `count`},
{Typ: COLON, Pos: 36, Val: `:`},
{Typ: NUMBER, Pos: 37, Val: `NaN`},
{Typ: CLOSE_HIST, Pos: 40, Val: `}}`},
},
seriesDesc: true,
},
},
},
{
name: "series descriptions",
tests: []testCase{
{
input: `{} _ 1 x .3`,
expected: []Item{
{Typ: LEFT_BRACE, Pos: 0, Val: `{`},
{Typ: RIGHT_BRACE, Pos: 1, Val: `}`},
{Typ: SPACE, Pos: 2, Val: ` `},
{Typ: BLANK, Pos: 3, Val: `_`},
{Typ: SPACE, Pos: 4, Val: ` `},
{Typ: NUMBER, Pos: 5, Val: `1`},
{Typ: SPACE, Pos: 6, Val: ` `},
{Typ: TIMES, Pos: 7, Val: `x`},
{Typ: SPACE, Pos: 8, Val: ` `},
{Typ: NUMBER, Pos: 9, Val: `.3`},
},
seriesDesc: true,
},
{
input: `metric +Inf Inf NaN`,
expected: []Item{
{Typ: IDENTIFIER, Pos: 0, Val: `metric`},
{Typ: SPACE, Pos: 6, Val: ` `},
{Typ: ADD, Pos: 7, Val: `+`},
{Typ: NUMBER, Pos: 8, Val: `Inf`},
{Typ: SPACE, Pos: 11, Val: ` `},
{Typ: NUMBER, Pos: 12, Val: `Inf`},
{Typ: SPACE, Pos: 15, Val: ` `},
{Typ: NUMBER, Pos: 16, Val: `NaN`},
},
seriesDesc: true,
},
{
input: `metric 1+1x4`,
expected: []Item{
{Typ: IDENTIFIER, Pos: 0, Val: `metric`},
{Typ: SPACE, Pos: 6, Val: ` `},
{Typ: NUMBER, Pos: 7, Val: `1`},
{Typ: ADD, Pos: 8, Val: `+`},
{Typ: NUMBER, Pos: 9, Val: `1`},
{Typ: TIMES, Pos: 10, Val: `x`},
{Typ: NUMBER, Pos: 11, Val: `4`},
},
seriesDesc: true,
},
},
},
{
name: "subqueries",
tests: []testCase{
{
input: `test_name{on!~"bar"}[4m:4s]`,
expected: []Item{
{Typ: IDENTIFIER, Pos: 0, Val: `test_name`},
{Typ: LEFT_BRACE, Pos: 9, Val: `{`},
{Typ: IDENTIFIER, Pos: 10, Val: `on`},
{Typ: NEQ_REGEX, Pos: 12, Val: `!~`},
{Typ: STRING, Pos: 14, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 19, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 20, Val: `[`},
{Typ: DURATION, Pos: 21, Val: `4m`},
{Typ: COLON, Pos: 23, Val: `:`},
{Typ: DURATION, Pos: 24, Val: `4s`},
{Typ: RIGHT_BRACKET, Pos: 26, Val: `]`},
},
},
{
input: `test:name{on!~"bar"}[4m:4s]`,
expected: []Item{
{Typ: METRIC_IDENTIFIER, Pos: 0, Val: `test:name`},
{Typ: LEFT_BRACE, Pos: 9, Val: `{`},
{Typ: IDENTIFIER, Pos: 10, Val: `on`},
{Typ: NEQ_REGEX, Pos: 12, Val: `!~`},
{Typ: STRING, Pos: 14, Val: `"bar"`},
{Typ: RIGHT_BRACE, Pos: 19, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 20, Val: `[`},
{Typ: DURATION, Pos: 21, Val: `4m`},
{Typ: COLON, Pos: 23, Val: `:`},
{Typ: DURATION, Pos: 24, Val: `4s`},
{Typ: RIGHT_BRACKET, Pos: 26, Val: `]`},
},
},
{
input: `test:name{on!~"b:ar"}[4m:4s]`,
expected: []Item{
{Typ: METRIC_IDENTIFIER, Pos: 0, Val: `test:name`},
{Typ: LEFT_BRACE, Pos: 9, Val: `{`},
{Typ: IDENTIFIER, Pos: 10, Val: `on`},
{Typ: NEQ_REGEX, Pos: 12, Val: `!~`},
{Typ: STRING, Pos: 14, Val: `"b:ar"`},
{Typ: RIGHT_BRACE, Pos: 20, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 21, Val: `[`},
{Typ: DURATION, Pos: 22, Val: `4m`},
{Typ: COLON, Pos: 24, Val: `:`},
{Typ: DURATION, Pos: 25, Val: `4s`},
{Typ: RIGHT_BRACKET, Pos: 27, Val: `]`},
},
},
{
input: `test:name{on!~"b:ar"}[4m:]`,
expected: []Item{
{Typ: METRIC_IDENTIFIER, Pos: 0, Val: `test:name`},
{Typ: LEFT_BRACE, Pos: 9, Val: `{`},
{Typ: IDENTIFIER, Pos: 10, Val: `on`},
{Typ: NEQ_REGEX, Pos: 12, Val: `!~`},
{Typ: STRING, Pos: 14, Val: `"b:ar"`},
{Typ: RIGHT_BRACE, Pos: 20, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 21, Val: `[`},
{Typ: DURATION, Pos: 22, Val: `4m`},
{Typ: COLON, Pos: 24, Val: `:`},
{Typ: RIGHT_BRACKET, Pos: 25, Val: `]`},
},
},
{ // Nested Subquery.
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`,
expected: []Item{
{Typ: IDENTIFIER, Pos: 0, Val: `min_over_time`},
{Typ: LEFT_PAREN, Pos: 13, Val: `(`},
{Typ: IDENTIFIER, Pos: 14, Val: `rate`},
{Typ: LEFT_PAREN, Pos: 18, Val: `(`},
{Typ: IDENTIFIER, Pos: 19, Val: `foo`},
{Typ: LEFT_BRACE, Pos: 22, Val: `{`},
{Typ: IDENTIFIER, Pos: 23, Val: `bar`},
{Typ: EQL, Pos: 26, Val: `=`},
{Typ: STRING, Pos: 27, Val: `"baz"`},
{Typ: RIGHT_BRACE, Pos: 32, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 33, Val: `[`},
{Typ: DURATION, Pos: 34, Val: `2s`},
{Typ: RIGHT_BRACKET, Pos: 36, Val: `]`},
{Typ: RIGHT_PAREN, Pos: 37, Val: `)`},
{Typ: LEFT_BRACKET, Pos: 38, Val: `[`},
{Typ: DURATION, Pos: 39, Val: `5m`},
{Typ: COLON, Pos: 41, Val: `:`},
{Typ: RIGHT_BRACKET, Pos: 42, Val: `]`},
{Typ: RIGHT_PAREN, Pos: 43, Val: `)`},
{Typ: LEFT_BRACKET, Pos: 44, Val: `[`},
{Typ: DURATION, Pos: 45, Val: `4m`},
{Typ: COLON, Pos: 47, Val: `:`},
{Typ: DURATION, Pos: 48, Val: `3s`},
{Typ: RIGHT_BRACKET, Pos: 50, Val: `]`},
},
},
// Subquery with offset.
{
input: `test:name{on!~"b:ar"}[4m:4s] offset 10m`,
expected: []Item{
{Typ: METRIC_IDENTIFIER, Pos: 0, Val: `test:name`},
{Typ: LEFT_BRACE, Pos: 9, Val: `{`},
{Typ: IDENTIFIER, Pos: 10, Val: `on`},
{Typ: NEQ_REGEX, Pos: 12, Val: `!~`},
{Typ: STRING, Pos: 14, Val: `"b:ar"`},
{Typ: RIGHT_BRACE, Pos: 20, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 21, Val: `[`},
{Typ: DURATION, Pos: 22, Val: `4m`},
{Typ: COLON, Pos: 24, Val: `:`},
{Typ: DURATION, Pos: 25, Val: `4s`},
{Typ: RIGHT_BRACKET, Pos: 27, Val: `]`},
{Typ: OFFSET, Pos: 29, Val: "offset"},
{Typ: DURATION, Pos: 36, Val: "10m"},
},
},
{
input: `min_over_time(rate(foo{bar="baz"}[2s])[5m:] offset 6m)[4m:3s]`,
expected: []Item{
{Typ: IDENTIFIER, Pos: 0, Val: `min_over_time`},
{Typ: LEFT_PAREN, Pos: 13, Val: `(`},
{Typ: IDENTIFIER, Pos: 14, Val: `rate`},
{Typ: LEFT_PAREN, Pos: 18, Val: `(`},
{Typ: IDENTIFIER, Pos: 19, Val: `foo`},
{Typ: LEFT_BRACE, Pos: 22, Val: `{`},
{Typ: IDENTIFIER, Pos: 23, Val: `bar`},
{Typ: EQL, Pos: 26, Val: `=`},
{Typ: STRING, Pos: 27, Val: `"baz"`},
{Typ: RIGHT_BRACE, Pos: 32, Val: `}`},
{Typ: LEFT_BRACKET, Pos: 33, Val: `[`},
{Typ: DURATION, Pos: 34, Val: `2s`},
{Typ: RIGHT_BRACKET, Pos: 36, Val: `]`},
{Typ: RIGHT_PAREN, Pos: 37, Val: `)`},
{Typ: LEFT_BRACKET, Pos: 38, Val: `[`},
{Typ: DURATION, Pos: 39, Val: `5m`},
{Typ: COLON, Pos: 41, Val: `:`},
{Typ: RIGHT_BRACKET, Pos: 42, Val: `]`},
{Typ: OFFSET, Pos: 44, Val: `offset`},
{Typ: DURATION, Pos: 51, Val: `6m`},
{Typ: RIGHT_PAREN, Pos: 53, Val: `)`},
{Typ: LEFT_BRACKET, Pos: 54, Val: `[`},
{Typ: DURATION, Pos: 55, Val: `4m`},
{Typ: COLON, Pos: 57, Val: `:`},
{Typ: DURATION, Pos: 58, Val: `3s`},
{Typ: RIGHT_BRACKET, Pos: 60, Val: `]`},
},
},
{
input: `test:name[ 5m]`,
expected: []Item{
{Typ: METRIC_IDENTIFIER, Pos: 0, Val: `test:name`},
{Typ: LEFT_BRACKET, Pos: 9, Val: `[`},
{Typ: DURATION, Pos: 11, Val: `5m`},
{Typ: RIGHT_BRACKET, Pos: 13, Val: `]`},
},
},
{
input: `test:name{o:n!~"bar"}[4m:4s]`,
fail: true,
},
{
input: `test:name{on!~"bar"}[4m:4s:4h]`,
fail: true,
},
{
input: `test:name{on!~"bar"}[4m:4s:]`,
fail: true,
},
{
input: `test:name{on!~"bar"}[4m::]`,
fail: true,
},
{
input: `test:name{on!~"bar"}[:4s]`,
fail: true,
},
},
},
}
// TestLexer tests basic functionality of the lexer. More elaborate tests are implemented
// for the parser to avoid duplicated effort.
func TestLexer(t *testing.T) {
for _, typ := range tests {
t.Run(typ.name, func(t *testing.T) {
for i, test := range typ.tests {
l := &Lexer{
input: test.input,
seriesDesc: test.seriesDesc,
}
var out []Item
for l.state = lexStatements; l.state != nil; {
out = append(out, Item{})
l.NextItem(&out[len(out)-1])
}
lastItem := out[len(out)-1]
if test.fail {
hasError := false
for _, item := range out {
if item.Typ == ERROR {
hasError = true
}
}
require.True(t, hasError, "%d: input %q, expected lexing error but did not fail", i, test.input)
continue
}
require.NotEqual(t, ERROR, lastItem.Typ, "%d: input %q, unexpected lexing error at position %d: %s", i, test.input, lastItem.Pos, lastItem)
eofItem := Item{Typ: EOF, Pos: posrange.Pos(len(test.input)), Val: ""}
require.Equal(t, lastItem, eofItem, "%d: input %q", i, test.input)
out = out[:len(out)-1]
require.Equal(t, test.expected, out, "%d: input %q", i, test.input)
}
})
}
}