2023-09-05 10:32:58 +00:00
|
|
|
// Copyright 2023 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 parse
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestMatchers(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
input string
|
|
|
|
expected labels.Matchers
|
|
|
|
error string
|
|
|
|
}{{
|
|
|
|
name: "no braces",
|
|
|
|
input: "",
|
|
|
|
expected: nil,
|
|
|
|
}, {
|
|
|
|
name: "open and closing braces",
|
|
|
|
input: "{}",
|
|
|
|
expected: nil,
|
|
|
|
}, {
|
|
|
|
name: "equals",
|
|
|
|
input: "{foo=bar}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals with trailing comma",
|
|
|
|
input: "{foo=bar,}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "not equals",
|
|
|
|
input: "{foo!=bar}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "match regex",
|
|
|
|
input: "{foo=~[a-z]+}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex",
|
|
|
|
input: "{foo!~[a-z]+}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji",
|
|
|
|
input: "{foo=🙂}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂")},
|
|
|
|
}, {
|
|
|
|
name: "equals unicode sentence",
|
|
|
|
input: "{foo=🙂bar}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals without braces",
|
|
|
|
input: "foo=bar",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals without braces but with trailing comma",
|
|
|
|
input: "foo=bar,",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "not equals without braces",
|
|
|
|
input: "foo!=bar",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "match regex without braces",
|
|
|
|
input: "foo=~[a-z]+",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex without braces",
|
|
|
|
input: "foo!~[a-z]+",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "equals in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals in quotes and with trailing comma",
|
|
|
|
input: "{\"foo\"=\"bar\",}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "not equals in quotes",
|
|
|
|
input: "{\"foo\"!=\"bar\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "match regex in quotes",
|
|
|
|
input: "{\"foo\"=~\"[a-z]+\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex in quotes",
|
|
|
|
input: "{\"foo\"!~\"[a-z]+\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+")},
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji in quotes",
|
|
|
|
input: "{\"foo\"=\"🙂\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂")},
|
2023-11-13 16:31:15 +00:00
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji as bytes in quotes",
|
|
|
|
input: "{\"foo\"=\"\\xf0\\x9f\\x99\\x82\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂")},
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji as code points in quotes",
|
|
|
|
input: "{\"foo\"=\"\\U0001f642\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂")},
|
2023-09-05 10:32:58 +00:00
|
|
|
}, {
|
|
|
|
name: "equals unicode sentence in quotes",
|
|
|
|
input: "{\"foo\"=\"🙂bar\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "🙂bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals with newline in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\n\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar\n")},
|
|
|
|
}, {
|
|
|
|
name: "equals with tab in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\t\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar\t")},
|
|
|
|
}, {
|
|
|
|
name: "equals with escaped quotes in quotes",
|
|
|
|
input: "{\"foo\"=\"\\\"bar\\\"\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "\"bar\"")},
|
|
|
|
}, {
|
|
|
|
name: "equals with escaped backslash in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\\\\"}",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar\\")},
|
|
|
|
}, {
|
|
|
|
name: "equals without braces in quotes",
|
|
|
|
input: "\"foo\"=\"bar\"",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "equals without braces in quotes with trailing comma",
|
|
|
|
input: "\"foo\"=\"bar\",",
|
|
|
|
expected: labels.Matchers{mustNewMatcher(t, labels.MatchEqual, "foo", "bar")},
|
|
|
|
}, {
|
|
|
|
name: "complex",
|
|
|
|
input: "{foo=bar,bar!=baz}",
|
|
|
|
expected: labels.Matchers{
|
|
|
|
mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
mustNewMatcher(t, labels.MatchNotEqual, "bar", "baz"),
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
name: "complex in quotes",
|
|
|
|
input: "{foo=\"bar\",bar!=\"baz\"}",
|
|
|
|
expected: labels.Matchers{
|
|
|
|
mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
mustNewMatcher(t, labels.MatchNotEqual, "bar", "baz"),
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
name: "complex without braces",
|
|
|
|
input: "foo=bar,bar!=baz",
|
|
|
|
expected: labels.Matchers{
|
|
|
|
mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
mustNewMatcher(t, labels.MatchNotEqual, "bar", "baz"),
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
name: "complex without braces in quotes",
|
|
|
|
input: "foo=\"bar\",bar!=\"baz\"",
|
|
|
|
expected: labels.Matchers{
|
|
|
|
mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
mustNewMatcher(t, labels.MatchNotEqual, "bar", "baz"),
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
name: "comma",
|
|
|
|
input: ",",
|
|
|
|
error: "0:1: unexpected ,: expected label name",
|
|
|
|
}, {
|
|
|
|
name: "comma in braces",
|
|
|
|
input: "{,}",
|
|
|
|
error: "1:2: unexpected ,: expected label name",
|
|
|
|
}, {
|
|
|
|
name: "open brace",
|
|
|
|
input: "{",
|
|
|
|
error: "0:1: end of input: expected close brace",
|
|
|
|
}, {
|
|
|
|
name: "close brace",
|
|
|
|
input: "}",
|
|
|
|
error: "0:1: }: expected opening brace",
|
|
|
|
}, {
|
|
|
|
name: "no open brace",
|
|
|
|
input: "foo=bar}",
|
|
|
|
error: "0:8: }: expected opening brace",
|
|
|
|
}, {
|
|
|
|
name: "no close brace",
|
|
|
|
input: "{foo=bar",
|
|
|
|
error: "0:8: end of input: expected close brace",
|
|
|
|
}, {
|
|
|
|
name: "invalid input after operator and before quotes",
|
|
|
|
input: "{foo=:\"bar\"}",
|
|
|
|
error: "6:11: unexpected \"bar\": expected a comma or close brace",
|
|
|
|
}, {
|
|
|
|
name: "invalid escape sequence",
|
|
|
|
input: "{foo=\"bar\\w\"}",
|
|
|
|
error: "5:12: \"bar\\w\": invalid input",
|
2023-10-30 13:56:54 +00:00
|
|
|
}, {
|
|
|
|
name: "no unquoted escape sequences",
|
|
|
|
input: "{foo=bar\\n}",
|
|
|
|
error: "8:9: \\: invalid input: expected a comma or close brace",
|
2023-11-13 16:31:15 +00:00
|
|
|
}, {
|
|
|
|
name: "invalid unicode",
|
|
|
|
input: "{\"foo\"=\"\\xf0\\x9f\"}",
|
|
|
|
error: "7:17: \"\\xf0\\x9f\": invalid input",
|
2023-09-05 10:32:58 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
matchers, err := Matchers(test.input)
|
|
|
|
if test.error != "" {
|
|
|
|
require.EqualError(t, err, test.error)
|
|
|
|
} else {
|
2023-12-10 08:33:13 +00:00
|
|
|
require.NoError(t, err)
|
2023-09-05 10:32:58 +00:00
|
|
|
require.EqualValues(t, test.expected, matchers)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMatcher(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
input string
|
|
|
|
expected *labels.Matcher
|
|
|
|
error string
|
|
|
|
}{{
|
|
|
|
name: "equals",
|
|
|
|
input: "{foo=bar}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals with trailing comma",
|
|
|
|
input: "{foo=bar,}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "not equals",
|
|
|
|
input: "{foo!=bar}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "match regex",
|
|
|
|
input: "{foo=~[a-z]+}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex",
|
|
|
|
input: "{foo!~[a-z]+}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji",
|
|
|
|
input: "{foo=🙂}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂"),
|
2023-11-13 16:31:15 +00:00
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji as bytes in quotes",
|
|
|
|
input: "{\"foo\"=\"\\xf0\\x9f\\x99\\x82\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂"),
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji as code points in quotes",
|
|
|
|
input: "{\"foo\"=\"\\U0001f642\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂"),
|
2023-09-05 10:32:58 +00:00
|
|
|
}, {
|
|
|
|
name: "equals unicode sentence",
|
|
|
|
input: "{foo=🙂bar}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals without braces",
|
|
|
|
input: "foo=bar",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals without braces but with trailing comma",
|
|
|
|
input: "foo=bar,",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "not equals without braces",
|
|
|
|
input: "foo!=bar",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "match regex without braces",
|
|
|
|
input: "foo=~[a-z]+",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex without braces",
|
|
|
|
input: "foo!~[a-z]+",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "equals in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals in quotes and with trailing comma",
|
|
|
|
input: "{\"foo\"=\"bar\",}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "not equals in quotes",
|
|
|
|
input: "{\"foo\"!=\"bar\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "match regex in quotes",
|
|
|
|
input: "{\"foo\"=~\"[a-z]+\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "doesn't match regex in quotes",
|
|
|
|
input: "{\"foo\"!~\"[a-z]+\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchNotRegexp, "foo", "[a-z]+"),
|
|
|
|
}, {
|
|
|
|
name: "equals unicode emoji in quotes",
|
|
|
|
input: "{\"foo\"=\"🙂\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂"),
|
|
|
|
}, {
|
|
|
|
name: "equals unicode sentence in quotes",
|
|
|
|
input: "{\"foo\"=\"🙂bar\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "🙂bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals with newline in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\n\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar\n"),
|
|
|
|
}, {
|
|
|
|
name: "equals with tab in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\t\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar\t"),
|
|
|
|
}, {
|
|
|
|
name: "equals with escaped quotes in quotes",
|
|
|
|
input: "{\"foo\"=\"\\\"bar\\\"\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "\"bar\""),
|
|
|
|
}, {
|
|
|
|
name: "equals with escaped backslash in quotes",
|
|
|
|
input: "{\"foo\"=\"bar\\\\\"}",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar\\"),
|
|
|
|
}, {
|
|
|
|
name: "equals without braces in quotes",
|
|
|
|
input: "\"foo\"=\"bar\"",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "equals without braces in quotes with trailing comma",
|
|
|
|
input: "\"foo\"=\"bar\",",
|
|
|
|
expected: mustNewMatcher(t, labels.MatchEqual, "foo", "bar"),
|
|
|
|
}, {
|
|
|
|
name: "no input",
|
|
|
|
error: "no matchers",
|
|
|
|
}, {
|
|
|
|
name: "open and closing braces",
|
|
|
|
input: "{}",
|
|
|
|
error: "no matchers",
|
|
|
|
}, {
|
|
|
|
name: "two or more returns error",
|
|
|
|
input: "foo=bar,bar=baz",
|
|
|
|
error: "expected 1 matcher, found 2",
|
2023-11-13 16:31:15 +00:00
|
|
|
}, {
|
|
|
|
name: "invalid unicode",
|
|
|
|
input: "foo=\"\\xf0\\x9f\"",
|
|
|
|
error: "4:14: \"\\xf0\\x9f\": invalid input",
|
2023-09-05 10:32:58 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
matcher, err := Matcher(test.input)
|
|
|
|
if test.error != "" {
|
|
|
|
require.EqualError(t, err, test.error)
|
|
|
|
} else {
|
2023-12-10 08:33:13 +00:00
|
|
|
require.NoError(t, err)
|
2023-09-05 10:32:58 +00:00
|
|
|
require.EqualValues(t, test.expected, matcher)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustNewMatcher(t *testing.T, op labels.MatchType, name, value string) *labels.Matcher {
|
|
|
|
m, err := labels.NewMatcher(op, name, value)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return m
|
|
|
|
}
|