Repository: kubernetes/kubernetes
File: pkg/api/testing/validation.go (Go)

Code before:
	}
}

// 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.
//
// The function operates by running the provided validation function under two scenarios:
//  1. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled,
//     simulating the legacy hand-written validation.
//  2. With both feature gates enabled, using the new declarative validation rules.
//
// It then asserts that the validation errors produced in both scenarios are equivalent,
// guaranteeing a safe migration. It also checks the errors against an expected set.
// It compares errors by field, origin and type; all three should match to be called equivalent.
// It also make sure all versions of the given API returns equivalent errors.
func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime.Object, validateFn ValidateFunc, expectedErrs field.ErrorList, testConfigs ...ValidationTestConfig) {
	t.Helper()
	opts := &validationOption{}
	for _, testcfg := range testConfigs {
		testcfg(opts)
	}
	verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList {
		return validateFn(ctx, obj)
	}, opts)
	verifyDeclarativeValidations(t, ctx, obj, nil, expectedErrs, opts)
	VerifyVersionedValidationEquivalence(t, obj, nil, testConfigs...)
}

// VerifyUpdateValidationEquivalence provides a helper for testing the migration from
// hand-written imperative validation to declarative validation for update operations.
// It ensures that the validation logic remains consistent before and after the feature is enabled.
//
// The function operates by running the provided validation function under two scenarios:
//  1. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled,
//     simulating the legacy hand-written validation.
//  2. With both feature gates enabled, using the new declarative validation rules.
//
// It then asserts that the validation errors produced in both scenarios are equivalent,
// guaranteeing a safe migration. It also checks the errors against an expected set.
// It compares errors by field, origin and type; all three should match to be called equivalent.
// It also make sure all versions of the given API returns equivalent errors.
func VerifyUpdateValidationEquivalence(t *testing.T, ctx context.Context, obj, old runtime.Object, validateUpdateFn ValidateUpdateFunc, expectedErrs field.ErrorList, testConfigs ...ValidationTestConfig) {
	t.Helper()
	opts := &validationOption{}
	for _, testcfg := range testConfigs {
		testcfg(opts)
	}
	verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList {
		return validateUpdateFn(ctx, obj, old)
	}, opts)

Review comment (refactor):
IIUC, this is adding additional test to only test DV generated code no subject to any featuregate toggling. Would it results in different "expectedErrs" between the one in above `verifyValidationEquivalence(t, expectedErrs, func()`?

I think my real question is - is this change strictly necessary? It seems like `strategy.validate` might be more aligned with the real-world behavior. If we do need it, should we extract it out of the `func VerifyValidationEquivalence` and use separate `expectedErrs`?

Code after:
	// Fuzzer is the fuzzer to use for generating test objects.
	Fuzzer *randfill.Filler
}

func WithSubResources(subResources ...string) ValidationTestConfig {
	return func(o *validationOption) {
		o.SubResources = subResources
	}
}

func WithNormalizationRules(rules ...field.NormalizationRule) ValidationTestConfig {
	return func(o *validationOption) {
		o.NormalizationRules = rules
	}
}

func WithIgnoreObjectConversionErrors() ValidationTestConfig {
	return func(o *validationOption) {
		o.IgnoreObjectConversionErrors = true
	}
}

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.
//
// The function operates by running the provided validation function under four scenarios:
//  1. With DeclarativeValidation and DeclarativeValidationBeta feature gates enabled,
//     using the new declarative validation rules (Beta stage).
//  2. With DeclarativeValidation enabled and DeclarativeValidationBeta disabled,
//     using the new declarative validation rules (Standard stage).
//  3. With DeclarativeValidation and DeclarativeValidationTakeover feature gates disabled,
//     simulating the legacy hand-written validation.
//  4. With all declarative rules enforced (including Alpha), ensuring that the full set of
//     declarative validations is correctly implemented (testing only).
//
// It then asserts that the validation errors produced in all scenarios are equivalent,
// guaranteeing a safe migration. It also checks the errors against an expected set.
// It compares errors by field, origin and type; all three should match to be called equivalent.
// It also make sure all versions of the given API returns equivalent errors.
func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime.Object, validateFn ValidateFunc, expectedErrs field.ErrorList, testConfigs ...ValidationTestConfig) {
	t.Helper()
	opts := &validationOption{}
	for _, testcfg := range testConfigs {
		testcfg(opts)