diff --git a/pkg/api/testing/validation.go b/pkg/api/testing/validation.go index e11ca144808..49f4eab260b 100644 --- a/pkg/api/testing/validation.go +++ b/pkg/api/testing/validation.go @@ -89,7 +89,7 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, return } if old == nil { - runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, accumulate, opts.SubResources...) + runtimetest.RunValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } else { // Convert old versioned object to internal format before validation. // runtimetest.RunUpdateValidationForEachVersion requires unversioned (internal) objects as input. @@ -100,7 +100,7 @@ func VerifyVersionedValidationEquivalence(t *testing.T, obj, old runtime.Object, if internalOld == nil { return } - runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, internalOld, accumulate, opts.SubResources...) + runtimetest.RunUpdateValidationForEachVersion(t, legacyscheme.Scheme, []string{}, internalObj, internalOld, accumulate, opts.IgnoreObjectConversionErrors, opts.SubResources...) } // Make a copy so we can modify it. @@ -201,6 +201,9 @@ type validationOption struct { SubResources []string // NormalizationRules are the rules to apply to field paths before comparison. NormalizationRules []field.NormalizationRule + + // IgnoreObjectConversions skips the tests if the conversion between object fails. + IgnoreObjectConversionErrors bool } func WithSubResources(subResources ...string) ValidationTestConfig { @@ -215,6 +218,12 @@ func WithNormalizationRules(rules ...field.NormalizationRule) ValidationTestConf } } +func WithIgnoreObjectConversionErrors() ValidationTestConfig { + return func(o *validationOption) { + o.IgnoreObjectConversionErrors = true + } +} + // 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 de1579b41de..d79f13fff79 100644 --- a/pkg/api/testing/validation_test.go +++ b/pkg/api/testing/validation_test.go @@ -47,6 +47,10 @@ func TestVersionedValidationByFuzzing(t *testing.T) { {Group: "node.k8s.io", Version: "v1beta1"}, {Group: "node.k8s.io", Version: "v1"}, {Group: "node.k8s.io", Version: "v1alpha1"}, + {Group: "autoscaling", Version: "v1"}, + {Group: "autoscaling", Version: "v1beta1"}, + {Group: "autoscaling", Version: "v1beta2"}, + {Group: "autoscaling", Version: "v2"}, } fuzzIters := *roundtrip.FuzzIters / 10 // TODO: Find a better way to manage test running time @@ -70,6 +74,7 @@ func TestVersionedValidationByFuzzing(t *testing.T) { allRules := append([]field.NormalizationRule{}, resourcevalidation.ResourceNormalizationRules...) allRules = append(allRules, nodevalidation.NodeNormalizationRules...) opts = append(opts, WithNormalizationRules(allRules...)) + opts = append(opts, WithIgnoreObjectConversionErrors()) VerifyVersionedValidationEquivalence(t, obj, nil, opts...) diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go index 8375d6ecfa1..2520cc7f4a4 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/testing/validation.go @@ -37,16 +37,16 @@ type VersionValidationRunner func(t *testing.T, gv string, versionValidationErro // errs = append(errs, versionValidationErrors...) // generated declarative validation // // Validate that the errors are what was expected for this test case. // }) -func RunValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, subresources ...string) { - runValidation(t, scheme, options, unversioned, fn, subresources...) +func RunValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runValidation(t, scheme, options, unversioned, fn, ignoreConversionErrors, subresources...) } // RunUpdateValidationForEachVersion is like RunValidationForEachVersion but for update validation. -func RunUpdateValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned, unversionedOld runtime.Object, fn VersionValidationRunner, subresources ...string) { - runUpdateValidation(t, scheme, options, unversioned, unversionedOld, fn, subresources...) +func RunUpdateValidationForEachVersion(t *testing.T, scheme *runtime.Scheme, options []string, unversioned, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { + runUpdateValidation(t, scheme, options, unversioned, unversionedOld, fn, ignoreConversionErrors, subresources...) } -func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, subresources ...string) { +func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversioned runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { unversionedGVKs, _, err := scheme.ObjectKinds(unversioned) if err != nil { t.Fatal(err) @@ -66,7 +66,9 @@ func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unver t.Fatal(err) } err = scheme.Convert(unversioned, versioned, nil) - if err != nil { + if ignoreConversionErrors && err != nil { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } fn(t, gv.String(), scheme.Validate(context.Background(), options, versioned, subresources...)) @@ -76,7 +78,7 @@ func runValidation(t *testing.T, scheme *runtime.Scheme, options []string, unver } } -func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversionedNew, unversionedOld runtime.Object, fn VersionValidationRunner, subresources ...string) { +func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, unversionedNew, unversionedOld runtime.Object, fn VersionValidationRunner, ignoreConversionErrors bool, subresources ...string) { unversionedGVKs, _, err := scheme.ObjectKinds(unversionedNew) if err != nil { t.Fatal(err) @@ -96,10 +98,11 @@ func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, t.Fatal(err) } err = scheme.Convert(unversionedNew, versionedNew, nil) - if err != nil { + if err != nil && ignoreConversionErrors { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } - var versionedOld runtime.Object if unversionedOld != nil { versionedOld, err = scheme.New(gvk) @@ -108,7 +111,9 @@ func runUpdateValidation(t *testing.T, scheme *runtime.Scheme, options []string, } err = scheme.Convert(unversionedOld, versionedOld, nil) - if err != nil { + if err != nil && ignoreConversionErrors { + t.Skipf("Failed to convert object from internal type to %s: %v", gvk.Version, err) + } else if err != nil { t.Fatal(err) } }