mirror of
				https://github.com/prometheus/prometheus.git
				synced 2025-10-31 16:31:03 +01:00 
			
		
		
		
	* Prettifier: Add spaces with non-callable keywords I prefer to have a difference between, on one side: functions calls, end(), start(), and on the other side with, without, ignoring, by and group_rrigt, group_left. The reasoning is that the former ones are not calls, while other are functions. Additionally, it matches the examples in our documentation. Signed-off-by: Julien Pivotto <roidelapluie@o11y.eu> * Fix tests Signed-off-by: Julien Pivotto <roidelapluie@o11y.eu>
		
			
				
	
	
		
			671 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			671 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022 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 parser
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestAggregateExprPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `sum(foo)`,
 | |
| 			out: `sum(foo)`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by() (task:errors:rate10s{job="s"})`,
 | |
| 			out: `sum(
 | |
|   task:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum without(job,foo) (task:errors:rate10s{job="s"})`,
 | |
| 			out: `sum without (job, foo) (
 | |
|   task:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum(task:errors:rate10s{job="s"}) without(job,foo)`,
 | |
| 			out: `sum without (job, foo) (
 | |
|   task:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo) (task:errors:rate10s{job="s"})`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   task:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum (task:errors:rate10s{job="s"}) by(job,foo)`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   task:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `topk(10, ask:errors:rate10s{job="s"})`,
 | |
| 			out: `topk(
 | |
|   10,
 | |
|   ask:errors:rate10s{job="s"}
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo) (sum by(job,foo) (task:errors:rate10s{job="s"}))`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   sum by (job, foo) (
 | |
|     task:errors:rate10s{job="s"}
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo) (sum by(job,foo) (sum by(job,foo) (task:errors:rate10s{job="s"})))`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   sum by (job, foo) (
 | |
|     sum by (job, foo) (
 | |
|       task:errors:rate10s{job="s"}
 | |
|     )
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo)
 | |
| (sum by(job,foo) (task:errors:rate10s{job="s"}))`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   sum by (job, foo) (
 | |
|     task:errors:rate10s{job="s"}
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo)
 | |
| (sum(task:errors:rate10s{job="s"}) without(job,foo))`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   sum without (job, foo) (
 | |
|     task:errors:rate10s{job="s"}
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum by(job,foo) # Comment 1.
 | |
| (sum by(job,foo) ( # Comment 2.
 | |
| task:errors:rate10s{job="s"}))`,
 | |
| 			out: `sum by (job, foo) (
 | |
|   sum by (job, foo) (
 | |
|     task:errors:rate10s{job="s"}
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		expr, err := ParseExpr(test.in)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Equal(t, test.out, Prettify(expr))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestBinaryExprPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `a+b`,
 | |
| 			out: `a + b`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `a == bool 1`,
 | |
| 			out: `  a
 | |
| == bool
 | |
|   1`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `a + ignoring(job) b`,
 | |
| 			out: `  a
 | |
| + ignoring (job)
 | |
|   b`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `foo_1 + foo_2`,
 | |
| 			out: `  foo_1
 | |
| +
 | |
|   foo_2`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `foo_1 + foo_2 + foo_3`,
 | |
| 			out: `    foo_1
 | |
|   +
 | |
|     foo_2
 | |
| +
 | |
|   foo_3`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `foo + baar + foo_3`,
 | |
| 			out: `  foo + baar
 | |
| +
 | |
|   foo_3`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `foo_1 + foo_2 + foo_3 + foo_4`,
 | |
| 			out: `      foo_1
 | |
|     +
 | |
|       foo_2
 | |
|   +
 | |
|     foo_3
 | |
| +
 | |
|   foo_4`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `foo_1 + ignoring(foo) foo_2 + ignoring(job) group_left foo_3 + on(instance) group_right foo_4`,
 | |
| 			out: `      foo_1
 | |
|     + ignoring (foo)
 | |
|       foo_2
 | |
|   + ignoring (job) group_left ()
 | |
|     foo_3
 | |
| + on (instance) group_right ()
 | |
|   foo_4`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		t.Run(test.in, func(t *testing.T) {
 | |
| 			expr, err := ParseExpr(test.in)
 | |
| 			require.NoError(t, err)
 | |
| 
 | |
| 			require.Equal(t, test.out, Prettify(expr))
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestCallExprPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in: `rate(foo[1m])`,
 | |
| 			out: `rate(
 | |
|   foo[1m]
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `sum_over_time(foo[1m])`,
 | |
| 			out: `sum_over_time(
 | |
|   foo[1m]
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `rate(long_vector_selector[10m:1m] @ start() offset 1m)`,
 | |
| 			out: `rate(
 | |
|   long_vector_selector[10m:1m] @ start() offset 1m
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `histogram_quantile(0.9, rate(foo[1m]))`,
 | |
| 			out: `histogram_quantile(
 | |
|   0.9,
 | |
|   rate(
 | |
|     foo[1m]
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `max_over_time(rate(demo_api_request_duration_seconds_count[1m])[1m:] @ start() offset 1m)`,
 | |
| 			out: `max_over_time(
 | |
|   rate(
 | |
|     demo_api_request_duration_seconds_count[1m]
 | |
|   )[1m:] @ start() offset 1m
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")`,
 | |
| 			out: `label_replace(
 | |
|   up{job="api-server",service="a:c"},
 | |
|   "foo",
 | |
|   "$1",
 | |
|   "service",
 | |
|   "(.*):.*"
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `label_replace(label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*"), "foo", "$1", "service", "(.*):.*")`,
 | |
| 			out: `label_replace(
 | |
|   label_replace(
 | |
|     up{job="api-server",service="a:c"},
 | |
|     "foo",
 | |
|     "$1",
 | |
|     "service",
 | |
|     "(.*):.*"
 | |
|   ),
 | |
|   "foo",
 | |
|   "$1",
 | |
|   "service",
 | |
|   "(.*):.*"
 | |
| )`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		expr, err := ParseExpr(test.in)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		fmt.Println("=>", expr.String())
 | |
| 		require.Equal(t, test.out, Prettify(expr))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestParenExprPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `(foo)`,
 | |
| 			out: `(foo)`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(_foo_long_)`,
 | |
| 			out: `(
 | |
|   _foo_long_
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `((foo_long))`,
 | |
| 			out: `(
 | |
|   (foo_long)
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `((_foo_long_))`,
 | |
| 			out: `(
 | |
|   (
 | |
|     _foo_long_
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(((foo_long)))`,
 | |
| 			out: `(
 | |
|   (
 | |
|     (foo_long)
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		expr, err := ParseExpr(test.in)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Equal(t, test.out, Prettify(expr))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestStepInvariantExpr(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `a @ 1`,
 | |
| 			out: `a @ 1.000`,
 | |
| 		},
 | |
| 		{
 | |
| 			in:  `a @ start()`,
 | |
| 			out: `a @ start()`,
 | |
| 		},
 | |
| 		{
 | |
| 			in:  `vector_selector @ start()`,
 | |
| 			out: `vector_selector @ start()`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		expr, err := ParseExpr(test.in)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Equal(t, test.out, Prettify(expr))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestExprPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `(1 + 2)`,
 | |
| 			out: `(1 + 2)`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(foo + bar)`,
 | |
| 			out: `(
 | |
|   foo + bar
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(foo_long + bar_long)`,
 | |
| 			out: `(
 | |
|     foo_long
 | |
|   +
 | |
|     bar_long
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(foo_long + bar_long + bar_2_long)`,
 | |
| 			out: `(
 | |
|       foo_long
 | |
|     +
 | |
|       bar_long
 | |
|   +
 | |
|     bar_2_long
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `((foo_long + bar_long) + bar_2_long)`,
 | |
| 			out: `(
 | |
|     (
 | |
|         foo_long
 | |
|       +
 | |
|         bar_long
 | |
|     )
 | |
|   +
 | |
|     bar_2_long
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(1111 + 2222)`,
 | |
| 			out: `(
 | |
|     1111
 | |
|   +
 | |
|     2222
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(sum_over_time(foo[1m]))`,
 | |
| 			out: `(
 | |
|   sum_over_time(
 | |
|     foo[1m]
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `histogram_quantile(0.9, rate(foo[1m] @ start()))`,
 | |
| 			out: `histogram_quantile(
 | |
|   0.9,
 | |
|   rate(
 | |
|     foo[1m] @ start()
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*"))`,
 | |
| 			out: `(
 | |
|   label_replace(
 | |
|     up{job="api-server",service="a:c"},
 | |
|     "foo",
 | |
|     "$1",
 | |
|     "service",
 | |
|     "(.*):.*"
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(label_replace(label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*"), "foo", "$1", "service", "(.*):.*"))`,
 | |
| 			out: `(
 | |
|   label_replace(
 | |
|     label_replace(
 | |
|       up{job="api-server",service="a:c"},
 | |
|       "foo",
 | |
|       "$1",
 | |
|       "service",
 | |
|       "(.*):.*"
 | |
|     ),
 | |
|     "foo",
 | |
|     "$1",
 | |
|     "service",
 | |
|     "(.*):.*"
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(label_replace(label_replace((up{job="api-server",service="a:c"}), "foo", "$1", "service", "(.*):.*"), "foo", "$1", "service", "(.*):.*"))`,
 | |
| 			out: `(
 | |
|   label_replace(
 | |
|     label_replace(
 | |
|       (
 | |
|         up{job="api-server",service="a:c"}
 | |
|       ),
 | |
|       "foo",
 | |
|       "$1",
 | |
|       "service",
 | |
|       "(.*):.*"
 | |
|     ),
 | |
|     "foo",
 | |
|     "$1",
 | |
|     "service",
 | |
|     "(.*):.*"
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		// Following queries have been taken from https://monitoring.mixins.dev/
 | |
| 		{
 | |
| 			in: `(node_filesystem_avail_bytes{job="node",fstype!=""} / node_filesystem_size_bytes{job="node",fstype!=""} * 100 < 40 and predict_linear(node_filesystem_avail_bytes{job="node",fstype!=""}[6h], 24*60*60) < 0 and node_filesystem_readonly{job="node",fstype!=""} == 0)`,
 | |
| 			out: `(
 | |
|             node_filesystem_avail_bytes{fstype!="",job="node"}
 | |
|           /
 | |
|             node_filesystem_size_bytes{fstype!="",job="node"}
 | |
|         *
 | |
|           100
 | |
|       <
 | |
|         40
 | |
|     and
 | |
|         predict_linear(
 | |
|           node_filesystem_avail_bytes{fstype!="",job="node"}[6h],
 | |
|             24 * 60
 | |
|           *
 | |
|             60
 | |
|         )
 | |
|       <
 | |
|         0
 | |
|   and
 | |
|       node_filesystem_readonly{fstype!="",job="node"}
 | |
|     ==
 | |
|       0
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(node_filesystem_avail_bytes{job="node",fstype!=""} / node_filesystem_size_bytes{job="node",fstype!=""} * 100 < 20 and predict_linear(node_filesystem_avail_bytes{job="node",fstype!=""}[6h], 4*60*60) < 0 and node_filesystem_readonly{job="node",fstype!=""} == 0)`,
 | |
| 			out: `(
 | |
|             node_filesystem_avail_bytes{fstype!="",job="node"}
 | |
|           /
 | |
|             node_filesystem_size_bytes{fstype!="",job="node"}
 | |
|         *
 | |
|           100
 | |
|       <
 | |
|         20
 | |
|     and
 | |
|         predict_linear(
 | |
|           node_filesystem_avail_bytes{fstype!="",job="node"}[6h],
 | |
|             4 * 60
 | |
|           *
 | |
|             60
 | |
|         )
 | |
|       <
 | |
|         0
 | |
|   and
 | |
|       node_filesystem_readonly{fstype!="",job="node"}
 | |
|     ==
 | |
|       0
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(node_timex_offset_seconds > 0.05 and deriv(node_timex_offset_seconds[5m]) >= 0) or (node_timex_offset_seconds < -0.05 and deriv(node_timex_offset_seconds[5m]) <= 0)`,
 | |
| 			out: `  (
 | |
|         node_timex_offset_seconds
 | |
|       >
 | |
|         0.05
 | |
|     and
 | |
|         deriv(
 | |
|           node_timex_offset_seconds[5m]
 | |
|         )
 | |
|       >=
 | |
|         0
 | |
|   )
 | |
| or
 | |
|   (
 | |
|         node_timex_offset_seconds
 | |
|       <
 | |
|         -0.05
 | |
|     and
 | |
|         deriv(
 | |
|           node_timex_offset_seconds[5m]
 | |
|         )
 | |
|       <=
 | |
|         0
 | |
|   )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `1 - ((node_memory_MemAvailable_bytes{job="node"} or (node_memory_Buffers_bytes{job="node"} + node_memory_Cached_bytes{job="node"} + node_memory_MemFree_bytes{job="node"} + node_memory_Slab_bytes{job="node"}) ) / node_memory_MemTotal_bytes{job="node"})`,
 | |
| 			out: `  1
 | |
| -
 | |
|   (
 | |
|       (
 | |
|           node_memory_MemAvailable_bytes{job="node"}
 | |
|         or
 | |
|           (
 | |
|                   node_memory_Buffers_bytes{job="node"}
 | |
|                 +
 | |
|                   node_memory_Cached_bytes{job="node"}
 | |
|               +
 | |
|                 node_memory_MemFree_bytes{job="node"}
 | |
|             +
 | |
|               node_memory_Slab_bytes{job="node"}
 | |
|           )
 | |
|       )
 | |
|     /
 | |
|       node_memory_MemTotal_bytes{job="node"}
 | |
|   )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `min by (job, integration) (rate(alertmanager_notifications_failed_total{job="alertmanager", integration=~".*"}[5m]) / rate(alertmanager_notifications_total{job="alertmanager", integration="~.*"}[5m])) > 0.01`,
 | |
| 			out: `  min by (job, integration) (
 | |
|       rate(
 | |
|         alertmanager_notifications_failed_total{integration=~".*",job="alertmanager"}[5m]
 | |
|       )
 | |
|     /
 | |
|       rate(
 | |
|         alertmanager_notifications_total{integration="~.*",job="alertmanager"}[5m]
 | |
|       )
 | |
|   )
 | |
| >
 | |
|   0.01`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(count by (job) (changes(process_start_time_seconds{job="alertmanager"}[10m]) > 4) / count by (job) (up{job="alertmanager"})) >= 0.5`,
 | |
| 			out: `  (
 | |
|       count by (job) (
 | |
|           changes(
 | |
|             process_start_time_seconds{job="alertmanager"}[10m]
 | |
|           )
 | |
|         >
 | |
|           4
 | |
|       )
 | |
|     /
 | |
|       count by (job) (
 | |
|         up{job="alertmanager"}
 | |
|       )
 | |
|   )
 | |
| >=
 | |
|   0.5`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		expr, err := ParseExpr(test.in)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, test.out, Prettify(expr))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestUnaryPretty(t *testing.T) {
 | |
| 	maxCharactersPerLine = 10
 | |
| 	inputs := []struct {
 | |
| 		in, out string
 | |
| 	}{
 | |
| 		{
 | |
| 			in:  `-1`,
 | |
| 			out: `-1`,
 | |
| 		},
 | |
| 		{
 | |
| 			in:  `-vector_selector`,
 | |
| 			out: `-vector_selector`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(-vector_selector)`,
 | |
| 			out: `(
 | |
|   -vector_selector
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `-histogram_quantile(0.9,rate(foo[1m]))`,
 | |
| 			out: `-histogram_quantile(
 | |
|   0.9,
 | |
|   rate(
 | |
|     foo[1m]
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `-histogram_quantile(0.99, sum by (le) (rate(foo[1m])))`,
 | |
| 			out: `-histogram_quantile(
 | |
|   0.99,
 | |
|   sum by (le) (
 | |
|     rate(
 | |
|       foo[1m]
 | |
|     )
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `-histogram_quantile(0.9, -rate(foo[1m] @ start()))`,
 | |
| 			out: `-histogram_quantile(
 | |
|   0.9,
 | |
|   -rate(
 | |
|     foo[1m] @ start()
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 		{
 | |
| 			in: `(-histogram_quantile(0.9, -rate(foo[1m] @ start())))`,
 | |
| 			out: `(
 | |
|   -histogram_quantile(
 | |
|     0.9,
 | |
|     -rate(
 | |
|       foo[1m] @ start()
 | |
|     )
 | |
|   )
 | |
| )`,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, test := range inputs {
 | |
| 		t.Run(test.in, func(t *testing.T) {
 | |
| 			expr, err := ParseExpr(test.in)
 | |
| 			require.NoError(t, err)
 | |
| 			require.Equal(t, test.out, Prettify(expr))
 | |
| 		})
 | |
| 	}
 | |
| }
 |