From 983d817724c3865fb9d3decf8824479bc1909078 Mon Sep 17 00:00:00 2001 From: Sandy Chen Date: Fri, 22 May 2026 19:16:17 +0900 Subject: [PATCH] fix(integration): log fuzz seed so failures can be reproduced Adds a newFuzzRand(t *testing.T) *rand.Rand helper that logs the random seed every fuzz test draws (via t.Logf), and replaces 13 ad-hoc rand.New(rand.NewSource(time.Now().Unix())) call sites across integration/query_fuzz_test.go (11 sites) and integration/parquet_querier_test.go (2 sites) with calls to it. A FUZZ_SEED environment variable overrides the default time-based seed so a failing CI run can be replayed locally byte-for-byte. This is a pure observability change. It does not fix the underlying TestProtobufCodecFuzz flake tracked in #7548 -- it makes the next failure classifiable. The current code silently consumes the seed via rand.New(rand.NewSource(now.Unix())) and never surfaces it in the test log, so the failing query/series combination cannot be regenerated and every failure has to be diagnosed from the comparison output alone. That is insufficient to distinguish real Cortex/Prometheus divergence from known-class non-determinism from infra noise. Logging the seed (and accepting an override) is the minimum surface needed to make the next failure reproducible. Invalid FUZZ_SEED values are logged rather than silently falling back, so a typo in CI configuration surfaces immediately instead of masquerading as a successful override. The helper is applied to all 13 fuzz call sites, not just TestProtobufCodecFuzz, because the underlying observability gap is identical for every fuzz test in the integration suite and applying the helper everywhere is strictly less code than leaving twelve other tests with the same gap. This is the final PR in a four-PR series filed against issues #7545-#7548, complementing PR #7544 (which fixes #7543). Fixes #7548 Signed-off-by: Sandy Chen Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Sandy Chen --- integration/parquet_querier_test.go | 5 ++-- integration/query_fuzz_test.go | 41 ++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/integration/parquet_querier_test.go b/integration/parquet_querier_test.go index ed8ac4463b..2f66607232 100644 --- a/integration/parquet_querier_test.go +++ b/integration/parquet_querier_test.go @@ -5,7 +5,6 @@ package integration import ( "context" "fmt" - "math/rand" "path/filepath" "slices" "strconv" @@ -89,7 +88,7 @@ func TestParquetFuzz(t *testing.T) { require.NoError(t, writeFileToSharedDir(s, "alertmanager_configs", []byte{})) ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") numSeries := 10 numSamples := 60 @@ -240,7 +239,7 @@ func TestParquetProjectionPushdownFuzz(t *testing.T) { require.NoError(t, writeFileToSharedDir(s, "alertmanager_configs", []byte{})) ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") numSeries := 20 numSamples := 100 diff --git a/integration/query_fuzz_test.go b/integration/query_fuzz_test.go index f735bd91d5..f49bddba8e 100644 --- a/integration/query_fuzz_test.go +++ b/integration/query_fuzz_test.go @@ -121,7 +121,7 @@ func TestNativeHistogramFuzz(t *testing.T) { } ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") err = os.MkdirAll(dir, os.ModePerm) @@ -222,7 +222,7 @@ func TestExperimentalPromQLFuncsWithPrometheus(t *testing.T) { } ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") err = os.MkdirAll(dir, os.ModePerm) @@ -357,7 +357,7 @@ func TestDisableChunkTrimmingFuzz(t *testing.T) { waitUntilReady(t, context.Background(), c1, c2, `{job="test"}`, start, now) - rnd := rand.New(rand.NewSource(now.Unix())) + rnd := newFuzzRand(t) opts := []promqlsmith.Option{ // @ modifier and offset disabled: known bug in Prometheus (e.g. predict_linear with @/offset can panic). promqlsmith.WithEnabledFunctions(enabledFunctions), @@ -538,7 +538,7 @@ func TestExpandedPostingsCacheFuzz(t *testing.T) { } } - rnd := rand.New(rand.NewSource(now.Unix())) + rnd := newFuzzRand(t) opts := []promqlsmith.Option{ // @ modifier and offset disabled: known bug in Prometheus (e.g. predict_linear with @/offset can panic). promqlsmith.WithEnabledAggrs(enabledAggrs), @@ -767,7 +767,7 @@ func TestVerticalShardingFuzz(t *testing.T) { waitUntilReady(t, context.Background(), c1, c2, `{job="test"}`, start, end) - rnd := rand.New(rand.NewSource(now.Unix())) + rnd := newFuzzRand(t) opts := []promqlsmith.Option{ // @ modifier and offset disabled: known bug in Prometheus (e.g. predict_linear with @/offset can panic). promqlsmith.WithEnabledFunctions(enabledFunctions), @@ -883,7 +883,7 @@ func TestProtobufCodecFuzz(t *testing.T) { waitUntilReady(t, context.Background(), c1, c2, `{job="test"}`, start, end) - rnd := rand.New(rand.NewSource(now.Unix())) + rnd := newFuzzRand(t) opts := []promqlsmith.Option{ // @ modifier and offset disabled: known bug in Prometheus (e.g. predict_linear with @/offset can panic). promqlsmith.WithEnabledFunctions(enabledFunctions), @@ -1202,7 +1202,7 @@ func TestStoreGatewayLazyExpandedPostingsSeriesFuzz(t *testing.T) { lbls = append(lbls, labels.FromStrings(labels.MetricName, metricName, "job", "test", "series", strconv.Itoa(i%200), "status_code", statusCodes[i%5])) } ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := t.TempDir() storage, err := e2ecortex.NewS3ClientForMinio(minio, flags["-blocks-storage.s3.bucket-name"]) @@ -1357,7 +1357,7 @@ func TestStoreGatewayLazyExpandedPostingsSeriesFuzzWithPrometheus(t *testing.T) lbls = append(lbls, labels.FromStrings(labels.MetricName, metricName, "job", "test", "series", strconv.Itoa(i%200), "status_code", statusCodes[i%5])) } ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") err = os.MkdirAll(dir, os.ModePerm) @@ -1590,7 +1590,7 @@ func TestBackwardCompatibilityQueryFuzz(t *testing.T) { ctx := context.Background() waitUntilReady(t, ctx, c1, c2, `{job="test"}`, start, end) - rnd := rand.New(rand.NewSource(now.Unix())) + rnd := newFuzzRand(t) opts := []promqlsmith.Option{ // @ modifier and offset disabled: known bug in Prometheus (e.g. predict_linear with @/offset can panic). promqlsmith.WithEnabledFunctions(enabledFunctions), @@ -1662,7 +1662,7 @@ func TestPrometheusCompatibilityQueryFuzz(t *testing.T) { } ctx := context.Background() - rnd := rand.New(rand.NewSource(time.Now().Unix())) + rnd := newFuzzRand(t) dir := filepath.Join(s.SharedDir(), "data") err = os.MkdirAll(dir, os.ModePerm) @@ -1816,8 +1816,7 @@ func TestRW1vsRW2QueryFuzz(t *testing.T) { _, err = c2.PushV2(symbols, v2Series) require.NoError(t, err) - seed := now.Unix() - rnd := rand.New(rand.NewSource(seed)) + rnd := newFuzzRand(t) ctx := context.Background() waitUntilReady(t, ctx, c1, c2, `{job="test"}`, start, end) @@ -1980,6 +1979,24 @@ func shouldUseSampleNumComparer(query string) bool { return false } +// newFuzzRand returns a *rand.Rand whose seed is logged via t.Logf so failing +// fuzz cases can be reproduced. By default the seed is time.Now().Unix(); +// setting FUZZ_SEED to a base-10 int64 overrides the default and pins the +// run to a specific seed. +func newFuzzRand(t *testing.T) *rand.Rand { + seed := time.Now().Unix() + if v := os.Getenv("FUZZ_SEED"); v != "" { + if parsed, err := strconv.ParseInt(v, 10, 64); err == nil { + t.Logf("integration fuzz random seed: overridden to %d via FUZZ_SEED", parsed) + seed = parsed + } else { + t.Logf("integration fuzz random seed: ignoring invalid FUZZ_SEED=%q: %v", v, err) + } + } + t.Logf("integration fuzz random seed: %d (override with FUZZ_SEED env var)", seed) + return rand.New(rand.NewSource(seed)) +} + func isValidQuery(generatedQuery parser.Expr, skipBackwardIncompat bool) bool { isValid := true queryStr := generatedQuery.String()