cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/source/source_test.go

845lines · modecode

1package source_test
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strconv"
9 "strings"
10 "testing"
11
12 "github.com/gkampitakis/go-snaps/snaps"
13 "github.com/stretchr/testify/require"
14 "go.yaml.in/yaml/v3"
15
16 "github.com/cloudflare/pint/internal/parser"
17 "github.com/cloudflare/pint/internal/parser/source"
18
19 promParser "github.com/prometheus/prometheus/promql/parser"
20 "github.com/prometheus/prometheus/promql/parser/posrange"
21)
22
23func TestMain(t *testing.M) {
24 v := t.Run()
25 if _, err := snaps.Clean(t, snaps.CleanOpts{Sort: true}); err != nil {
26 fmt.Printf("snaps.Clean() returned an error: %s", err)
27 os.Exit(100)
28 }
29 os.Exit(v)
30}
31
32var testCases = []string{
33 "1",
34 "1 / 5",
35 "(2 ^ 5) == bool 5",
36 "(2 ^ 5 + 11) % 5 <= bool 2",
37 "(2 ^ 5 + 11) % 5 >= bool 20",
38 "(2 ^ 5 + 11) % 5 <= bool 3",
39 "(2 ^ 5 + 11) % 5 < bool 1",
40 "20 - 15 < bool 1",
41 "2 * 5",
42 "(foo or bar) * 5",
43 "(foo or vector(2)) * 5",
44 "(foo or vector(5)) * (vector(2) or bar)",
45 `1 > bool 0`,
46 `20 > bool 10`,
47 `"test"`,
48 "foo",
49 "(foo > 1) > bool 1",
50 "foo > bool 5",
51 "foo > bool 5 == 1",
52 "foo > bool bar",
53 "(foo > bool bar) == 0",
54 "foo > bool on(instance) bar",
55 "(foo > bool on(instance) bar) == 1",
56 "foo > bool on(instance) group_left(version) bar",
57 "bar > bool on(instance) group_right(version) foo",
58 "foo and bar > bool 0",
59 "foo offset 5m",
60 `foo{job="bar"}`,
61 `foo{job=""}`,
62 `foo{job="bar"} or bar{job="foo"}`,
63 `foo{a="bar"} or bar{b="foo"}`,
64 "foo[5m]",
65 "prometheus_build_info[2m:1m]",
66 "deriv(rate(distance_covered_meters_total[1m])[5m:1m])",
67 "foo - 1",
68 "foo / 5",
69 "-foo",
70 `sum(foo{job="myjob"})`,
71 `sum(count(foo{job="myjob"}) by(instance))`,
72 `sum(foo{job="myjob"}) > 20`,
73 `sum(foo{job="myjob"}) without(job)`,
74 `sum(foo) by(job)`,
75 `sum(foo{job="myjob"}) by(job)`,
76 `abs(foo{job="myjob"} offset 5m)`,
77 `abs(foo{job="myjob"} or bar{cluster="dev"})`,
78 `sum(foo{job="myjob"} or bar{cluster="dev"}) without(instance)`,
79 `sum(foo{job="myjob"}) without(instance)`,
80 `min(foo{job="myjob"}) / max(foo{job="myjob"})`,
81 `max(foo{job="myjob"}) / min(foo{job="myjob"})`,
82 `avg(foo{job="myjob"}) by(job)`,
83 `group(foo) by(job)`,
84 `stddev(rate(foo[5m]))`,
85 `stdvar(rate(foo[5m]))`,
86 `stddev_over_time(foo[5m])`,
87 `stdvar_over_time(foo[5m])`,
88 `quantile(0.9, rate(foo[5m]))`,
89 `count_values("version", build_version)`,
90 `count_values("version", build_version) without(job)`,
91 `count_values("version", build_version{job="foo"}) without(job)`,
92 `count_values("version", build_version) by(job)`,
93 `topk(10, foo{job="myjob"}) > 10`,
94 `topk(10, foo or bar)`,
95 `rate(foo[10m])`,
96 `sum(rate(foo[10m])) without(instance)`,
97 `foo{job="foo"} / bar`,
98 `foo{job="foo"} * on(instance) bar`,
99 `foo{job="foo"} * on(instance) group_left(bar) bar`,
100 `foo{job="foo"} * on(instance) group_left(cluster) bar{cluster="bar", ignored="true"}`,
101 `foo{job="foo", ignored="true"} * on(instance) group_right(job) bar{cluster="bar"}`,
102 `count(foo / bar)`,
103 `count(up{job="a"} / on () up{job="b"})`,
104 `count(up{job="a"} / on (env) up{job="b"})`,
105 `foo{job="foo", instance="1"} and bar`,
106 `foo{job="foo", instance="1"} and on(cluster) bar`,
107 `topk(10, foo)`,
108 `topk(10, foo) without(cluster)`,
109 `topk(10, foo) by(cluster)`,
110 `bottomk(10, sum(rate(foo[5m])) without(job))`,
111 `foo or bar`,
112 `foo or bar or baz`,
113 `(foo or bar) or baz`,
114 `foo unless bar`,
115 `foo unless bar > 5`,
116 `foo unless bar unless baz`,
117 `count(sum(up{job="foo", cluster="dev"}) by(job, cluster) == 0) without(job, cluster)`,
118 "year()",
119 "year(foo)",
120 `label_join(up{job="api-server",src1="a",src2="b",src3="c"}, "foo", ",", "src1", "src2", "src3")`,
121 `
122(
123 sum(foo:sum > 0) without(notify)
124 * on(job) group_left(notify)
125 job:notify
126)
127and on(job)
128sum(foo:count) by(job) > 20`,
129 `container_file_descriptors / on (instance, app_name) container_ulimits_soft{ulimit="max_open_files"}`,
130 `container_file_descriptors / on (instance, app_name) group_left() container_ulimits_soft{ulimit="max_open_files"}`,
131 `absent(foo{job="bar"})`,
132 `absent(foo{job="bar", cluster!="dev", instance=~".+", env="prod"})`,
133 `absent(sum(foo) by(job, instance))`,
134 `absent(foo{job="prometheus", xxx="1"}) AND on(job) prometheus_build_info`,
135 `1 + sum(foo) by(notjob)`,
136 `count(node_exporter_build_info) by (instance, version) != ignoring(package,version) group_left(foo) count(deb_package_version) by (instance, version, package)`,
137 `absent(foo) or absent(bar)`,
138 `absent(vector(1))`,
139 `absent_over_time(foo[5m]) or absent(bar)`,
140 `bar * on() group_right(cluster, env) absent(foo{job="xxx"})`,
141 `bar * on() group_right() absent(foo{job="xxx"})`,
142 "vector(1)",
143 "vector(scalar(foo))",
144 "vector(0.0 >= bool 0.5) == 1",
145 `sum_over_time(foo{job="myjob"}[5m])`,
146 `days_in_month()`,
147 `days_in_month(foo{job="foo"})`,
148 `label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")`,
149 `label_replace(sum by (pod) (pod_status) > 0, "cluster", "$1", "pod", "(.*)")`,
150 `(time() - my_metric) > 5*3600`,
151 `up{instance="a", job="prometheus"} * ignoring(job) up{instance="a", job="pint"}`,
152 `
153avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case!~".*offpeak.*"})
154< 0.5 > 0
155or sum without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*tier1.*"})
156< on() count(colo_router_tier:disabled_pops:max{tier="1",router=~"edge.*"}) * 0.4 > 0
157or avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*regional.*"})
158< 0.1 > 0
159`,
160 `label_replace(sum(foo) without(instance), "instance", "none", "", "")`,
161 `
162sum by (region, target, colo_name) (
163 sum_over_time(probe_success{job="abc"}[5m])
164 or
165 vector(1)
166) == 0`,
167 `vector(1) or foo`,
168 `vector(0) > 0`,
169 `vector(0) > vector(1)`,
170 `sum(foo or vector(0)) > 0`,
171 `(sum(foo or vector(1)) > 0) == 2`,
172 `(sum(foo or vector(1)) > 0) != 2`,
173 `(sum(foo or vector(2)) > 0) != 2`,
174 `(sum(sometimes{foo!="bar"} or vector(0)))
175or
176((bob > 10) or sum(foo) or vector(1))`,
177 `
178(
179 sum(sometimes{foo!="bar"})
180 or
181 vector(1)
182) and (
183 ((bob > 10) or sum(bar))
184 or
185 notfound > 0
186)`,
187 "foo offset 5m > 5",
188 `
189(rate(metric2[5m]) or vector(0)) +
190(rate(metric1[5m]) or vector(1)) +
191(rate(metric3{log_name="samplerd"}[5m]) or vector(2)) > 0
192`,
193 `label_replace(vector(1), "nexthop_tag", "$1", "nexthop", "(.+)")`,
194 `(sum(foo{job="myjob"}))`,
195 `(-foo{job="myjob"})`,
196 "\n((( group(vector(0)) ))) > 0",
197 "1 > bool 5",
198 `prometheus_ready{job="prometheus"} unless vector(0)`,
199 `prometheus_ready{job="prometheus"} unless on() vector(0)`,
200 `prometheus_ready{job="prometheus"} unless on(job) vector(0)`,
201 `
202max by (instance, cluster) (cf_node_role{kubernetes_role="master",role="kubernetes"})
203unless
204 sum by (instance, cluster) (time() - node_systemd_timer_last_trigger_seconds{name=~"etcd-defrag-.*.timer"})
205 * on (instance) group_left (cluster)
206 cf_node_role{kubernetes_role="master",role="kubernetes"}
207`,
208 `foo{a="1"} * on() bar{b="2"}`,
209 `foo{a="1"} * on(instance) group_left(c,d) bar{b="2"}`,
210 `foo{a="1"} * on(instance) group_right(c,d) bar{b="2"}`,
211 `foo{a="1"} * on(instance) sum(bar{b="2"})`,
212 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"})`,
213 `sum(foo{a="1"}) * on(instance) group_right(c,d) bar{b="2"}`,
214 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"}) without(instance)`,
215 `sum(foo{a="1"}) without(instance) * on(instance) group_right(c,d) bar{b="2"}`,
216 `
217 max without (source_instance) (
218 increase(kernel_device_io_errors_total{device!~"loop.+"}[120m]) > 3 unless on(instance, device) (
219 increase(kernel_device_io_soft_errors_total{device!~"loop.+"}[125m])*2 > increase(kernel_device_io_errors_total[120m])
220 )
221 and on(device, instance) absent(node_disk_info)
222 ) * on(instance) group_left(group) label_replace(salt_highstate_runner_configured_minions, "instance", "$1", "minion", "(.+)")
223`,
224 `sum(foo{a="1"}) by(job) * on() bar{b="2"}`,
225 `sum(sum(foo) without(job)) by(job)`,
226 `
227prometheus:scrape_series_added:since_gc:sum
228* on(prometheus) group_left()
229label_replace(
230 max(max_over_time(go_memstats_alloc_bytes{job="prometheus"}[2h])) by(instance)
231 /
232 max(max_over_time(prometheus_tsdb_head_series[2h])) by(instance),
233 "prometheus", "$1",
234 "instance", "(.+)"
235)
236`,
237 `(day_of_week() == 6 and hour() < 1) or vector(1)`,
238 `
239sum by (foo, bar) (
240 rate(errors_total[5m])
241 * on (instance) group_left (bob, alice)
242 server_errors_total
243)`,
244 `1 - (foo or vector(0)) < 0.999`,
245 `
246(
247 vector(1) and month() == 2
248) or vector(0)
249`,
250 `count by (region) (stddev by (colo_name, region) (error_total))`,
251 `
252(
253 avg(
254 rate(foo_rejections[6h])
255 or
256 vector(0)
257 ) by (colo_name)
258 /
259 (
260 avg(
261 rate(foo_total[6h])
262 or
263 vector(1)
264 ) by (colo_name)
265 )
266) > 5
267*
268(
269 avg(
270 rate(foo_rejections[6h] offset 1d)
271 or
272 vector(0)
273 ) by (colo_name)
274 /
275 avg(
276 rate(foo_total[6h] offset 1d)
277 or
278 vector(1)
279 ) by (colo_name)
280) and on (colo_name) (colo_job:foo_total:rate2m or vector(0)) > 80
281 and on (colo_name) (colo_job:foo_total:rate2m offset 1d or vector(0)) > 80
282`,
283 `sum(selector) / sum(selector offset 30m) > 5`,
284 `
285count by (dc) (
286 max(0 < (token_expiration - time()) < (6*60*60)) by (instance)
287 * on (instance) group_right label_replace(
288 configured_minions, "instance", "$1", "minion", "(.+)")
289 ) > 5`,
290 `topk(10, prometheus_build_info*prometheus_ready)`,
291 `bottomk(10, prometheus_build_info*prometheus_ready)`,
292 `histogram_fraction(0, 0.1, metric)`,
293 `foo * foo `,
294 `foo + on(__name__, job) foo `,
295 `foo + on(__name__, job) group_left foo `,
296 `foo + on(__name__, job) group_right foo `,
297 `group by (env, cluster) (
298 up{env="prod", job="foo"} and on (instance) (services_enabled == 999)
299 )`,
300 `group by (env, cluster) (
301 up{env="prod", job="foo"} * on (instance) (services_enabled == 999)
302 )`,
303 `foo / on(instance) sum(bar)`,
304 `foo / on(instance) group_left(cluster) sum(bar)`,
305 `sum(bar) / on(instance) group_right(cluster) foo`,
306 `sum(bar) * on(cluster) sum(foo)`,
307 `
308group by (colo_name, instance, tier, animal, brand, sliver, pop_name) (
309 up{node_status="v", job="node_exporter"}
310 and on (instance) (metal_services_enabled == 999)
311 * on (colo_name) group_left(tier, animal, brand, pop_name) colo_metadata{colo_status="v"}
312 * on (instance) group_left (sliver) sliver_metadata{node_status="v"}
313)`,
314 `
315up{node_status="v", job="node_exporter"}
316* on (colo_name) group_left(tier) colo_metadata{colo_status="v"}
317* on (instance) group_left (sliver) sliver_metadata{node_status="v"}
318`, // all group_left() labels are joined to the left
319 `
320up{node_status="v", job="node_exporter"}
321and on (colo_name) colo_metadata{colo_status="v"}
322* on (instance) group_left (sliver) sliver_metadata{node_status="v"}
323`, // all group_left() labels are NOT joined to the left
324 `
325colo_metadata{colo_status="v"} * on (colo_name) group_right(tier, animal, brand, pop_name)
326sliver_metadata{node_status="v"} * on (instance) group_right (sliver)
327(metal_services_enabled == 999) * on (instance)
328up{node_status="v", job="node_exporter"}
329`, // only instance label will be present
330 `
331colo_metadata{colo_status="v"} * on (colo_name) group_right(tier, animal, brand, pop_name)
332sliver_metadata{node_status="v"} * on (instance) group_right (sliver)
333(metal_services_enabled == 999) * on (instance) group_right()
334up{node_status="v", job="node_exporter"}
335`, // no labels are joined to the right
336 `
337sliver_metadata{node_status="v"} * on (instance) group_right (sliver)
338(metal_services_enabled == 999) * on (colo_name) group_left(tier, animal, brand, pop_name)
339colo_metadata{colo_status="v"}
340`, // labels from both group_left and group_right are joined
341 `
342colo_metadata * on (colo_name) group_right(tier, animal, brand, pop_name)
343sliver_metadata * on (instance) group_right (sliver)
344metal_services_enabled
345`, // only sliver and tier are joined to the right
346 `
347colo_metadata * on (colo_name) group_right(tier, animal, brand, pop_name)
348(
349 sliver_metadata * on (instance) group_right (sliver)
350 metal_services_enabled
351)
352`, // all labels are joined to the right
353 `
354up{node_status="v", job="node_exporter"}
355* on(instance) group_left(node_status) sliver_metadata
356`, // group_left on a label already guaranteed on the left
357 `services_enabled{job=""}`,
358 `
359group by (cluster, namespace, workload, workload_type, pod) (
360 label_join(
361 label_join(
362 group by (cluster, namespace, job_name, pod) (
363 label_join(
364 kube_pod_owner{job="kube-state-metrics", owner_kind="Job"}
365 , "job_name", "", "owner_name")
366 )
367 * on (cluster, namespace, job_name) group_left(owner_kind, owner_name)
368 group by (cluster, namespace, job_name, owner_kind, owner_name) (
369 kube_job_owner{job="kube-state-metrics", owner_kind!="Pod", owner_kind!=""}
370 )
371 , "workload", "", "owner_name")
372 , "workload_type", "", "owner_kind")
373)
374`,
375 `foo{job="xxx"} + on(job) group_right(instance) bar{}`,
376 `foo{job="xxx"} + ignoring(job) group_right(instance) bar{job="zzz"}`,
377 `foo or ignoring(job) bar`,
378 `foo or on(job) bar`,
379 `
380(
381 sum(rate(panics_total{module_name=~".+"}[5m])) by (colo_name, module_name)
382 /
383 ignoring(module_name) group_left
384 sum(colo:requests:rate5m) by (colo_name)
385) > 0.01
386`,
387 `foo atan2 bar`,
388 `foo offset -5m`,
389 `foo @ 1609459200`,
390 `foo @ start()`,
391 `foo @ end()`,
392 `foo[5m] @ 1609459200`,
393 `rate(foo[5m] @ start())`,
394 `histogram_quantile(0.9, rate(foo[5m]))`,
395 `predict_linear(foo[5m], 3600)`,
396 `clamp(foo, 0, 100)`,
397 `irate(foo[5m])`,
398 `delta(foo[5m])`,
399 `idelta(foo[5m])`,
400 `increase(foo[5m])`,
401 `changes(foo[5m])`,
402 `resets(foo[5m])`,
403 `timestamp(foo)`,
404 `sort(foo)`,
405 `sort_desc(foo)`,
406 `pi()`,
407 `sgn(foo)`,
408 `clamp_min(foo, 0)`,
409 `clamp_max(foo, 100)`,
410 `round(foo, 0.1)`,
411 `exp(foo)`,
412 `ln(foo)`,
413 `log2(foo)`,
414 `log10(foo)`,
415 `sqrt(foo)`,
416 `ceil(foo)`,
417 `floor(foo)`,
418 `avg_over_time(foo[5m])`,
419 `min_over_time(foo[5m])`,
420 `max_over_time(foo[5m])`,
421 `count_over_time(foo[5m])`,
422 `last_over_time(foo[5m])`,
423 `present_over_time(foo[5m])`,
424 `quantile_over_time(0.9, foo[5m])`,
425 `absent_over_time(foo[5m])`,
426 `deg(foo)`,
427 `rad(foo)`,
428 `acos(foo)`,
429 `asin(foo)`,
430 `atan(foo)`,
431 `cos(foo)`,
432 `sin(foo)`,
433 `tan(foo)`,
434 `acosh(foo)`,
435 `asinh(foo)`,
436 `atanh(foo)`,
437 `cosh(foo)`,
438 `sinh(foo)`,
439 `tanh(foo)`,
440 `histogram_count(foo)`,
441 `histogram_sum(foo)`,
442 `histogram_avg(foo)`,
443 `histogram_fraction(0, 0.1, foo)`,
444 `histogram_stddev(foo)`,
445 `histogram_stdvar(foo)`,
446 `min(foo) by(job)`,
447 `max(foo) without(job)`,
448 `sum(foo * on(job) group_left(cluster) bar) by(cluster)`,
449 `sum(foo * on(job) group_left(cluster) bar) without(instance)`,
450 `foo atan2 on(job) bar`,
451 `foo atan2 on(job) group_left(cluster) bar`,
452 `foo != bool bar`,
453 `foo == on(job) bar`,
454 `foo > on(job) group_left() bar`,
455 `foo < on(job) group_right() bar`,
456 `foo >= on(job) group_left(cluster) bar`,
457 `foo <= on(job) group_right(cluster) bar`,
458 `foo * ignoring(job) group_left() bar`,
459 `foo / ignoring(job) group_right() bar`,
460 `foo % bar`,
461 `foo ^ bar`,
462 `sum(foo * on(job) group_left(cluster) bar) by(job) * on(job) group_left(cluster) baz`,
463}
464
465func TestLabelsSource(t *testing.T) {
466 type Snapshot struct {
467 Expr string
468 Output []source.Source
469 }
470
471 _, file, _, ok := runtime.Caller(0)
472 require.True(t, ok, "can't get caller function")
473 file = strings.TrimSuffix(filepath.Base(file), ".go")
474
475 done := map[string]struct{}{}
476 for i, expr := range testCases {
477 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
478 if _, ok := done[expr]; ok {
479 t.Fatalf("Duplicated query: %s", expr)
480 }
481 done[expr] = struct{}{}
482
483 t.Log(expr)
484 n, err := parser.DecodeExpr(expr)
485 if err != nil {
486 t.Error(err)
487 t.FailNow()
488 }
489 output := source.LabelsSource(expr, n.Expr)
490
491 for _, src := range output {
492 src.WalkSources(func(s source.Source, _ *source.Join, _ *source.Unless) {
493 require.Positive(t, s.Position.End, "empty position %+v", s)
494 if s.DeadInfo != nil {
495 require.Positive(t, s.DeadInfo.Fragment.End, "empty dead position %+v", s)
496 }
497 })
498 }
499
500 snap := Snapshot{
501 Expr: expr,
502 Output: output,
503 }
504 d, err := yaml.Marshal(snap)
505 require.NoError(t, err, "failed to YAML encode snapshots")
506 snaps.WithConfig(snaps.Dir("."), snaps.Filename(file)).MatchSnapshot(t, string(d))
507 })
508 }
509}
510
511func TestLabelsSourceCallCoverage(t *testing.T) {
512 for name, def := range promParser.Functions {
513 t.Run(name, func(t *testing.T) {
514 if def.Experimental {
515 t.SkipNow()
516 }
517
518 var b strings.Builder
519 b.WriteString(name)
520 b.WriteRune('(')
521 for i, at := range def.ArgTypes {
522 if i > 0 {
523 b.WriteString(", ")
524 }
525 switch at {
526 case promParser.ValueTypeNone:
527 case promParser.ValueTypeScalar:
528 b.WriteRune('1')
529 case promParser.ValueTypeVector:
530 b.WriteString("http_requests_total")
531 case promParser.ValueTypeMatrix:
532 b.WriteString("http_requests_total[2m]")
533 case promParser.ValueTypeString:
534 b.WriteString(`"foo"`)
535 }
536 }
537 b.WriteRune(')')
538
539 n, err := parser.DecodeExpr(b.String())
540 if err != nil {
541 t.Error(err)
542 t.FailNow()
543 }
544 output := source.LabelsSource(b.String(), n.Expr)
545 require.Len(t, output, 1)
546 require.NotEmpty(t, output[0].Operations)
547 call, ok := source.MostOuterOperation[*promParser.Call](output[0])
548 require.True(t, ok, "no call found in operations for: %q ~> %+v", b.String(), output)
549 require.NotNil(t, call, "no call detected in: %q ~> %+v", b.String(), output)
550 require.Equal(t, name, output[0].Operation())
551 require.Equal(t, def.ReturnType, output[0].Returns, "incorrect return type on Source{}")
552 })
553 }
554}
555
556// Verifies that experimental functions can be parsed and that
557// LabelsSource produces output.
558func TestLabelsSourceCallCoverageExperimental(t *testing.T) {
559 // info() second arg must be a bare label selector, not a named metric.
560 overrides := map[string]string{
561 "info": `info(http_requests_total, {job="foo"})`,
562 }
563
564 for name, def := range promParser.Functions {
565 t.Run(name, func(t *testing.T) {
566 if !def.Experimental {
567 t.SkipNow()
568 }
569
570 var b strings.Builder
571 if override, ok := overrides[name]; ok {
572 b.WriteString(override)
573 } else {
574 b.WriteString(name)
575 b.WriteRune('(')
576 for i, at := range def.ArgTypes {
577 if i > 0 {
578 b.WriteString(", ")
579 }
580 switch at {
581 case promParser.ValueTypeNone:
582 case promParser.ValueTypeScalar:
583 b.WriteRune('1')
584 case promParser.ValueTypeVector:
585 b.WriteString("http_requests_total")
586 case promParser.ValueTypeMatrix:
587 b.WriteString("http_requests_total[2m]")
588 case promParser.ValueTypeString:
589 b.WriteString(`"foo"`)
590 }
591 }
592 b.WriteRune(')')
593 }
594
595 n, err := parser.DecodeExpr(b.String())
596 require.NoError(t, err, "unexpected parse error for: %s", b.String())
597
598 output := source.LabelsSource(b.String(), n.Expr)
599 require.NotEmpty(t, output, "LabelsSource returned no sources for: %s", b.String())
600 })
601 }
602}
603
604func TestLabelsSourceCallCoverageFail(t *testing.T) {
605 n := &parser.PromQLNode{
606 Expr: &promParser.Call{
607 Func: &promParser.Function{
608 Name: "fake_call",
609 },
610 },
611 }
612 output := source.LabelsSource("fake_call()", n.Expr)
613 require.Len(t, output, 1)
614 call, ok := source.MostOuterOperation[*promParser.Call](output[0])
615 require.False(t, ok, "no call should have been detected in fake function, got: %v", ok)
616 require.Nil(t, call, "no call should have been detected in fake function, got: %+v", call)
617}
618
619func TestVectorOperation(t *testing.T) {
620 n := &parser.PromQLNode{
621 Expr: &promParser.NumberLiteral{
622 Val: 1,
623 },
624 }
625 output := source.LabelsSource("1", n.Expr)
626 require.Len(t, output, 1)
627 require.Empty(t, output[0].Operation())
628}
629
630func TestDeadLabelKindUnknownString(t *testing.T) {
631 var dl source.DeadLabelKind = 100
632 require.Equal(t, "unknown", dl.String())
633}
634
635func TestFindFuncNamePositionNoMatch(t *testing.T) {
636 within := posrange.PositionRange{Start: 0, End: 8}
637 pos := source.FindFuncNamePosition("sum(foo)", within, "rate")
638 require.Equal(t, within, pos)
639}
640
641// Verifies that RangeSelectorMode.MarshalYAML returns "default" for the zero value.
642func TestRangeSelectorModeDefaultMarshalYAML(t *testing.T) {
643 var rsm source.RangeSelectorMode
644 val, err := rsm.MarshalYAML()
645 require.NoError(t, err)
646 require.Equal(t, "default", val)
647}
648
649// Verifies that LabelsSource correctly processes expressions that require
650// experimental parser features.
651func TestLabelsSourceWithFeatures(t *testing.T) {
652 type Snapshot struct {
653 Expr string
654 Output []source.Source
655 }
656
657 _, file, _, ok := runtime.Caller(0)
658 require.True(t, ok, "can't get caller function")
659 file = strings.TrimSuffix(filepath.Base(file), ".go")
660
661 type testCaseT struct {
662 description string
663 expr string
664 }
665
666 testCases := []testCaseT{
667 // Experimental function: mad_over_time.
668 {
669 description: "mad_over_time",
670 expr: `mad_over_time(foo[5m])`,
671 },
672 // Experimental aggregator: limitk.
673 {
674 description: "limitk",
675 expr: `limitk(5, foo)`,
676 },
677 // Experimental function: sort_by_label.
678 {
679 description: "sort_by_label",
680 expr: `sort_by_label(foo, "job")`,
681 },
682 // Duration expression in matrix selector.
683 {
684 description: "duration expression in matrix selector",
685 expr: `foo[11s+10s]`,
686 },
687 // Duration expression inside rate().
688 {
689 description: "duration expression inside rate",
690 expr: `rate(foo[5m+1m])`,
691 },
692 // Anchored modifier on vector selector.
693 {
694 description: "anchored vector selector",
695 expr: `foo anchored`,
696 },
697 // Smoothed modifier on matrix selector.
698 {
699 description: "smoothed matrix selector inside rate",
700 expr: `rate(foo[5m] smoothed)`,
701 },
702 // Fill modifier on binop.
703 {
704 description: "binop fill modifier",
705 expr: `foo + on(job) fill(0) bar`,
706 },
707 // fill_left modifier on binop.
708 {
709 description: "binop fill_left modifier",
710 expr: `foo + on(job) fill_left(0) bar`,
711 },
712 // Experimental function with fill modifier.
713 {
714 description: "experimental function with fill modifier",
715 expr: `mad_over_time(foo[5m]) + on(job) fill(0) bar`,
716 },
717 // Experimental function with smoothed modifier.
718 {
719 description: "experimental function with smoothed modifier",
720 expr: `mad_over_time(foo[5m] smoothed)`,
721 },
722 // Duration expression with smoothed modifier.
723 {
724 description: "duration expression with smoothed modifier",
725 expr: `rate(foo[5m+1m] smoothed)`,
726 },
727 // fill_left modifier on binop.
728 {
729 description: "binop fill_left modifier only",
730 expr: `foo + on(job) fill_left(0) bar`,
731 },
732 // fill_right modifier on binop.
733 {
734 description: "binop fill_right modifier only",
735 expr: `foo + on(job) fill_right(0) bar`,
736 },
737 // Duration expression in subquery range.
738 {
739 description: "duration expression in subquery range",
740 expr: `max_over_time(rate(foo[5m])[1h+10m:5m])`,
741 },
742 // Duration expression in subquery step.
743 {
744 description: "duration expression in subquery step",
745 expr: `max_over_time(foo[1h:5m+1m])`,
746 },
747 // Duration expression in both subquery range and step.
748 {
749 description: "duration expression in subquery range and step",
750 expr: `max_over_time(rate(foo[5m])[1h+10m:5m+1m])`,
751 },
752 // Duration expression in vector selector offset.
753 {
754 description: "duration expression in vector offset",
755 expr: `foo offset 5m+1m`,
756 },
757 // Duration expression in vector selector offset using parenthesized
758 // duration to trigger OriginalOffsetExpr on VectorSelector.
759 {
760 description: "duration expression in vector selector offset expr",
761 expr: `foo offset (5m+1m)`,
762 },
763 // Duration expression in subquery offset using parenthesized duration
764 // to trigger OriginalOffsetExpr on SubqueryExpr.
765 {
766 description: "duration expression in subquery offset expr",
767 expr: `max_over_time(foo[1h:5m] offset (5m+1m))`,
768 },
769 // Fill modifier with group_right (CardOneToMany).
770 {
771 description: "fill with group_right",
772 expr: `foo + on(job) group_right() fill(0) bar`,
773 },
774 // Fill modifier with group_left (CardManyToOne).
775 {
776 description: "fill with group_left",
777 expr: `foo + on(job) group_left() fill(0) bar`,
778 },
779 // All four features combined in a single query.
780 {
781 description: "all four features combined",
782 expr: `mad_over_time(foo[5m+1m] smoothed) + on(job) fill(0) bar`,
783 },
784 // Experimental aggregator: limitk.
785 {
786 description: "limitk aggregator",
787 expr: `limitk(5, foo) by(job)`,
788 },
789 // Experimental aggregator: limit_ratio.
790 {
791 description: "limit_ratio aggregator",
792 expr: `limit_ratio(0.5, foo) by(job)`,
793 },
794 // Experimental aggregator: limitk without grouping.
795 {
796 description: "limitk without grouping",
797 expr: `limitk(5, foo)`,
798 },
799 // Experimental aggregator: limit_ratio with without().
800 {
801 description: "limit_ratio with without",
802 expr: `limit_ratio(0.5, foo) without(job)`,
803 },
804 }
805
806 for _, tc := range testCases {
807 t.Run(tc.description, func(t *testing.T) {
808 n, err := parser.DecodeExpr(tc.expr)
809 require.NoError(t, err, "unexpected parse error for: %s", tc.expr)
810
811 output := source.LabelsSource(tc.expr, n.Expr)
812 require.NotEmpty(t, output, "LabelsSource returned no sources for: %s", tc.expr)
813
814 snap := Snapshot{
815 Expr: tc.expr,
816 Output: output,
817 }
818 d, err := yaml.Marshal(snap)
819 require.NoError(t, err, "failed to YAML encode snapshots")
820 snaps.WithConfig(snaps.Dir("."), snaps.Filename(file)).MatchSnapshot(t, string(d))
821 })
822 }
823}
824
825func BenchmarkLabelsSource(b *testing.B) {
826 type testCase struct {
827 node promParser.Node
828 expr string
829 }
830 queries := make([]testCase, 0, len(testCases))
831 for _, expr := range testCases {
832 n, err := parser.DecodeExpr(expr)
833 require.NoError(b, err)
834 queries = append(queries, testCase{
835 expr: expr,
836 node: n.Expr,
837 })
838 }
839
840 for b.Loop() {
841 for _, tc := range queries {
842 source.LabelsSource(tc.expr, tc.node)
843 }
844 }
845}
846