Skip to content

Commit 42d726c

Browse files
committed
Exclude known edge cases from fuzz tests
Signed-off-by: 🌲 Harry 🌊 John 🏔 <[email protected]>
1 parent eb186b2 commit 42d726c

File tree

4 files changed

+179
-24
lines changed

4 files changed

+179
-24
lines changed

engine/engine_test.go

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/thanos-io/promql-engine/engine"
2525
"github.com/thanos-io/promql-engine/execution/model"
2626
"github.com/thanos-io/promql-engine/execution/warnings"
27+
"github.com/thanos-io/promql-engine/extlabels"
2728
"github.com/thanos-io/promql-engine/logicalplan"
2829
"github.com/thanos-io/promql-engine/query"
2930
"github.com/thanos-io/promql-engine/storage/prometheus"
@@ -2314,6 +2315,20 @@ or
23142315
end: time.UnixMilli(124000),
23152316
step: 15 * time.Second,
23162317
},
2318+
{
2319+
name: "fuzz native histogram float",
2320+
load: `load 2m
2321+
http_request_duration_seconds{pod="nginx-1"} {{schema:0 count:30 sum:14.00 buckets:[27 2 1]}}+{{schema:0 count:30 buckets:[27 2 1]}}x20
2322+
http_request_duration_seconds{pod="nginx-2"} {{schema:-2 count:58 sum:4368.00 buckets:[54 2 2]}}+{{schema:-2 count:58 buckets:[54 2 2]}}x30`,
2323+
query: `-(
2324+
-{__name__="http_request_duration_seconds"}
2325+
/
2326+
histogram_stdvar({__name__="http_request_duration_seconds"})
2327+
)`,
2328+
start: time.UnixMilli(83000),
2329+
end: time.UnixMilli(160000),
2330+
step: time.Minute + 16*time.Second,
2331+
},
23172332
}
23182333

23192334
disableOptimizerOpts := []bool{true, false}
@@ -6038,14 +6053,65 @@ var (
60386053
}
60396054
return true
60406055
}
6056+
compareValueMetrics := func(l, r labels.Labels) (valueMetric bool, equals bool) {
6057+
// For count_value() float values embedded in the labels should be extracted out and compared separately from other labels.
6058+
lLabels := l.Copy()
6059+
rLabels := r.Copy()
6060+
var (
6061+
lVal, rVal string
6062+
lFloat, rFloat float64
6063+
err error
6064+
)
6065+
6066+
if lVal = lLabels.Get("value"); lVal == "" {
6067+
return false, false
6068+
}
6069+
6070+
if rVal = rLabels.Get("value"); rVal == "" {
6071+
return false, false
6072+
}
6073+
6074+
if lFloat, err = strconv.ParseFloat(lVal, 64); err != nil {
6075+
return false, false
6076+
}
6077+
if rFloat, err = strconv.ParseFloat(rVal, 64); err != nil {
6078+
return false, false
6079+
}
6080+
6081+
// Exclude the value label in comparison.
6082+
lLabels = lLabels.MatchLabels(false, "value")
6083+
rLabels = rLabels.MatchLabels(false, "value")
6084+
6085+
if !labels.Equal(lLabels, rLabels) {
6086+
return false, false
6087+
}
6088+
6089+
return true, compareFloats(lFloat, rFloat)
6090+
}
60416091
compareMetrics := func(l, r labels.Labels) bool {
6092+
if valueMetric, equals := compareValueMetrics(l, r); valueMetric {
6093+
return equals
6094+
}
60426095
return l.Hash() == r.Hash()
60436096
}
60446097

6045-
if x.Err != nil && y.Err != nil {
6046-
return cmp.Equal(x.Err.Error(), y.Err.Error())
6047-
} else if x.Err != nil || y.Err != nil {
6048-
return false
6098+
compareErrors := func(l, r error) (stop bool, result bool) {
6099+
if l == nil && r == nil {
6100+
return false, true
6101+
}
6102+
if l != nil && r != nil {
6103+
return true, l.Error() == r.Error()
6104+
}
6105+
err := l
6106+
if err == nil {
6107+
err = r
6108+
}
6109+
// Thanos engine handles duplicate label check differently than Prometheus engine.
6110+
return true, err.Error() == extlabels.ErrDuplicateLabelSet.Error()
6111+
}
6112+
6113+
if stop, result := compareErrors(x.Err, y.Err); stop {
6114+
return result
60496115
}
60506116

60516117
if !compareAnnotations(x.Warnings, y.Warnings) {

engine/enginefuzz_test.go

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,28 @@ type testCase struct {
3939
validateSamples bool
4040
}
4141

42+
type testType int
43+
44+
const (
45+
testTypeFloat testType = iota // 0
46+
testTypeNativeHistogram // 1
47+
)
48+
4249
// shouldValidateSamples checks if the samples can be compared for the expr.
43-
// For certain known cases, prometheus engine and thanos engine returns different samples.
50+
// For certain known cases, Thanos engine returns less samples than Prometheus engine due to optimizations.
4451
func shouldValidateSamples(expr parser.Expr) bool {
4552
valid := true
4653

4754
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
4855
switch n := node.(type) {
4956
case *parser.Call:
50-
if n.Func.Name == "scalar" {
57+
switch n.Func.Name {
58+
case "scalar":
59+
// Optimized to step_invariant in Thanos engine.
60+
valid = false
61+
return errors.New("error")
62+
case "histogram_count", "histogram_sum", "histogram_avg":
63+
// Optimized using DetectHistogramStatsOptimizer and will return smaller samples than Prometheus engine.
5164
valid = false
5265
return errors.New("error")
5366
}
@@ -57,6 +70,48 @@ func shouldValidateSamples(expr parser.Expr) bool {
5770
return valid
5871
}
5972

73+
// validateExpr checks if the given expression is valid for fuzz tests.
74+
// For certain known cases Thanos engine results do not match with Prometheus engine.
75+
func validateExpr(expr parser.Expr, testType testType) bool {
76+
expr, _ = promql.PreprocessExpr(expr, time.Unix(0, 0), time.Unix(0, 0), 0)
77+
valid := true
78+
79+
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
80+
switch n := node.(type) {
81+
case *parser.Call:
82+
switch n.Func.Name {
83+
case "sort", "sort_desc", "sort_by_label", "sort_by_label_desc":
84+
if testType == testTypeNativeHistogram {
85+
// Prometheus engine filters out native histograms in nested sort().
86+
// Thanos engine implements sorting only at the presentation time and ignores nested sort().
87+
// See: https://github.com/thanos-io/promql-engine/pull/595
88+
valid = false
89+
return errors.New("error")
90+
}
91+
case "predict_linear":
92+
switch t := n.Args[0].(type) {
93+
case *parser.StepInvariantExpr:
94+
// Thanos engine cannot correctly handle a MatrixSelector wrapped by StepInvariant.
95+
// eg: predict_linear({__name__="http_request_duration_seconds"}[5m] @ end(), 0.5)
96+
// See: https://github.com/thanos-io/promql-engine/pull/527
97+
if _, ok := t.Expr.(*parser.MatrixSelector); ok {
98+
valid = false
99+
return errors.New("error")
100+
}
101+
}
102+
case "timestamp":
103+
if testType == testTypeNativeHistogram {
104+
// TODO(johrry): Remove after merging https://github.com/thanos-io/promql-engine/pull/598
105+
valid = false
106+
return errors.New("error")
107+
}
108+
}
109+
}
110+
return nil
111+
})
112+
return valid
113+
}
114+
60115
func FuzzEnginePromQLSmithRangeQuery(f *testing.F) {
61116
f.Add(int64(0), uint32(0), uint32(120), uint32(30), 1.0, 1.0, 1.0, 2.0, 30)
62117

@@ -114,6 +169,9 @@ func FuzzEnginePromQLSmithRangeQuery(f *testing.F) {
114169
for i := range testRuns {
115170
for {
116171
expr := ps.WalkRangeQuery()
172+
if !validateExpr(expr, testTypeFloat) {
173+
continue
174+
}
117175
validateSamples = shouldValidateSamples(expr)
118176

119177
query = expr.Pretty(0)
@@ -200,8 +258,9 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
200258
ps := promqlsmith.New(rnd, seriesSet, psOpts...)
201259

202260
var (
203-
q1 promql.Query
204-
query string
261+
q1 promql.Query
262+
query string
263+
validateSamples bool
205264
)
206265
cases := make([]*testCase, testRuns)
207266
for i := range testRuns {
@@ -210,9 +269,10 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
210269
// Parsing experimental function, like mad_over_time, will lead to a parser.ParseErrors, so we also ignore those.
211270
for {
212271
expr := ps.WalkInstantQuery()
213-
if !shouldValidateSamples(expr) {
272+
if !validateExpr(expr, testTypeFloat) {
214273
continue
215274
}
275+
validateSamples = shouldValidateSamples(expr)
216276
query = expr.Pretty(0)
217277
q1, err = newEngine.NewInstantQuery(context.Background(), storage, qOpts, query, queryTime)
218278
if engine.IsUnimplemented(err) || errors.As(err, &parser.ParseErrors{}) {
@@ -243,7 +303,7 @@ func FuzzEnginePromQLSmithInstantQuery(f *testing.F) {
243303
loads: []string{load},
244304
start: queryTime,
245305
end: queryTime,
246-
validateSamples: true,
306+
validateSamples: validateSamples,
247307
}
248308
}
249309
validateTestCases(t, cases)
@@ -278,7 +338,6 @@ func validateTestCases(t *testing.T, cases []*testCase) {
278338
for i, c := range cases {
279339
if !cmp.Equal(c.oldRes, c.newRes, comparer) {
280340
logQuery(c)
281-
282341
t.Logf("case %d error mismatch.\nnew result: %s\nold result: %s\n", i, c.newRes.String(), c.oldRes.String())
283342
failures++
284343
continue
@@ -308,7 +367,6 @@ func getMaxNativeHistogramSumLimit(schema int8, noOfBuckets int) float64 {
308367
}
309368

310369
func FuzzNativeHistogramQuery(f *testing.F) {
311-
f.Skip()
312370
f.Add(int64(0), uint32(0), uint32(60), uint32(120), int8(0), int8(0), uint64(1), uint64(2), uint64(1))
313371

314372
f.Fuzz(func(t *testing.T, seed int64, startTS, endTS, intervalSeconds uint32, schema1 int8, schema2 int8, bucketValue1, bucketValue2, bucketValue3 uint64) {
@@ -323,6 +381,9 @@ func FuzzNativeHistogramQuery(f *testing.F) {
323381
}
324382

325383
bucket1 := []uint64{bucketValue1, bucketValue2, bucketValue3}
384+
if bucketValue1 == 0 || bucketValue2 == 0 || bucketValue3 == 0 {
385+
return
386+
}
326387
bucket2 := []uint64{2 * bucketValue1, bucketValue2, 2 * bucketValue3}
327388

328389
sum1 := getMaxNativeHistogramSumLimit(schema1, len(bucket1))
@@ -349,12 +410,12 @@ func FuzzNativeHistogramQuery(f *testing.F) {
349410
rnd := rand.New(rand.NewSource(seed))
350411

351412
load := fmt.Sprintf(`load 2m
352-
http_request_duration_seconds{pod="nginx-1"} {{schema:%d count:%d sum:%.2f buckets:[%d %d]}}+{{schema:%d count:%d buckets:[%d %d %d]}}x20
353-
http_request_duration_seconds{pod="nginx-2"} {{schema:%d count:%d sum:%.2f buckets:[%d]}}+{{schema:%d count:%d buckets:[%d %d %d]}}x20`,
354-
schema1, count1-bucket1[2], sum1, bucket1[0], bucket1[1],
413+
http_request_duration_seconds{pod="nginx-1"} {{schema:%d count:%d sum:%.2f buckets:[%d %d %d]}}+{{schema:%d count:%d buckets:[%d %d %d]}}x20
414+
http_request_duration_seconds{pod="nginx-2"} {{schema:%d count:%d sum:%.2f buckets:[%d %d %d]}}+{{schema:%d count:%d buckets:[%d %d %d]}}x30`,
415+
schema1, count1, sum1, bucket1[0], bucket1[1], bucket1[2],
355416
schema1, count1, bucket1[0], bucket1[1], bucket1[2],
356417

357-
schema2, count2-bucket2[1]-bucket2[2], sum2, bucket2[0],
418+
schema2, count2, sum2, bucket2[0], bucket2[1], bucket2[2],
358419
schema2, count2, bucket2[0], bucket2[1], bucket2[2],
359420
)
360421

@@ -396,17 +457,21 @@ func FuzzNativeHistogramQuery(f *testing.F) {
396457

397458
for range testRuns / 2 {
398459
var (
399-
qInstant promql.Query
400-
qRange promql.Query
401-
instantQuery string
402-
rangeQuery string
460+
qInstant promql.Query
461+
qRange promql.Query
462+
instantQuery string
463+
rangeQuery string
464+
validateSamplesForInstantQuery bool
465+
validateSamplesForRangeQuery bool
403466
)
404467

405468
for {
406469
expr := ps.WalkInstantQuery()
407-
if !shouldValidateSamples(expr) {
470+
if !validateExpr(expr, testTypeNativeHistogram) {
408471
continue
409472
}
473+
474+
validateSamplesForInstantQuery = shouldValidateSamples(expr)
410475
instantQuery = expr.Pretty(0)
411476

412477
qInstant, err = newEngine.NewInstantQuery(context.Background(), storage, qOpts, instantQuery, startTime)
@@ -420,9 +485,11 @@ func FuzzNativeHistogramQuery(f *testing.F) {
420485

421486
for {
422487
expr := ps.WalkRangeQuery()
423-
if !shouldValidateSamples(expr) {
488+
if !validateExpr(expr, testTypeNativeHistogram) {
424489
continue
425490
}
491+
492+
validateSamplesForRangeQuery = shouldValidateSamples(expr)
426493
rangeQuery = expr.Pretty(0)
427494

428495
qRange, err = newEngine.NewRangeQuery(context.Background(), storage, qOpts, rangeQuery, startTime, endTime, interval)
@@ -463,7 +530,8 @@ func FuzzNativeHistogramQuery(f *testing.F) {
463530
loads: []string{load},
464531
start: startTime,
465532
end: startTime,
466-
validateSamples: true,
533+
interval: 0,
534+
validateSamples: validateSamplesForInstantQuery,
467535
})
468536

469537
rangeCases = append(rangeCases, &testCase{
@@ -475,7 +543,8 @@ func FuzzNativeHistogramQuery(f *testing.F) {
475543
loads: []string{load},
476544
start: startTime,
477545
end: endTime,
478-
validateSamples: true,
546+
interval: interval,
547+
validateSamples: validateSamplesForRangeQuery,
479548
})
480549
}
481550

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
go test fuzz v1
2+
int64(20)
3+
uint32(0)
4+
uint32(120)
5+
uint32(109)
6+
float64(73)
7+
float64(10)
8+
float64(7)
9+
float64(2)
10+
int(71)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
go test fuzz v1
2+
int64(-2)
3+
uint32(83)
4+
uint32(160)
5+
uint32(76)
6+
int8(0)
7+
int8(-2)
8+
uint64(27)
9+
uint64(2)
10+
uint64(1)

0 commit comments

Comments
 (0)