github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logql/syntax/ast_test.go (about)

     1  package syntax
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/prometheus/prometheus/model/labels"
     9  	"github.com/prometheus/prometheus/promql"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/grafana/loki/pkg/logql/log"
    14  )
    15  
    16  var labelBar, _ = ParseLabels("{app=\"bar\"}")
    17  
    18  func Test_logSelectorExpr_String(t *testing.T) {
    19  	t.Parallel()
    20  	tests := []struct {
    21  		selector     string
    22  		expectFilter bool
    23  	}{
    24  		{`{foo="bar"}`, false},
    25  		{`{foo="bar", bar!="baz"}`, false},
    26  		{`{foo="bar", bar!="baz"} != "bip" !~ ".+bop"`, true},
    27  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap"`, true},
    28  		{`{foo="bar", bar!="baz"} |= ""`, false},
    29  		{`{foo="bar", bar!="baz"} |= "" |= ip("::1")`, true},
    30  		{`{foo="bar", bar!="baz"} |= "" != ip("127.0.0.1")`, true},
    31  		{`{foo="bar", bar!="baz"} |~ ""`, false},
    32  		{`{foo="bar", bar!="baz"} |~ ".*"`, false},
    33  		{`{foo="bar", bar!="baz"} |= "" |= ""`, false},
    34  		{`{foo="bar", bar!="baz"} |~ "" |= "" |~ ".*"`, false},
    35  		{`{foo="bar", bar!="baz"} != "bip" !~ ".+bop" | json`, true},
    36  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt`, true},
    37  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | unpack | foo>5`, true},
    38  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | pattern "<foo> bar <buzz>" | foo>5`, true},
    39  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b>=10GB`, true},
    40  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1")`, true},
    41  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1") | level="error"`, true},
    42  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | logfmt | b=ip("127.0.0.1") | level="error" | c=ip("::1")`, true}, // chain inside label filters.
    43  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | regexp "(?P<foo>foo|bar)"`, true},
    44  		{`{foo="bar"} |= "baz" |~ "blip" != "flip" !~ "flap" | regexp "(?P<foo>foo|bar)" | ( ( foo<5.01 , bar>20ms ) or foo="bar" ) | line_format "blip{{.boop}}bap" | label_format foo=bar,bar="blip{{.blop}}"`, true},
    45  	}
    46  
    47  	for _, tt := range tests {
    48  		tt := tt
    49  		t.Run(tt.selector, func(t *testing.T) {
    50  			t.Parallel()
    51  			expr, err := ParseLogSelector(tt.selector, true)
    52  			if err != nil {
    53  				t.Fatalf("failed to parse log selector: %s", err)
    54  			}
    55  			p, err := expr.Pipeline()
    56  			if err != nil {
    57  				t.Fatalf("failed to get filter: %s", err)
    58  			}
    59  			if !tt.expectFilter {
    60  				require.Equal(t, log.NewNoopPipeline(), p)
    61  			}
    62  			if expr.String() != tt.selector {
    63  				t.Fatalf("error expected: %s got: %s", tt.selector, expr.String())
    64  			}
    65  		})
    66  	}
    67  }
    68  
    69  func Test_SampleExpr_String(t *testing.T) {
    70  	t.Parallel()
    71  	for _, tc := range []string{
    72  		`rate( ( {job="mysql"} |="error" !="timeout" ) [10s] )`,
    73  		`absent_over_time( ( {job="mysql"} |="error" !="timeout" ) [10s] )`,
    74  		`absent_over_time( ( {job="mysql"} |="error" !="timeout" ) [10s] offset 10d )`,
    75  		`sum without(a) ( rate ( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`,
    76  		`sum by(a) (rate( ( {job="mysql"} |="error" !="timeout" ) [10s] ) )`,
    77  		`sum(count_over_time({job="mysql"}[5m]))`,
    78  		`sum(count_over_time({job="mysql"}[5m] offset 10m))`,
    79  		`sum(count_over_time({job="mysql"} | json [5m]))`,
    80  		`sum(count_over_time({job="mysql"} | json [5m] offset 10m))`,
    81  		`sum(count_over_time({job="mysql"} | logfmt [5m]))`,
    82  		`sum(count_over_time({job="mysql"} | logfmt [5m] offset 10m))`,
    83  		`sum(count_over_time({job="mysql"} | pattern "<foo> bar <buzz>" | json [5m]))`,
    84  		`sum(count_over_time({job="mysql"} | unpack | json [5m]))`,
    85  		`sum(count_over_time({job="mysql"} | regexp "(?P<foo>foo|bar)" [5m]))`,
    86  		`sum(count_over_time({job="mysql"} | regexp "(?P<foo>foo|bar)" [5m] offset 10y))`,
    87  		`topk(10,sum(rate({region="us-east1"}[5m])) by (name))`,
    88  		`topk by (name)(10,sum(rate({region="us-east1"}[5m])))`,
    89  		`avg( rate( ( {job="nginx"} |= "GET" ) [10s] ) ) by (region)`,
    90  		`avg(min_over_time({job="nginx"} |= "GET" | unwrap foo[10s])) by (region)`,
    91  		`avg(min_over_time({job="nginx"} |= "GET" | unwrap foo[10s] offset 10m)) by (region)`,
    92  		`sum by (cluster) (count_over_time({job="mysql"}[5m]))`,
    93  		`sum by (cluster) (count_over_time({job="mysql"}[5m] offset 10m))`,
    94  		`sum by (cluster) (count_over_time({job="mysql"}[5m])) / sum by (cluster) (count_over_time({job="postgres"}[5m])) `,
    95  		`sum by (cluster) (count_over_time({job="mysql"}[5m] offset 10m)) / sum by (cluster) (count_over_time({job="postgres"}[5m] offset 10m)) `,
    96  		`
    97  		sum by (cluster) (count_over_time({job="postgres"}[5m])) /
    98  		sum by (cluster) (count_over_time({job="postgres"}[5m])) /
    99  		sum by (cluster) (count_over_time({job="postgres"}[5m]))
   100  		`,
   101  		`sum by (cluster) (count_over_time({job="mysql"}[5m])) / min(count_over_time({job="mysql"}[5m])) `,
   102  		`sum by (job) (
   103  			count_over_time({namespace="tns"} |= "level=error"[5m])
   104  		/
   105  			count_over_time({namespace="tns"}[5m])
   106  		)`,
   107  		`stdvar_over_time({app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200)
   108  		| line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo [5m])`,
   109  		`stdvar_over_time({app="foo"} |= "bar" | json | latency >= 250ms or ( status_code < 500 and status_code > 200)
   110  		| line_format "blip{{ .foo }}blop {{.status_code}}" | label_format foo=bar,status_code="buzz{{.bar}}" | unwrap foo [5m] offset 10m)`,
   111  		`sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms|unwrap latency [5m])`,
   112  		`sum by (job) (
   113  			sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap latency[5m])
   114  		/
   115  			count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
   116  		)`,
   117  		`sum by (job) (
   118  			sum_over_time({namespace="tns"} |= "level=error" | json | foo=5 and bar<25ms | unwrap bytes(latency)[5m])
   119  		/
   120  			count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
   121  		)`,
   122  		`sum by (job) (
   123  			sum_over_time(
   124  				{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency) [5m]
   125  			)
   126  		/
   127  			count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
   128  		)`,
   129  		`sum_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
   130  		`last_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
   131  		`first_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
   132  		`absent_over_time({namespace="tns"} |= "level=error" | json |foo>=5,bar<25ms | unwrap latency | __error__!~".*" | foo >5[5m])`,
   133  		`sum by (job) (
   134  			sum_over_time(
   135  				{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency)  | __error__!~".*" [5m]
   136  			)
   137  		/
   138  			count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
   139  		)`,
   140  		`label_replace(
   141  			sum by (job) (
   142  				sum_over_time(
   143  					{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency)  | __error__!~".*" [5m]
   144  				)
   145  			/
   146  				count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m])
   147  			),
   148  			"foo",
   149  			"$1",
   150  			"service",
   151  			"(.*):.*"
   152  		)
   153  		`,
   154  		`10 / (5/2)`,
   155  		`10 / (count_over_time({job="postgres"}[5m])/2)`,
   156  		`{app="foo"} | json response_status="response.status.code", first_param="request.params[0]"`,
   157  		`label_replace(
   158  			sum by (job) (
   159  				sum_over_time(
   160  					{namespace="tns"} |= "level=error" | json | avg=5 and bar<25ms | unwrap duration(latency)  | __error__!~".*" [5m] offset 1h
   161  				)
   162  			/
   163  				count_over_time({namespace="tns"} | logfmt | label_format foo=bar[5m] offset 1h)
   164  			),
   165  			"foo",
   166  			"$1",
   167  			"service",
   168  			"(.*):.*"
   169  		)
   170  		`,
   171  	} {
   172  		t.Run(tc, func(t *testing.T) {
   173  			expr, err := ParseExpr(tc)
   174  			require.Nil(t, err)
   175  
   176  			expr2, err := ParseExpr(expr.String())
   177  			require.Nil(t, err)
   178  			require.Equal(t, expr, expr2)
   179  		})
   180  	}
   181  }
   182  
   183  func TestMatcherGroups(t *testing.T) {
   184  	for i, tc := range []struct {
   185  		query string
   186  		exp   []MatcherRange
   187  	}{
   188  		{
   189  			query: `{job="foo"}`,
   190  			exp: []MatcherRange{
   191  				{
   192  					Matchers: []*labels.Matcher{
   193  						labels.MustNewMatcher(labels.MatchEqual, "job", "foo"),
   194  					},
   195  				},
   196  			},
   197  		},
   198  		{
   199  			query: `count_over_time({job="foo"}[5m]) / count_over_time({job="bar"}[5m] offset 10m)`,
   200  			exp: []MatcherRange{
   201  				{
   202  					Interval: 5 * time.Minute,
   203  					Matchers: []*labels.Matcher{
   204  						labels.MustNewMatcher(labels.MatchEqual, "job", "foo"),
   205  					},
   206  				},
   207  				{
   208  					Interval: 5 * time.Minute,
   209  					Offset:   10 * time.Minute,
   210  					Matchers: []*labels.Matcher{
   211  						labels.MustNewMatcher(labels.MatchEqual, "job", "bar"),
   212  					},
   213  				},
   214  			},
   215  		},
   216  	} {
   217  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   218  			expr, err := ParseExpr(tc.query)
   219  			require.Nil(t, err)
   220  			out := MatcherGroups(expr)
   221  			require.Equal(t, tc.exp, out)
   222  		})
   223  	}
   224  }
   225  
   226  func Test_NilFilterDoesntPanic(t *testing.T) {
   227  	t.Parallel()
   228  	for _, tc := range []string{
   229  		`{namespace="dev", container_name="cart"} |= "" |= "bloop"`,
   230  		`{namespace="dev", container_name="cart"} |= "bleep" |= ""`,
   231  		`{namespace="dev", container_name="cart"} |= "bleep" |= "" |= "bloop"`,
   232  		`{namespace="dev", container_name="cart"} |= "bleep" |= "" |= "bloop"`,
   233  		`{namespace="dev", container_name="cart"} |= "bleep" |= "bloop" |= ""`,
   234  	} {
   235  		t.Run(tc, func(t *testing.T) {
   236  			expr, err := ParseLogSelector(tc, true)
   237  			require.Nil(t, err)
   238  
   239  			p, err := expr.Pipeline()
   240  			require.Nil(t, err)
   241  			_, _, matches := p.ForStream(labelBar).Process(0, []byte("bleepbloop"))
   242  
   243  			require.True(t, matches)
   244  		})
   245  	}
   246  }
   247  
   248  type linecheck struct {
   249  	l string
   250  	e bool
   251  }
   252  
   253  func Test_FilterMatcher(t *testing.T) {
   254  	t.Parallel()
   255  	for _, tt := range []struct {
   256  		q string
   257  
   258  		expectedMatchers []*labels.Matcher
   259  		// test line against the resulting filter, if empty filter should also be nil
   260  		lines []linecheck
   261  	}{
   262  		{
   263  			`{app="foo",cluster=~".+bar"}`,
   264  			[]*labels.Matcher{
   265  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   266  				mustNewMatcher(labels.MatchRegexp, "cluster", ".+bar"),
   267  			},
   268  			nil,
   269  		},
   270  		{
   271  			`{app!="foo",cluster=~".+bar",bar!~".?boo"}`,
   272  			[]*labels.Matcher{
   273  				mustNewMatcher(labels.MatchNotEqual, "app", "foo"),
   274  				mustNewMatcher(labels.MatchRegexp, "cluster", ".+bar"),
   275  				mustNewMatcher(labels.MatchNotRegexp, "bar", ".?boo"),
   276  			},
   277  			nil,
   278  		},
   279  		{
   280  			`{app="foo"} |= "foo"`,
   281  			[]*labels.Matcher{
   282  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   283  			},
   284  			[]linecheck{{"foobar", true}, {"bar", false}},
   285  		},
   286  		{
   287  			`{app="foo"} |= "foo" != "bar"`,
   288  			[]*labels.Matcher{
   289  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   290  			},
   291  			[]linecheck{{"foobuzz", true}, {"bar", false}},
   292  		},
   293  		{
   294  			`{app="foo"} |= "foo" !~ "f.*b"`,
   295  			[]*labels.Matcher{
   296  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   297  			},
   298  			[]linecheck{{"foo", true}, {"bar", false}, {"foobar", false}},
   299  		},
   300  		{
   301  			`{app="foo"} |= "foo" |~ "f.*b"`,
   302  			[]*labels.Matcher{
   303  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   304  			},
   305  			[]linecheck{{"foo", false}, {"bar", false}, {"foobar", true}},
   306  		},
   307  		{
   308  			`{app="foo"} |~ "foo"`,
   309  			[]*labels.Matcher{
   310  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   311  			},
   312  			[]linecheck{{"foo", true}, {"bar", false}, {"foobar", true}},
   313  		},
   314  		{
   315  			`{app="foo"} | logfmt | duration > 1s and total_bytes < 1GB`,
   316  			[]*labels.Matcher{
   317  				mustNewMatcher(labels.MatchEqual, "app", "foo"),
   318  			},
   319  			[]linecheck{{"duration=5m total_bytes=5kB", true}, {"duration=1s total_bytes=256B", false}, {"duration=0s", false}},
   320  		},
   321  	} {
   322  		tt := tt
   323  		t.Run(tt.q, func(t *testing.T) {
   324  			t.Parallel()
   325  			expr, err := ParseLogSelector(tt.q, true)
   326  			assert.Nil(t, err)
   327  			assert.Equal(t, tt.expectedMatchers, expr.Matchers())
   328  			p, err := expr.Pipeline()
   329  			assert.Nil(t, err)
   330  			if tt.lines == nil {
   331  				assert.Equal(t, p, log.NewNoopPipeline())
   332  			} else {
   333  				sp := p.ForStream(labelBar)
   334  				for _, lc := range tt.lines {
   335  					_, _, matches := sp.Process(0, []byte(lc.l))
   336  					assert.Equalf(t, lc.e, matches, "query for line '%s' was %v and not %v", lc.l, matches, lc.e)
   337  				}
   338  			}
   339  		})
   340  	}
   341  }
   342  
   343  func TestStringer(t *testing.T) {
   344  	for _, tc := range []struct {
   345  		in  string
   346  		out string
   347  	}{
   348  		{
   349  			in:  `1 > 1 > 1`,
   350  			out: `0`,
   351  		},
   352  		{
   353  			in:  `1.6`,
   354  			out: `1.6`,
   355  		},
   356  		{
   357  			in:  `1 > 1 > bool 1`,
   358  			out: `0`,
   359  		},
   360  		{
   361  			in:  `1 > bool 1 > count_over_time({foo="bar"}[1m])`,
   362  			out: `(0 > count_over_time({foo="bar"}[1m]))`,
   363  		},
   364  		{
   365  			in:  `1 > bool 1 > bool count_over_time({foo="bar"}[1m])`,
   366  			out: `(0 > bool count_over_time({foo="bar"}[1m]))`,
   367  		},
   368  		{
   369  			in:  `0 > count_over_time({foo="bar"}[1m])`,
   370  			out: `(0 > count_over_time({foo="bar"}[1m]))`,
   371  		},
   372  	} {
   373  		t.Run(tc.in, func(t *testing.T) {
   374  			expr, err := ParseExpr(tc.in)
   375  			require.Nil(t, err)
   376  			require.Equal(t, tc.out, expr.String())
   377  		})
   378  	}
   379  }
   380  
   381  func BenchmarkContainsFilter(b *testing.B) {
   382  	lines := [][]byte{
   383  		[]byte("hello world foo bar"),
   384  		[]byte("bar hello world for"),
   385  		[]byte("hello world foobar and the bar and more bar until the end"),
   386  		[]byte("hello world foobar and the bar and more bar and more than one hundred characters for sure until the end"),
   387  		[]byte("hello world foobar and the bar and more bar and more than one hundred characters for sure until the end and yes bar"),
   388  	}
   389  
   390  	benchmarks := []struct {
   391  		name string
   392  		expr string
   393  	}{
   394  		{
   395  			"AllMatches",
   396  			`{app="foo"} |= "foo" |= "hello" |= "world" |= "bar"`,
   397  		},
   398  		{
   399  			"OneMatches",
   400  			`{app="foo"} |= "foo" |= "not" |= "in" |= "there"`,
   401  		},
   402  		{
   403  			"MixedFiltersTrue",
   404  			`{app="foo"} |= "foo" != "not" |~ "hello.*bar" != "there" |= "world"`,
   405  		},
   406  		{
   407  			"MixedFiltersFalse",
   408  			`{app="foo"} |= "baz" != "not" |~ "hello.*bar" != "there" |= "world"`,
   409  		},
   410  		{
   411  			"GreedyRegex",
   412  			`{app="foo"} |~ "hello.*bar.*"`,
   413  		},
   414  		{
   415  			"NonGreedyRegex",
   416  			`{app="foo"} |~ "hello.*?bar.*?"`,
   417  		},
   418  		{
   419  			"ReorderedRegex",
   420  			`{app="foo"} |~ "hello.*?bar.*?" |= "not"`,
   421  		},
   422  	}
   423  
   424  	for _, bm := range benchmarks {
   425  		b.Run(bm.name, func(b *testing.B) {
   426  			expr, err := ParseLogSelector(bm.expr, false)
   427  			if err != nil {
   428  				b.Fatal(err)
   429  			}
   430  
   431  			p, err := expr.Pipeline()
   432  			if err != nil {
   433  				b.Fatal(err)
   434  			}
   435  
   436  			b.ResetTimer()
   437  			sp := p.ForStream(labelBar)
   438  			for i := 0; i < b.N; i++ {
   439  				for _, line := range lines {
   440  					sp.Process(0, line)
   441  				}
   442  			}
   443  		})
   444  	}
   445  }
   446  
   447  func Test_parserExpr_Parser(t *testing.T) {
   448  	tests := []struct {
   449  		name    string
   450  		op      string
   451  		param   string
   452  		want    log.Stage
   453  		wantErr bool
   454  	}{
   455  		{"json", OpParserTypeJSON, "", log.NewJSONParser(), false},
   456  		{"unpack", OpParserTypeUnpack, "", log.NewUnpackParser(), false},
   457  		{"logfmt", OpParserTypeLogfmt, "", log.NewLogfmtParser(), false},
   458  		{"pattern", OpParserTypePattern, "<foo> bar <buzz>", mustNewPatternParser("<foo> bar <buzz>"), false},
   459  		{"pattern err", OpParserTypePattern, "bar", nil, true},
   460  		{"regexp", OpParserTypeRegexp, "(?P<foo>foo)", mustNewRegexParser("(?P<foo>foo)"), false},
   461  		{"regexp err ", OpParserTypeRegexp, "foo", nil, true},
   462  	}
   463  	for _, tt := range tests {
   464  		t.Run(tt.name, func(t *testing.T) {
   465  			e := &LabelParserExpr{
   466  				Op:    tt.op,
   467  				Param: tt.param,
   468  			}
   469  			got, err := e.Stage()
   470  			if (err != nil) != tt.wantErr {
   471  				t.Errorf("parserExpr.Parser() error = %v, wantErr %v", err, tt.wantErr)
   472  				return
   473  			}
   474  			if tt.wantErr {
   475  				require.Nil(t, got)
   476  			} else {
   477  				require.Equal(t, tt.want, got)
   478  			}
   479  		})
   480  	}
   481  }
   482  
   483  func mustNewRegexParser(re string) log.Stage {
   484  	r, err := log.NewRegexpParser(re)
   485  	if err != nil {
   486  		panic(err)
   487  	}
   488  	return r
   489  }
   490  
   491  func mustNewPatternParser(p string) log.Stage {
   492  	r, err := log.NewPatternParser(p)
   493  	if err != nil {
   494  		panic(err)
   495  	}
   496  	return r
   497  }
   498  
   499  func Test_canInjectVectorGrouping(t *testing.T) {
   500  	tests := []struct {
   501  		vecOp   string
   502  		rangeOp string
   503  		want    bool
   504  	}{
   505  		{OpTypeSum, OpRangeTypeBytes, true},
   506  		{OpTypeSum, OpRangeTypeBytesRate, true},
   507  		{OpTypeSum, OpRangeTypeSum, true},
   508  		{OpTypeSum, OpRangeTypeRate, true},
   509  		{OpTypeSum, OpRangeTypeCount, true},
   510  
   511  		{OpTypeSum, OpRangeTypeAvg, false},
   512  		{OpTypeSum, OpRangeTypeMax, false},
   513  		{OpTypeSum, OpRangeTypeQuantile, false},
   514  		{OpTypeSum, OpRangeTypeStddev, false},
   515  		{OpTypeSum, OpRangeTypeStdvar, false},
   516  		{OpTypeSum, OpRangeTypeMin, false},
   517  		{OpTypeSum, OpRangeTypeMax, false},
   518  
   519  		{OpTypeAvg, OpRangeTypeBytes, false},
   520  		{OpTypeCount, OpRangeTypeBytesRate, false},
   521  		{OpTypeBottomK, OpRangeTypeSum, false},
   522  		{OpTypeMax, OpRangeTypeRate, false},
   523  		{OpTypeMin, OpRangeTypeCount, false},
   524  		{OpTypeTopK, OpRangeTypeCount, false},
   525  	}
   526  	for _, tt := range tests {
   527  		t.Run(tt.vecOp+"_"+tt.rangeOp, func(t *testing.T) {
   528  			if got := canInjectVectorGrouping(tt.vecOp, tt.rangeOp); got != tt.want {
   529  				t.Errorf("canInjectVectorGrouping() = %v, want %v", got, tt.want)
   530  			}
   531  		})
   532  	}
   533  }
   534  
   535  func Test_MergeBinOpVectors_Filter(t *testing.T) {
   536  	res := MergeBinOp(
   537  		OpTypeGT,
   538  		&promql.Sample{
   539  			Point: promql.Point{V: 2},
   540  		},
   541  		&promql.Sample{
   542  			Point: promql.Point{V: 0},
   543  		},
   544  		true,
   545  		true,
   546  	)
   547  
   548  	// ensure we return the left hand side's value (2) instead of the
   549  	// comparison operator's result (1: the truthy answer)
   550  	require.Equal(t, &promql.Sample{
   551  		Point: promql.Point{V: 2},
   552  	}, res)
   553  }