prometheus/web/ui/module/codemirror-promql/src/parser/parser.test.ts
Neeraj Gartia 99f9d32499
UTF-8: updates UI parser to support UTF-8 characters (#13590)
Signed-off-by: Neeraj Gartia <neerajgartia211002@gmail.com>
2024-04-29 11:14:01 +02:00

981 lines
28 KiB
TypeScript

// Copyright 2021 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.
import { Parser } from './parser';
import { Diagnostic } from '@codemirror/lint';
import { createEditorState } from '../test/utils-test';
import { syntaxTree } from '@codemirror/language';
import { ValueType } from '../types';
describe('promql operations', () => {
const testCases = [
{
expr: '1',
expectedValueType: ValueType.scalar,
expectedDiag: [] as Diagnostic[],
},
{
expr: '2 * 3',
expectedValueType: ValueType.scalar,
expectedDiag: [] as Diagnostic[],
},
{
expr: '1 unless 1',
expectedValueType: ValueType.scalar,
expectedDiag: [
{
from: 0,
to: 10,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: 'metric_name * "string"',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 14,
to: 22,
message: 'binary expression must contain only scalar and instant vector types',
severity: 'error',
},
] as Diagnostic[],
},
{
expr: 'metric_name_1 > bool metric_name_2',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'metric_name_1 + bool metric_name_2',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 34,
message: 'bool modifier can only be used on comparison operators',
severity: 'error',
},
] as Diagnostic[],
},
{
expr: 'metric_name offset 1d',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'metric_name[5m] offset 1d',
expectedValueType: ValueType.matrix,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'rate(metric_name[5m])[1h:] offset 1m',
expectedValueType: ValueType.matrix,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'sum(metric_name offset 1m)',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'rate(metric_name[5m] offset 1d)',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'mad_over_time(rate(metric_name[5m])[1h:] offset 1m)',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'max_over_time(rate(metric_name[5m])[1h:] offset 1m)',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo * bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo*bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo* bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo *bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo==bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo * sum',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo == 1',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo == bool 1',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: '2.5 / bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo or bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo unless bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
// Test and/or precedence and reassigning of operands.
// Here it will test only the first VectorMatching so (a + b) or (c and d) ==> ManyToMany
expr: 'foo + bar or bla and blub',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
// Test and/or/unless precedence.
// Here it will test only the first VectorMatching so ((a and b) unless c) or d ==> ManyToMany
expr: 'foo and bar unless baz or qux',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo * on(test,blub) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo*on(test,blub)bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo * on(test,blub) group_left bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo*on(test,blub)group_left()bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and on(test,blub) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and on(test,"blub") bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and on() bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and ignoring(test,blub) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and ignoring(test,"blub") bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and ignoring() bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo unless on(bar) baz',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo / on(test,blub) group_left(bar) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo / on(test,blub) group_left("bar") bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo / ignoring(test,blub) group_left(blub) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo / ignoring(test,blub) group_left(bar) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo - on(test,blub) group_right(bar,foo) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo - ignoring(test,blub) group_right(bar,foo) bar',
expectedValueType: ValueType.vector,
expectedDiag: [] as Diagnostic[],
},
{
expr: 'foo and 1',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 9,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: '1 and foo',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 9,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: 'foo or 1',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 8,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: '1 or foo',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 8,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: 'foo unless 1',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 12,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: '1 unless foo',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 12,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: '1 or on(bar) foo',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 16,
message: 'vector matching only allowed between instant vectors',
severity: 'error',
},
{
from: 0,
to: 16,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
{
expr: 'foo == on(bar) 10',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 17,
message: 'vector matching only allowed between instant vectors',
severity: 'error',
},
],
},
{
expr: 'foo and on(bar) group_left(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 35,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 35,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'foo and on(bar) group_right(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 36,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 36,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'foo or on(bar) group_left(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 34,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 34,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'foo or on(bar) group_right(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 35,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 35,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'foo unless on(bar) group_left(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 38,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 38,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'foo unless on(bar) group_right(baz) bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 39,
message: 'no grouping allowed for set operations',
severity: 'error',
},
{
from: 0,
to: 39,
message: 'set operations must always be many-to-many',
severity: 'error',
},
],
},
{
expr: 'http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 95,
message: 'label "instance" must not occur in ON and GROUP clause at once',
severity: 'error',
},
],
},
{
expr: 'foo + bool bar',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 14,
message: 'bool modifier can only be used on comparison operators',
severity: 'error',
},
],
},
{
expr: 'foo + bool 10',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 13,
message: 'bool modifier can only be used on comparison operators',
severity: 'error',
},
],
},
{
expr: 'foo and bool 10',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 15,
message: 'bool modifier can only be used on comparison operators',
severity: 'error',
},
{
from: 0,
to: 15,
message: 'set operator not allowed in binary scalar expression',
severity: 'error',
},
],
},
// test aggregration
{
expr: 'sum by (foo)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'avg by (foo)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'max by (foo)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum without (foo) (some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum (some_metric) without (foo)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'stddev(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'stdvar by (foo)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum by ()(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum by (foo,bar,)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum by (foo,)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'topk(5, some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'topk( # my awesome comment\n' + '5, some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'count_values("value", some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum without(and, by, avg, count, alert, annotations)(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum some_metric by (test)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 25,
message: 'unable to find the parameter for the expression',
severity: 'error',
},
],
},
// Test function calls.
{
expr: 'time()',
expectedValueType: ValueType.scalar,
expectedDiag: [],
},
{
expr: 'floor(some_metric{foo!="bar"})',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'rate(some_metric[5m])',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'round(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'round(some_metric, 5)',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'floor()',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 7,
message: 'expected 1 argument(s) in call to "floor", got 0',
severity: 'error',
},
],
},
{
expr: 'floor(some_metric, other_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 32,
message: 'expected 1 argument(s) in call to "floor", got 2',
severity: 'error',
},
],
},
{
expr: 'floor(some_metric, 1)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 21,
message: 'expected 1 argument(s) in call to "floor", got 2',
severity: 'error',
},
],
},
{
expr: 'floor(1)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 6,
to: 7,
message: 'expected type vector in call to function "floor", got scalar',
severity: 'error',
},
],
},
{
expr: 'hour(some_metric, some_metric, some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 43,
message: 'expected at most 1 argument(s) in call to "hour", got 3',
severity: 'error',
},
],
},
{
expr: 'time(some_metric)',
expectedValueType: ValueType.scalar,
expectedDiag: [
{
from: 0,
to: 17,
message: 'expected 0 argument(s) in call to "time", got 1',
severity: 'error',
},
],
},
{
expr: 'rate(some_metric)',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 5,
to: 16,
message: 'expected type matrix in call to function "rate", got vector',
severity: 'error',
},
],
},
{
expr:
'histogram_quantile( # Root of the query, final result, approximates a quantile.\n' +
' 0.9, # 1st argument to histogram_quantile(), the target quantile.\n' +
' sum by(le, method, path) ( # 2nd argument to histogram_quantile(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds_bucket{job="demo"}[5m] # Argument to rate(), the raw histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_fraction( # Root of the query, final result, approximates a fraction of observations within an interval.\n' +
' -Inf, # 1st argument to histogram_fraction(), start of the interval.\n' +
' 123.4, # 2nd argument to histogram_fraction(), end of the interval.\n' +
' sum by(method, path) ( # 3rd argument to histogram_fraction(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_count( # Root of the query, final result, returns the count of observations.\n' +
' sum by(method, path) ( # Argument to histogram_count(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_sum( # Root of the query, final result, returns the sum of observations.\n' +
' sum by(method, path) ( # Argument to histogram_sum(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_avg( # Root of the query, final result, returns the average of observations.\n' +
' sum by(method, path) ( # Argument to histogram_avg(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_stddev( # Root of the query, final result, returns the standard deviation of observations.\n' +
' sum by(method, path) ( # Argument to histogram_stddev(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr:
'histogram_stdvar( # Root of the query, final result, returns the standard variance of observations.\n' +
' sum by(method, path) ( # Argument to histogram_stdvar(), an aggregated histogram.\n' +
' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' +
' )\n' +
' )\n' +
')',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '1 @ start()',
expectedValueType: ValueType.scalar,
expectedDiag: [
{
from: 0,
to: 11,
message: '@ modifier must be preceded by an instant selector vector or range vector selector or a subquery',
severity: 'error',
},
],
},
{
expr: 'foo @ 879',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'food @ start()',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'food @ end()',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'sum (rate(foo[5m])) @ 456',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"foo"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
// with metric name in the middle
expr: '{a="b","foo",c~="d"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"foo", a="bc"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"colon:in:the:middle"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"dot.in.the.middle"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"😀 in metric name"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
// quotes with escape
expr: '{"this is \"foo\" metric"}', // eslint-disable-line
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"foo","colon:in:the:middle"="val"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"foo","dot.in.the.middle"="val"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{"foo","😀 in label name"="val"}',
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
// quotes with escape
expr: '{"foo","this is \"bar\" label"="val"}', // eslint-disable-line
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: 'foo{"bar"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
message: 'metric name must not be set twice: foo or bar',
severity: 'error',
to: 10,
},
],
},
{
expr: '{"foo", __name__="bar"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
message: 'metric name must not be set twice: foo or bar',
severity: 'error',
to: 23,
},
],
},
{
expr: '{"foo", "__name__"="bar"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
message: 'metric name must not be set twice: foo or bar',
severity: 'error',
to: 25,
},
],
},
{
expr: '{"__name__"="foo", __name__="bar"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
message: 'metric name must not be set twice: foo or bar',
severity: 'error',
to: 34,
},
],
},
{
expr: '{"foo", "bar"}',
expectedValueType: ValueType.vector,
expectedDiag: [
{
from: 0,
to: 14,
message: 'metric name must not be set twice: foo or bar',
severity: 'error',
},
],
},
{
expr: `{'foo\`metric':'bar'}`, // eslint-disable-line
expectedValueType: ValueType.vector,
expectedDiag: [],
},
{
expr: '{`foo\"metric`=`bar`}', // eslint-disable-line
expectedValueType: ValueType.vector,
expectedDiag: [],
},
];
testCases.forEach((value) => {
const state = createEditorState(value.expr);
const parser = new Parser(state);
it(value.expr, () => {
expect(parser.checkAST(syntaxTree(state).topNode.firstChild)).toEqual(value.expectedValueType);
expect(parser.getDiagnostics()).toEqual(value.expectedDiag);
});
});
});