From 64140b838542ca7470ced477adc62cbd2726e093 Mon Sep 17 00:00:00 2001 From: yongruilin Date: Thu, 29 Jan 2026 10:29:30 +0000 Subject: [PATCH] fuzz internal objects in versioned validation tests Fuzzing must be performed on the internal version of objects because custom fuzzing functions are typically registered for internal types. This ensures that all fields are properly initialized with random values before being converted to various API versions for validation. Move fuzzing logic into VerifyVersionedValidationEquivalence via a new WithFuzzer option. This also fixes a panic that occurred when attempting to fuzz types without an internal version (e.g., APIGroupList) by adding a nil check after internal conversion. --- pkg/api/testing/validation.go | 21 +++++++++++++++++++++ pkg/api/testing/validation_test.go | 11 ++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pkg/api/testing/validation.go b/pkg/api/testing/validation.go index aab847ece4b..7bdd19fc64e 100644 --- a/pkg/api/testing/validation.go +++ b/pkg/api/testing/validation.go @@ -31,6 +31,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" "k8s.io/kubernetes/pkg/api/legacyscheme" + "sigs.k8s.io/randfill" ) // ValidateFunc is a function that runs validation. @@ -88,6 +89,13 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, if internalObj == nil { return } + // We do fuzzing on the internal version of the object. + // This is because custom fuzzing function are only + // supported for internal objects. + // Fuzz the internal object if a fuzzer is provided. + if opts.Fuzzer != nil { + opts.Fuzzer.Fill(internalObj) + } if old == nil { runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } else { @@ -100,6 +108,10 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, if internalOld == nil { return } + // Fuzz the internal old object if a fuzzer is provided. + if opts.Fuzzer != nil { + opts.Fuzzer.Fill(internalOld) + } runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, internalOld, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } @@ -205,6 +217,9 @@ type validationOption struct { // IgnoreObjectConversions skips the tests if the conversion from the internal object // to the versioned object fails. IgnoreObjectConversionErrors bool + + // Fuzzer is the fuzzer to use for generating test objects. + Fuzzer *randfill.Filler } func WithSubResources(subResources ...string) ValidationTestConfig { @@ -225,6 +240,12 @@ func WithIgnoreObjectConversionErrors() ValidationTestConfig { } } +func WithFuzzer(fuzzer *randfill.Filler) ValidationTestConfig { + return func(o *validationOption) { + o.Fuzzer = fuzzer + } +} + // VerifyValidationEquivalence provides a helper for testing the migration from // hand-written imperative validation to declarative validation. It ensures that // the validation logic remains consistent before and after the feature is enabled. diff --git a/pkg/api/testing/validation_test.go b/pkg/api/testing/validation_test.go index df8df8c8a78..62bee0bcef5 100644 --- a/pkg/api/testing/validation_test.go +++ b/pkg/api/testing/validation_test.go @@ -77,7 +77,6 @@ func TestVersionedValidationByFuzzing(t *testing.T) { if err != nil { t.Fatalf("could not create a %v: %s", kind, err) } - f.Fill(obj) var opts []ValidationTestConfig // TODO(API group level configuration): Consider configuring normalization rules at the @@ -85,9 +84,11 @@ func TestVersionedValidationByFuzzing(t *testing.T) { // This would allow each API group to register its own normalization rules independently. allRules := append([]field.NormalizationRule{}, resourcevalidation.ResourceNormalizationRules...) allRules = append(allRules, nodevalidation.NodeNormalizationRules...) - opts = append(opts, WithNormalizationRules(allRules...)) - if gv.Group == "autoscaling" { - opts = append(opts, WithIgnoreObjectConversionErrors()) + opts = append(opts, WithNormalizationRules(allRules...), WithFuzzer(f)) + + // Scale subresource needs to be specified explicitly. + if gv.Group == "autoscaling" && kind == "Scale" { + opts = append(opts, WithSubResources("scale")) } VerifyVersionedValidationEquivalence(t, obj, nil, opts...) @@ -96,7 +97,7 @@ func TestVersionedValidationByFuzzing(t *testing.T) { if err != nil { t.Fatalf("could not create a %v: %s", kind, err) } - f.Fill(old) + VerifyVersionedValidationEquivalence(t, obj, old, opts...) } })