cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.74.7

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/utils/source_test.go

406lines · modecode

1package utils_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 "gopkg.in/yaml.v3"
15
16 "github.com/cloudflare/pint/internal/parser"
17 "github.com/cloudflare/pint/internal/parser/utils"
18
19 promParser "github.com/prometheus/prometheus/promql/parser"
20)
21
22func TestMain(t *testing.M) {
23 v := t.Run()
24 if _, err := snaps.Clean(t, snaps.CleanOpts{Sort: true}); err != nil {
25 fmt.Printf("snaps.Clean() returned an error: %s", err)
26 os.Exit(100)
27 }
28 os.Exit(v)
29}
30
31func TestLabelsSource(t *testing.T) {
32 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_over_time(foo[5m]) or absent(bar)`,
139 `bar * on() group_right(cluster, env) absent(foo{job="xxx"})`,
140 `bar * on() group_right() absent(foo{job="xxx"})`,
141 "vector(1)",
142 "vector(scalar(foo))",
143 "vector(0.0 >= bool 0.5) == 1",
144 `sum_over_time(foo{job="myjob"}[5m])`,
145 `days_in_month()`,
146 `days_in_month(foo{job="foo"})`,
147 `label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")`,
148 `label_replace(sum by (pod) (pod_status) > 0, "cluster", "$1", "pod", "(.*)")`,
149 `(time() - my_metric) > 5*3600`,
150 `up{instance="a", job="prometheus"} * ignoring(job) up{instance="a", job="pint"}`,
151 `
152avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case!~".*offpeak.*"})
153< 0.5 > 0
154or sum without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*tier1.*"})
155< on() count(colo_router_tier:disabled_pops:max{tier="1",router=~"edge.*"}) * 0.4 > 0
156or avg without(router, colo_id, instance) (router_anycast_prefix_enabled{cidr_use_case=~".*regional.*"})
157< 0.1 > 0
158`,
159 `label_replace(sum(foo) without(instance), "instance", "none", "", "")`,
160 `
161sum by (region, target, colo_name) (
162 sum_over_time(probe_success{job="abc"}[5m])
163 or
164 vector(1)
165) == 0`,
166 `vector(1) or foo`,
167 `vector(0) > 0`,
168 `vector(0) > vector(1)`,
169 `sum(foo or vector(0)) > 0`,
170 `(sum(foo or vector(1)) > 0) == 2`,
171 `(sum(foo or vector(1)) > 0) != 2`,
172 `(sum(foo or vector(2)) > 0) != 2`,
173 `(sum(sometimes{foo!="bar"} or vector(0)))
174or
175((bob > 10) or sum(foo) or vector(1))`,
176 `
177(
178 sum(sometimes{foo!="bar"})
179 or
180 vector(1)
181) and (
182 ((bob > 10) or sum(bar))
183 or
184 notfound > 0
185)`,
186 "foo offset 5m > 5",
187 `
188(rate(metric2[5m]) or vector(0)) +
189(rate(metric1[5m]) or vector(1)) +
190(rate(metric3{log_name="samplerd"}[5m]) or vector(2)) > 0
191`,
192 `label_replace(vector(1), "nexthop_tag", "$1", "nexthop", "(.+)")`,
193 `(sum(foo{job="myjob"}))`,
194 `(-foo{job="myjob"})`,
195 "\n((( group(vector(0)) ))) > 0",
196 "1 > bool 5",
197 `prometheus_ready{job="prometheus"} unless vector(0)`,
198 `prometheus_ready{job="prometheus"} unless on() vector(0)`,
199 `prometheus_ready{job="prometheus"} unless on(job) vector(0)`,
200 `
201max by (instance, cluster) (cf_node_role{kubernetes_role="master",role="kubernetes"})
202unless
203 sum by (instance, cluster) (time() - node_systemd_timer_last_trigger_seconds{name=~"etcd-defrag-.*.timer"})
204 * on (instance) group_left (cluster)
205 cf_node_role{kubernetes_role="master",role="kubernetes"}
206`,
207 `foo{a="1"} * on() bar{b="2"}`,
208 `foo{a="1"} * on(instance) group_left(c,d) bar{b="2"}`,
209 `foo{a="1"} * on(instance) group_right(c,d) bar{b="2"}`,
210 `foo{a="1"} * on(instance) sum(bar{b="2"})`,
211 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"})`,
212 `sum(foo{a="1"}) * on(instance) group_right(c,d) bar{b="2"}`,
213 `foo{a="1"} * on(instance) group_left(c,d) sum(bar{b="2"}) without(instance)`,
214 `sum(foo{a="1"}) without(instance) * on(instance) group_right(c,d) bar{b="2"}`,
215 `
216 max without (source_instance) (
217 increase(kernel_device_io_errors_total{device!~"loop.+"}[120m]) > 3 unless on(instance, device) (
218 increase(kernel_device_io_soft_errors_total{device!~"loop.+"}[125m])*2 > increase(kernel_device_io_errors_total[120m])
219 )
220 and on(device, instance) absent(node_disk_info)
221 ) * on(instance) group_left(group) label_replace(salt_highstate_runner_configured_minions, "instance", "$1", "minion", "(.+)")
222`,
223 `sum(foo{a="1"}) by(job) * on() bar{b="2"}`,
224 `sum(sum(foo) without(job)) by(job)`,
225 `
226prometheus:scrape_series_added:since_gc:sum
227* on(prometheus) group_left()
228label_replace(
229 max(max_over_time(go_memstats_alloc_bytes{job="prometheus"}[2h])) by(instance)
230 /
231 max(max_over_time(prometheus_tsdb_head_series[2h])) by(instance),
232 "prometheus", "$1",
233 "instance", "(.+)"
234)
235`,
236 `(day_of_week() == 6 and hour() < 1) or vector(1)`,
237 `
238sum by (foo, bar) (
239 rate(errors_total[5m])
240 * on (instance) group_left (bob, alice)
241 server_errors_total
242)`,
243 `1 - (foo or vector(0)) < 0.999`,
244 `
245(
246 vector(1) and month() == 2
247) or vector(0)
248`,
249 `count by (region) (stddev by (colo_name, region) (error_total))`,
250 `
251(
252 avg(
253 rate(foo_rejections[6h])
254 or
255 vector(0)
256 ) by (colo_name)
257 /
258 (
259 avg(
260 rate(foo_total[6h])
261 or
262 vector(1)
263 ) by (colo_name)
264 )
265) > 5
266*
267(
268 avg(
269 rate(foo_rejections[6h] offset 1d)
270 or
271 vector(0)
272 ) by (colo_name)
273 /
274 avg(
275 rate(foo_total[6h] offset 1d)
276 or
277 vector(1)
278 ) by (colo_name)
279) and on (colo_name) (colo_job:foo_total:rate2m or vector(0)) > 80
280 and on (colo_name) (colo_job:foo_total:rate2m offset 1d or vector(0)) > 80
281`,
282 `sum(selector) / sum(selector offset 30m) > 5`,
283 `
284count by (dc) (
285 max(0 < (token_expiration - time()) < (6*60*60)) by (instance)
286 * on (instance) group_right label_replace(
287 configured_minions, "instance", "$1", "minion", "(.+)")
288 ) > 5`,
289 `topk(10, prometheus_build_info*prometheus_ready)`,
290 `bottomk(10, prometheus_build_info*prometheus_ready)`,
291 }
292
293 type Snapshot struct {
294 Expr string
295 Output []utils.Source
296 }
297
298 _, file, _, ok := runtime.Caller(0)
299 require.True(t, ok, "can't get caller function")
300 file = strings.TrimSuffix(filepath.Base(file), ".go")
301
302 done := map[string]struct{}{}
303 for i, expr := range testCases {
304 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
305 if _, ok := done[expr]; ok {
306 t.Fatalf("Duplicated query: %s", expr)
307 }
308 done[expr] = struct{}{}
309
310 n, err := parser.DecodeExpr(expr)
311 if err != nil {
312 t.Error(err)
313 t.FailNow()
314 }
315 output := utils.LabelsSource(expr, n.Expr)
316
317 for _, src := range output {
318 src.WalkSources(func(s utils.Source, _ *utils.Join, _ *utils.Unless) {
319 require.Positive(t, s.Position.End, "empty position %+v", s)
320 if s.DeadInfo != nil {
321 require.Positive(t, s.DeadInfo.Fragment.End, "empty dead position %+v", s)
322 }
323 })
324 }
325
326 snap := Snapshot{
327 Expr: expr,
328 Output: output,
329 }
330 d, err := yaml.Marshal(snap)
331 require.NoError(t, err, "failed to YAML encode snapshots")
332 snaps.WithConfig(snaps.Dir("."), snaps.Filename(file)).MatchSnapshot(t, string(d))
333 })
334 }
335}
336
337func TestLabelsSourceCallCoverage(t *testing.T) {
338 for name, def := range promParser.Functions {
339 t.Run(name, func(t *testing.T) {
340 if def.Experimental {
341 t.SkipNow()
342 }
343
344 var b strings.Builder
345 b.WriteString(name)
346 b.WriteRune('(')
347 for i, at := range def.ArgTypes {
348 if i > 0 {
349 b.WriteString(", ")
350 }
351 switch at {
352 case promParser.ValueTypeNone:
353 case promParser.ValueTypeScalar:
354 b.WriteRune('1')
355 case promParser.ValueTypeVector:
356 b.WriteString("http_requests_total")
357 case promParser.ValueTypeMatrix:
358 b.WriteString("http_requests_total[2m]")
359 case promParser.ValueTypeString:
360 b.WriteString(`"foo"`)
361 }
362 }
363 b.WriteRune(')')
364
365 n, err := parser.DecodeExpr(b.String())
366 if err != nil {
367 t.Error(err)
368 t.FailNow()
369 }
370 output := utils.LabelsSource(b.String(), n.Expr)
371 require.Len(t, output, 1)
372 require.NotEmpty(t, output[0].Operations)
373 call, ok := utils.MostOuterOperation[*promParser.Call](output[0])
374 require.True(t, ok, "no call found in operations for: %q ~> %+v", b.String(), output)
375 require.NotNil(t, call, "no call detected in: %q ~> %+v", b.String(), output)
376 require.Equal(t, name, output[0].Operation())
377 require.Equal(t, def.ReturnType, output[0].Returns, "incorrect return type on Source{}")
378 })
379 }
380}
381
382func TestLabelsSourceCallCoverageFail(t *testing.T) {
383 n := &parser.PromQLNode{
384 Expr: &promParser.Call{
385 Func: &promParser.Function{
386 Name: "fake_call",
387 },
388 },
389 }
390 output := utils.LabelsSource("fake_call()", n.Expr)
391 require.Len(t, output, 1)
392 call, ok := utils.MostOuterOperation[*promParser.Call](output[0])
393 require.False(t, ok, "no call should have been detected in fake function, got: %v", ok)
394 require.Nil(t, call, "no call should have been detected in fake function, got: %+v", call)
395}
396
397func TestVectorOperation(t *testing.T) {
398 n := &parser.PromQLNode{
399 Expr: &promParser.NumberLiteral{
400 Val: 1,
401 },
402 }
403 output := utils.LabelsSource("1", n.Expr)
404 require.Len(t, output, 1)
405 require.Empty(t, output[0].Operation())
406}
407