1// Copyright 2018 Prometheus Team
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package labels
15
16import (
17	"reflect"
18	"testing"
19)
20
21func TestMatchers(t *testing.T) {
22	for _, tc := range []struct {
23		input string
24		want  []*Matcher
25		err   string
26	}{
27		{
28			input: `{foo="bar"}`,
29			want: func() []*Matcher {
30				ms := []*Matcher{}
31				m, _ := NewMatcher(MatchEqual, "foo", "bar")
32				return append(ms, m)
33			}(),
34		},
35		{
36			input: `{foo=~"bar.*"}`,
37			want: func() []*Matcher {
38				ms := []*Matcher{}
39				m, _ := NewMatcher(MatchRegexp, "foo", "bar.*")
40				return append(ms, m)
41			}(),
42		},
43		{
44			input: `{foo!="bar"}`,
45			want: func() []*Matcher {
46				ms := []*Matcher{}
47				m, _ := NewMatcher(MatchNotEqual, "foo", "bar")
48				return append(ms, m)
49			}(),
50		},
51		{
52			input: `{foo!~"bar.*"}`,
53			want: func() []*Matcher {
54				ms := []*Matcher{}
55				m, _ := NewMatcher(MatchNotRegexp, "foo", "bar.*")
56				return append(ms, m)
57			}(),
58		},
59		{
60			input: `{foo="bar", baz!="quux"}`,
61			want: func() []*Matcher {
62				ms := []*Matcher{}
63				m, _ := NewMatcher(MatchEqual, "foo", "bar")
64				m2, _ := NewMatcher(MatchNotEqual, "baz", "quux")
65				return append(ms, m, m2)
66			}(),
67		},
68		{
69			input: `{foo="bar", baz!~"quux.*"}`,
70			want: func() []*Matcher {
71				ms := []*Matcher{}
72				m, _ := NewMatcher(MatchEqual, "foo", "bar")
73				m2, _ := NewMatcher(MatchNotRegexp, "baz", "quux.*")
74				return append(ms, m, m2)
75			}(),
76		},
77		{
78			input: `{foo="bar",baz!~".*quux", derp="wat"}`,
79			want: func() []*Matcher {
80				ms := []*Matcher{}
81				m, _ := NewMatcher(MatchEqual, "foo", "bar")
82				m2, _ := NewMatcher(MatchNotRegexp, "baz", ".*quux")
83				m3, _ := NewMatcher(MatchEqual, "derp", "wat")
84				return append(ms, m, m2, m3)
85			}(),
86		},
87		{
88			input: `{foo="bar", baz!="quux", derp="wat"}`,
89			want: func() []*Matcher {
90				ms := []*Matcher{}
91				m, _ := NewMatcher(MatchEqual, "foo", "bar")
92				m2, _ := NewMatcher(MatchNotEqual, "baz", "quux")
93				m3, _ := NewMatcher(MatchEqual, "derp", "wat")
94				return append(ms, m, m2, m3)
95			}(),
96		},
97		{
98			input: `{foo="bar", baz!~".*quux.*", derp="wat"}`,
99			want: func() []*Matcher {
100				ms := []*Matcher{}
101				m, _ := NewMatcher(MatchEqual, "foo", "bar")
102				m2, _ := NewMatcher(MatchNotRegexp, "baz", ".*quux.*")
103				m3, _ := NewMatcher(MatchEqual, "derp", "wat")
104				return append(ms, m, m2, m3)
105			}(),
106		},
107		{
108			input: `{foo="bar", instance=~"some-api.*"}`,
109			want: func() []*Matcher {
110				ms := []*Matcher{}
111				m, _ := NewMatcher(MatchEqual, "foo", "bar")
112				m2, _ := NewMatcher(MatchRegexp, "instance", "some-api.*")
113				return append(ms, m, m2)
114			}(),
115		},
116		{
117			input: `{foo=""}`,
118			want: func() []*Matcher {
119				ms := []*Matcher{}
120				m, _ := NewMatcher(MatchEqual, "foo", "")
121				return append(ms, m)
122			}(),
123		},
124		{
125			input: `{foo="bar,quux", job="job1"}`,
126			want: func() []*Matcher {
127				ms := []*Matcher{}
128				m, _ := NewMatcher(MatchEqual, "foo", "bar,quux")
129				m2, _ := NewMatcher(MatchEqual, "job", "job1")
130				return append(ms, m, m2)
131			}(),
132		},
133		{
134			input: `{foo = "bar", dings != "bums", }`,
135			want: func() []*Matcher {
136				ms := []*Matcher{}
137				m, _ := NewMatcher(MatchEqual, "foo", "bar")
138				m2, _ := NewMatcher(MatchNotEqual, "dings", "bums")
139				return append(ms, m, m2)
140			}(),
141		},
142		{
143			input: `foo=bar,dings!=bums`,
144			want: func() []*Matcher {
145				ms := []*Matcher{}
146				m, _ := NewMatcher(MatchEqual, "foo", "bar")
147				m2, _ := NewMatcher(MatchNotEqual, "dings", "bums")
148				return append(ms, m, m2)
149			}(),
150		},
151		{
152			input: `{quote="She said: \"Hi, ladies! That's gender-neutral…\""}`,
153			want: func() []*Matcher {
154				ms := []*Matcher{}
155				m, _ := NewMatcher(MatchEqual, "quote", `She said: "Hi, ladies! That's gender-neutral…"`)
156				return append(ms, m)
157			}(),
158		},
159		{
160			input: `statuscode=~"5.."`,
161			want: func() []*Matcher {
162				ms := []*Matcher{}
163				m, _ := NewMatcher(MatchRegexp, "statuscode", "5..")
164				return append(ms, m)
165			}(),
166		},
167		{
168			input: `tricky=~~~`,
169			want: func() []*Matcher {
170				ms := []*Matcher{}
171				m, _ := NewMatcher(MatchRegexp, "tricky", "~~")
172				return append(ms, m)
173			}(),
174		},
175		{
176			input: `trickier==\\=\=\"`,
177			want: func() []*Matcher {
178				ms := []*Matcher{}
179				m, _ := NewMatcher(MatchEqual, "trickier", `=\=\="`)
180				return append(ms, m)
181			}(),
182		},
183		{
184			input: `contains_quote != "\"" , contains_comma !~ "foo,bar" , `,
185			want: func() []*Matcher {
186				ms := []*Matcher{}
187				m, _ := NewMatcher(MatchNotEqual, "contains_quote", `"`)
188				m2, _ := NewMatcher(MatchNotRegexp, "contains_comma", "foo,bar")
189				return append(ms, m, m2)
190			}(),
191		},
192		{
193			input: `job="value`,
194			err:   `matcher value contains unescaped double quote: "value`,
195		},
196		{
197			input: `job=value"`,
198			err:   `matcher value contains unescaped double quote: value"`,
199		},
200		{
201			input: `trickier==\\=\=\""`,
202			err:   `matcher value contains unescaped double quote: =\\=\=\""`,
203		},
204		{
205			input: `contains_unescaped_quote = foo"bar`,
206			err:   `matcher value contains unescaped double quote: foo"bar`,
207		},
208		{
209			input: `{invalid-name = "valid label"}`,
210			err:   `bad matcher format: invalid-name = "valid label"`,
211		},
212		{
213			input: `{foo=~"invalid[regexp"}`,
214			err:   "error parsing regexp: missing closing ]: `[regexp)$`",
215		},
216		// Double escaped strings.
217		{
218			input: `"{foo=\"bar"}`,
219			err:   `bad matcher format: "{foo=\"bar"`,
220		},
221		{
222			input: `"foo=\"bar"`,
223			err:   `bad matcher format: "foo=\"bar"`,
224		},
225		{
226			input: `"foo=\"bar\""`,
227			err:   `bad matcher format: "foo=\"bar\""`,
228		},
229		{
230			input: `"foo=\"bar\"`,
231			err:   `bad matcher format: "foo=\"bar\"`,
232		},
233		{
234			input: `"{foo=\"bar\"}"`,
235			err:   `bad matcher format: "{foo=\"bar\"}"`,
236		},
237		{
238			input: `"foo="bar""`,
239			err:   `bad matcher format: "foo="bar""`,
240		},
241	} {
242		t.Run(tc.input, func(t *testing.T) {
243			got, err := ParseMatchers(tc.input)
244			if err != nil && tc.err == "" {
245				t.Fatalf("got error where none expected: %v", err)
246			}
247			if err == nil && tc.err != "" {
248				t.Fatalf("expected error but got none: %v", tc.err)
249			}
250			if err != nil && err.Error() != tc.err {
251				t.Fatalf("error not equal:\ngot  %v\nwant %v", err, tc.err)
252			}
253			if !reflect.DeepEqual(got, tc.want) {
254				t.Fatalf("labels not equal:\ngot  %v\nwant %v", got, tc.want)
255			}
256		})
257	}
258
259}
260