cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.74.6

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/utils/source_test.go

402lines · modecode

1package utils_test
2
3import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "strconv"
8 "strings"
9 "testing"
10
11 "github.com/gkampitakis/go-snaps/snaps"
12 "github.com/stretchr/testify/require"
13 "gopkg.in/yaml.v3"
14
15 "github.com/cloudflare/pint/internal/parser"
16 "github.com/cloudflare/pint/internal/parser/utils"
17
18 promParser "github.com/prometheus/prometheus/promql/parser"
19)
20
21func TestMain(t *testing.M) {
22 v := t.Run()
23 snaps.Clean(t, snaps.CleanOpts{Sort: true})
24 os.Exit(v)
25}
26
27func TestLabelsSource(t *testing.T) {
28 testCases := []string{
29 "1",
30 "1 / 5",
31 "(2 ^ 5) == bool 5",
32 "(2 ^ 5 + 11) % 5 <= bool 2",
33 "(2 ^ 5 + 11) % 5 >= bool 20",
34 "(2 ^ 5 + 11) % 5 <= bool 3",
35 "(2 ^ 5 + 11) % 5 < bool 1",
36 "20 - 15 < bool 1",
37 "2 * 5",
38 "(foo or bar) * 5",
39 "(foo or vector(2)) * 5",
40 "(foo or vector(5)) * (vector(2) or bar)",
41 `1 > bool 0`,
42 `20 > bool 10`,
43 `"test"`,
44 "foo",
45 "(foo > 1) > bool 1",
46 "foo > bool 5",
47 "foo > bool 5 == 1",
48 "foo > bool bar",
49 "(foo > bool bar) == 0",
50 "foo > bool on(instance) bar",
51 "(foo > bool on(instance) bar) == 1",
52 "foo > bool on(instance) group_left(version) bar",
53 "bar > bool on(instance) group_right(version) foo",
54 "foo and bar > bool 0",
55 "foo offset 5m",
56 `foo{job="bar"}`,
57 `foo{job=""}`,
58 `foo{job="bar"} or bar{job="foo"}`,
59 `foo{a="bar"} or bar{b="foo"}`,
60 "foo[5m]",
61 "prometheus_build_info[2m:1m]",
62 "deriv(rate(distance_covered_meters_total[1m])[5m:1m])",
63 "foo - 1",
64 "foo / 5",
65 "-foo",
66 `sum(foo{job="myjob"})`,
67 `sum(count(foo{job="myjob"}) by(instance))`,
68 `sum(foo{job="myjob"}) > 20`,
69 `sum(foo{job="myjob"}) without(job)`,
70 `sum(foo) by(job)`,
71 `sum(foo{job="myjob"}) by(job)`,
72 `abs(foo{job="myjob"} offset 5m)`,
73 `abs(foo{job="myjob"} or bar{cluster="dev"})`,
74 `sum(foo{job="myjob"} or bar{cluster="dev"}) without(instance)`,
75 `sum(foo{job="myjob"}) without(instance)`,
76 `min(foo{job="myjob"}) / max(foo{job="myjob"})`,
77 `max(foo{job="myjob"}) / min(foo{job="myjob"})`,
78 `avg(foo{job="myjob"}) by(job)`,
79 `group(foo) by(job)`,
80 `stddev(rate(foo[5m]))`,
81 `stdvar(rate(foo[5m]))`,
82 `stddev_over_time(foo[5m])`,
83 `stdvar_over_time(foo[5m])`,
84 `quantile(0.9, rate(foo[5m]))`,
85 `count_values("version", build_version)`,
86 `count_values("version", build_version) without(job)`,
87 `count_values("version", build_version{job="foo"}) without(job)`,
88 `count_values("version", build_version) by(job)`,
89 `topk(10, foo{job="myjob"}) > 10`,
90 `topk(10, foo or bar)`,
91 `rate(foo[10m])`,
92 `sum(rate(foo[10m])) without(instance)`,
93 `foo{job="foo"} / bar`,
94 `foo{job="foo"} * on(instance) bar`,
95 `foo{job="foo"} * on(instance) group_left(bar) bar`,
96 `foo{job="foo"} * on(instance) group_left(cluster) bar{cluster="bar", ignored="true"}`,
97 `foo{job="foo", ignored="true"} * on(instance) group_right(job) bar{cluster="bar"}`,
98 `count(foo / bar)`,
99 `count(up{job="a"} / on () up{job="b"})`,
100 `count(up{job="a"} / on (env) up{job="b"})`,
101 `foo{job="foo", instance="1"} and bar`,
102 `foo{job="foo", instance="1"} and on(cluster) bar`,
103 `topk(10, foo)`,
104 `topk(10, foo) without(cluster)`,
105 `topk(10, foo) by(cluster)`,
106 `bottomk(10, sum(rate(foo[5m])) without(job))`,
107 `foo or bar`,
108 `foo or bar or baz`,
109 `(foo or bar) or baz`,
110 `foo unless bar`,
111 `foo unless bar > 5`,
112 `foo unless bar unless baz`,
113 `count(sum(up{job="foo", cluster="dev"}) by(job, cluster) == 0) without(job, cluster)`,
114 "year()",
115 "year(foo)",
116 `label_join(up{job="api-server",src1="a",src2="b",src3="c"}, "foo", ",", "src1", "src2", "src3")`,
117 `
118(
119 sum(foo:sum > 0) without(notify)
120 * on(job) group_left(notify)
121 job:notify
122)
123and on(job)
124sum(foo:count) by(job) > 20`,
125 `container_file_descriptors / on (instance, app_name) container_ulimits_soft{ulimit="max_open_files"}`,
126 `container_file_descriptors / on (instance, app_name) group_left() container_ulimits_soft{ulimit="max_open_files"}`,
127 `absent(foo{job="bar"})`,
128 `absent(foo{job="bar", cluster!="dev", instance=~".+", env="prod"})`,
129 `absent(sum(foo) by(job, instance))`,
130 `absent(foo{job="prometheus", xxx="1"}) AND on(job) prometheus_build_info`,
131 `1 + sum(foo) by(notjob)`,
132 `count(node_exporter_build_info) by (instance, version) != ignoring(package,version) group_left(foo) count(deb_package_version) by (instance, version, package)`,
133 `absent(foo) or absent(bar)`,
134 `absent_over_time(foo[5m]) or absent(bar)`,
135 `bar * on() group_right(cluster, env) absent(foo{job="xxx"})`,
136 `bar * on() group_right() absent(foo{job="xxx"})`,
137 "vector(1)",
138 "vector(scalar(foo))",
139 "vector(0.0 >= bool 0.5) == 1",
140 `sum_over_time(foo{job="myjob"}[5m])`,
141 `days_in_month()`,
142 `days_in_month(foo{job="foo"})`,
143 `label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")`,
144 `label_replace(sum by (pod) (pod_status) > 0, "cluster", "$1", "pod", "(.*)")`,
145 `(time() - my_metric) > 5*3600`,
146 `up{instance="a", job="prometheus"} * ignoring(job) up{instance="a", job="pint"}`,
147 `
148avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case!~".*offpeak.*"})
149< 0.5 > 0
150or sum without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*tier1.*"})
151< on() count(colo_router_tier:disabled_pops:max{tier="1",router=~"edge.*"}) * 0.4 > 0
152or avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*regional.*"})
153< 0.1 > 0
154`,
155 `label_replace(sum(foo) without(instance), "instance", "none", "", "")`,
156 `
157sum by (region, target, colo_name) (
158 sum_over_time(probe_success{job="abc"}[5m])
159 or
160 vector(1)
161) == 0`,
162 `vector(1) or foo`,
163 `vector(0) > 0`,
164 `vector(0) > vector(1)`,
165 `sum(foo or vector(0)) > 0`,
166 `(sum(foo or vector(1)) > 0) == 2`,
167 `(sum(foo or vector(1)) > 0) != 2`,
168 `(sum(foo or vector(2)) > 0) != 2`,
169 `(sum(sometimes{foo!="bar"} or vector(0)))
170or
171((bob > 10) or sum(foo) or vector(1))`,
172 `
173(
174 sum(sometimes{foo!="bar"})
175 or
176 vector(1)
177) and (
178 ((bob > 10) or sum(bar))
179 or
180 notfound > 0
181)`,
182 "foo offset 5m > 5",
183 `
184(rate(metric2[5m]) or vector(0)) +
185(rate(metric1[5m]) or vector(1)) +
186(rate(metric3{log_name="samplerd"}[5m]) or vector(2)) > 0
187`,
188 `label_replace(vector(1), "nexthop_tag", "$1", "nexthop", "(.+)")`,
189 `(sum(foo{job="myjob"}))`,
190 `(-foo{job="myjob"})`,
191 "\n((( group(vector(0)) ))) > 0",
192 "1 > bool 5",
193 `prometheus_ready{job="prometheus"} unless vector(0)`,
194 `prometheus_ready{job="prometheus"} unless on() vector(0)`,
195 `prometheus_ready{job="prometheus"} unless on(job) vector(0)`,
196 `
197max by (instance, cluster) (cf_node_role{kubernetes_role="master",role="kubernetes"})
198unless
199 sum by (instance, cluster) (time() - node_systemd_timer_last_trigger_seconds{name=~"etcd-defrag-.*.timer"})
200 * on (instance) group_left (cluster)
201 cf_node_role{kubernetes_role="master",role="kubernetes"}
202`,
203 `foo{a="1"} * on() bar{b="2"}`,
204 `foo{a="1"} * on(instance) group_left(c,d) bar{b="2"}`,
205 `foo{a="1"} * on(instance) group_right(c,d) bar{b="2"}`,
206 `foo{a="1"} * on(instance) sum(bar{b="2"})`,
207 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"})`,
208 `sum(foo{a="1"}) * on(instance) group_right(c,d) bar{b="2"}`,
209 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"}) without(instance)`,
210 `sum(foo{a="1"}) without(instance) * on(instance) group_right(c,d) bar{b="2"}`,
211 `
212 max without (source_instance) (
213 increase(kernel_device_io_errors_total{device!~"loop.+"}[120m]) > 3 unless on(instance, device) (
214 increase(kernel_device_io_soft_errors_total{device!~"loop.+"}[125m])*2 > increase(kernel_device_io_errors_total[120m])
215 )
216 and on(device, instance) absent(node_disk_info)
217 ) * on(instance) group_left(group) label_replace(salt_highstate_runner_configured_minions, "instance", "$1", "minion", "(.+)")
218`,
219 `sum(foo{a="1"}) by(job) * on() bar{b="2"}`,
220 `sum(sum(foo) without(job)) by(job)`,
221 `
222prometheus:scrape_series_added:since_gc:sum
223* on(prometheus) group_left()
224label_replace(
225 max(max_over_time(go_memstats_alloc_bytes{job="prometheus"}[2h])) by(instance)
226 /
227 max(max_over_time(prometheus_tsdb_head_series[2h])) by(instance),
228 "prometheus", "$1",
229 "instance", "(.+)"
230)
231`,
232 `(day_of_week() == 6 and hour() < 1) or vector(1)`,
233 `
234sum by (foo, bar) (
235 rate(errors_total[5m])
236 * on (instance) group_left (bob, alice)
237 server_errors_total
238)`,
239 `1 - (foo or vector(0)) < 0.999`,
240 `
241(
242 vector(1) and month() == 2
243) or vector(0)
244`,
245 `count by (region) (stddev by (colo_name, region) (error_total))`,
246 `
247(
248 avg(
249 rate(foo_rejections[6h])
250 or
251 vector(0)
252 ) by (colo_name)
253 /
254 (
255 avg(
256 rate(foo_total[6h])
257 or
258 vector(1)
259 ) by (colo_name)
260 )
261) > 5
262*
263(
264 avg(
265 rate(foo_rejections[6h] offset 1d)
266 or
267 vector(0)
268 ) by (colo_name)
269 /
270 avg(
271 rate(foo_total[6h] offset 1d)
272 or
273 vector(1)
274 ) by (colo_name)
275) and on (colo_name) (colo_job:foo_total:rate2m or vector(0)) > 80
276 and on (colo_name) (colo_job:foo_total:rate2m offset 1d or vector(0)) > 80
277`,
278 `sum(selector) / sum(selector offset 30m) > 5`,
279 `
280count by (dc) (
281 max(0 < (token_expiration - time()) < (6*60*60)) by (instance)
282 * on (instance) group_right label_replace(
283 configured_minions, "instance", "$1", "minion", "(.+)")
284 ) > 5`,
285 `topk(10, prometheus_build_info*prometheus_ready)`,
286 `bottomk(10, prometheus_build_info*prometheus_ready)`,
287 }
288
289 type Snapshot struct {
290 Expr string
291 Output []utils.Source
292 }
293
294 _, file, _, ok := runtime.Caller(0)
295 require.True(t, ok, "can't get caller function")
296 file = strings.TrimSuffix(filepath.Base(file), ".go")
297
298 done := map[string]struct{}{}
299 for i, expr := range testCases {
300 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
301 if _, ok := done[expr]; ok {
302 t.Fatalf("Duplicated query: %s", expr)
303 }
304 done[expr] = struct{}{}
305
306 n, err := parser.DecodeExpr(expr)
307 if err != nil {
308 t.Error(err)
309 t.FailNow()
310 }
311 output := utils.LabelsSource(expr, n.Expr)
312
313 for _, src := range output {
314 src.WalkSources(func(s utils.Source, _ *utils.Join, _ *utils.Unless) {
315 require.Positive(t, s.Position.End, "empty position %+v", s)
316 if s.DeadInfo != nil {
317 require.Positive(t, s.DeadInfo.Fragment.End, "empty dead position %+v", s)
318 }
319 })
320 }
321
322 snap := Snapshot{
323 Expr: expr,
324 Output: output,
325 }
326 d, err := yaml.Marshal(snap)
327 require.NoError(t, err, "failed to YAML encode snapshots")
328 snaps.WithConfig(snaps.Dir("."), snaps.Filename(file)).MatchSnapshot(t, string(d))
329 })
330 }
331}
332
333func TestLabelsSourceCallCoverage(t *testing.T) {
334 for name, def := range promParser.Functions {
335 t.Run(name, func(t *testing.T) {
336 if def.Experimental {
337 t.SkipNow()
338 }
339
340 var b strings.Builder
341 b.WriteString(name)
342 b.WriteRune('(')
343 for i, at := range def.ArgTypes {
344 if i > 0 {
345 b.WriteString(", ")
346 }
347 switch at {
348 case promParser.ValueTypeNone:
349 case promParser.ValueTypeScalar:
350 b.WriteRune('1')
351 case promParser.ValueTypeVector:
352 b.WriteString("http_requests_total")
353 case promParser.ValueTypeMatrix:
354 b.WriteString("http_requests_total[2m]")
355 case promParser.ValueTypeString:
356 b.WriteString(`"foo"`)
357 }
358 }
359 b.WriteRune(')')
360
361 n, err := parser.DecodeExpr(b.String())
362 if err != nil {
363 t.Error(err)
364 t.FailNow()
365 }
366 output := utils.LabelsSource(b.String(), n.Expr)
367 require.Len(t, output, 1)
368 require.NotEmpty(t, output[0].Operations)
369 call, ok := utils.MostOuterOperation[*promParser.Call](output[0])
370 require.True(t, ok, "no call found in operations for: %q ~> %+v", b.String(), output)
371 require.NotNil(t, call, "no call detected in: %q ~> %+v", b.String(), output)
372 require.Equal(t, name, output[0].Operation())
373 require.Equal(t, def.ReturnType, output[0].Returns, "incorrect return type on Source{}")
374 })
375 }
376}
377
378func TestLabelsSourceCallCoverageFail(t *testing.T) {
379 n := &parser.PromQLNode{
380 Expr: &promParser.Call{
381 Func: &promParser.Function{
382 Name: "fake_call",
383 },
384 },
385 }
386 output := utils.LabelsSource("fake_call()", n.Expr)
387 require.Len(t, output, 1)
388 call, ok := utils.MostOuterOperation[*promParser.Call](output[0])
389 require.False(t, ok, "no call should have been detected in fake function, got: %v", ok)
390 require.Nil(t, call, "no call should have been detected in fake function, got: %+v", call)
391}
392
393func TestVectorOperation(t *testing.T) {
394 n := &parser.PromQLNode{
395 Expr: &promParser.NumberLiteral{
396 Val: 1,
397 },
398 }
399 output := utils.LabelsSource("1", n.Expr)
400 require.Len(t, output, 1)
401 require.Empty(t, output[0].Operation())
402}
403