Documentation
¶
Overview ¶
SPDX-FileCopyrightText: 2026 The Crossplane Authors <https://crossplane.io>
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 The Crossplane Authors <https://crossplane.io>
SPDX-License-Identifier: Apache-2.0
Package roundtrip provides a testing utility library for verifying that Crossplane provider managed resources correctly survive serialization and version-conversion round trips.
The library exercises two kinds of invariants:
Serialization round-trip: an object fuzzed with random data can be encoded to JSON/YAML and decoded back to an identical object.
Conversion round-trip: an object converted spoke→hub→spoke (and hub→spoke→hub) is bit-identical to the original, proving that no data is lost across API version conversions registered via pkg/controller/conversion.RegisterConversions.
Basic usage:
func TestRoundTrip(t *testing.T) {
provider, _ := config.GetProvider(t.Context(), schema, false)
providerNamespaced, _ := config.GetNamespacedProvider(t.Context(), schema, false)
testScheme := runtime.NewScheme()
clusterapis.AddToScheme(testScheme)
namespacedapis.AddToScheme(testScheme)
rt, _ := roundtrip.NewRoundTripTest(provider, providerNamespaced, testScheme)
t.Run("Serialization", rt.TestSerializationRoundtrip)
t.Run("Conversion", rt.TestConversionRoundtrip)
}
Index ¶
- func ASCIIStringFuzzer(s *string, c randfill.Continue)
- func EquateEmptyAndSingleZeroSlice() cmp.Option
- func EquateNilAndZeroValuePtr() cmp.Option
- type FuzzFunc
- type FuzzerOption
- func FuzzerAllowUnexportedFields(allow bool) FuzzerOption
- func FuzzerIterations(n int) FuzzerOption
- func FuzzerMaxDepth(d int) FuzzerOption
- func FuzzerNilChance(p float64) FuzzerOption
- func FuzzerNumElements(min, max int) FuzzerOption
- func FuzzerRandSource(src rand.Source) FuzzerOption
- func FuzzerSkipPatterns(patterns ...*regexp.Regexp) FuzzerOption
- type RoundTripTest
- type TestOption
- func WithCodecFactory(c serializer.CodecFactory) TestOption
- func WithComparisonOptions(cmpOpts ...cmp.Option) TestOption
- func WithExcludeGroupKinds(groupKinds ...schema.GroupKind) TestOption
- func WithExcludeGroups(groups ...string) TestOption
- func WithExtraFuzzFuncs(fns ...FuzzFunc) TestOption
- func WithFuzzerConfig(opts ...FuzzerOption) TestOption
- func WithIncludeGroupKinds(groupKinds ...schema.GroupKind) TestOption
- func WithIncludeGroups(groups ...string) TestOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ASCIIStringFuzzer ¶
ASCIIStringFuzzer is a randfill-compatible fuzzer function that fills string fields with random lowercase-alphanumeric strings of up to 29 characters. Register it via WithExtraFuzzFuncs or rely on the default that NewRoundTripTest includes it automatically.
Restricting the character set avoids encoding issues and makes test output readable when a diff is printed.
func EquateEmptyAndSingleZeroSlice ¶
EquateEmptyAndSingleZeroSlice returns a cmp.Option that treats an empty (or nil) slice as equal to a slice containing exactly one zero-value element:
- pointer slices: []*T{} ≡ []*T{nil}
- value slices: []T{} ≡ []T{zero} (zero is the zero value of T)
This handles conversions that produce []T{zero} where the source had []T{}.
Conflict avoidance: cmpopts.EquateEmpty (already in the default options) registers its own Comparer for the case where both sides have Len==0. This option requires at least one side to have Len==1, so the two Comparers are mutually exclusive and go-cmp never sees an ambiguous pair.
Multi-element slices and slices whose single element is non-zero fall through to go-cmp's normal element-by-element comparison.
Use with WithComparisonOptions:
rt, _ := roundtrip.NewRoundTripTest(provider, nil,
roundtrip.WithComparisonOptions(roundtrip.EquateEmptyAndSingleZeroSlice()))
func EquateNilAndZeroValuePtr ¶
EquateNilAndZeroValuePtr returns a cmp.Option that treats a nil pointer as equal to a pointer to an effectively-zero struct value:
(*Foo)(nil) ≡ &Foo{}
(*Foo)(nil) ≡ &Foo{Nested: []Bar{{}}} // nested slices also effectively zero
Only struct pointer types are intercepted; pointers to scalars, maps, or other non-struct types fall through to go-cmp's default comparison.
Use with WithComparisonOptions:
rt, _ := roundtrip.NewRoundTripTest(provider, nil,
roundtrip.WithComparisonOptions(roundtrip.EquateNilAndZeroValuePtr()))
Types ¶
type FuzzFunc ¶
type FuzzFunc = any
FuzzFunc is a randfill-compatible fuzzer function. The concrete value must have the signature func(*T, randfill.Continue) where T is the type whose instances should be fuzz-filled. Values are registered with randfill.Filler.Funcs and called during fuzzing. The alias preserves full compatibility with the k8s fuzzer infrastructure, which expects []interface{}.
type FuzzerOption ¶
type FuzzerOption func(*fuzzerOptions)
FuzzerOption configures a single fuzzerOptions entry. Pass one or more to WithFuzzerConfig to register a new fuzzer configuration.
func FuzzerAllowUnexportedFields ¶
func FuzzerAllowUnexportedFields(allow bool) FuzzerOption
FuzzerAllowUnexportedFields allows this fuzzer to fill unexported struct fields.
func FuzzerIterations ¶
func FuzzerIterations(n int) FuzzerOption
FuzzerIterations sets the number of fuzz-fill + round-trip cycles this fuzzer configuration will run per (kind, version-pair).
func FuzzerMaxDepth ¶
func FuzzerMaxDepth(d int) FuzzerOption
FuzzerMaxDepth sets the maximum recursion depth the fuzzer will descend into nested structs.
func FuzzerNilChance ¶
func FuzzerNilChance(p float64) FuzzerOption
FuzzerNilChance sets the probability [0, 1] that pointer fields are left nil. Use 0 to force all pointers to be non-nil.
func FuzzerNumElements ¶
func FuzzerNumElements(min, max int) FuzzerOption
FuzzerNumElements sets the min and max number of elements generated for maps and slices.
func FuzzerRandSource ¶
func FuzzerRandSource(src rand.Source) FuzzerOption
FuzzerRandSource sets a deterministic random source for this fuzzer configuration.
func FuzzerSkipPatterns ¶
func FuzzerSkipPatterns(patterns ...*regexp.Regexp) FuzzerOption
FuzzerSkipPatterns registers regexp patterns; any struct field whose name matches a pattern will be skipped by this fuzzer.
type RoundTripTest ¶
type RoundTripTest struct {
// contains filtered or unexported fields
}
RoundTripTest holds all state needed to run serialization and conversion round-trip tests for a single provider. Create it once with NewRoundTripTest and reuse it across sub-tests.
func NewRoundTripTest ¶
func NewRoundTripTest(provider *config.Provider, providerNamespaced *config.Provider, scheme *runtime.Scheme, opts ...TestOption) (*RoundTripTest, error)
NewRoundTripTest constructs a RoundTripTest and performs one-time setup (scheme codec factory, conversion registration).
provider is the cluster-scoped provider configuration. providerNamespaced is the namespaced variant; pass nil if the provider only exposes cluster-scoped resources. scheme must have all provider types registered before being passed here. opts customise test behaviour.
func (*RoundTripTest) TestConversionRoundtrip ¶
func (rt *RoundTripTest) TestConversionRoundtrip(t *testing.T)
TestConversionRoundtrip iterates over every API group registered in the scheme, discovers hub and spoke versions for each GroupKind, and runs both spoke→hub→spoke and hub→spoke→hub conversions, asserting that the result is identical to the input.
All fuzzer configurations registered via WithFuzzerConfig are exercised for each (kind, version-pair). Groups and kinds can be narrowed or excluded with the WithInclude* and WithExclude* options. Any GroupKind with fewer than two registered versions is skipped with a log message.
func (*RoundTripTest) TestSerializationRoundtrip ¶
func (rt *RoundTripTest) TestSerializationRoundtrip(t *testing.T)
TestSerializationRoundtrip verifies that every type registered in the scheme and passing the include/exclude filters survives a JSON encode→decode cycle with no data loss. It delegates to the upstream k8s roundtrip helper. The same WithIncludeGroups/WithExcludeGroups/WithIncludeGroupKinds/ WithExcludeGroupKinds filters that apply to TestConversionRoundtrip also apply here. The first fuzzer configuration is used to build the fuzzer.
type TestOption ¶
type TestOption func(*RoundTripTest)
TestOption is a functional option that customises a RoundTripTest.
func WithCodecFactory ¶
func WithCodecFactory(c serializer.CodecFactory) TestOption
WithCodecFactory overrides the codec factory derived from the scheme. Use this when you need to control codec negotiation (e.g. to add custom serializers).
func WithComparisonOptions ¶
func WithComparisonOptions(cmpOpts ...cmp.Option) TestOption
WithComparisonOptions appends additional cmp.Options to those used when comparing objects after a round trip.
func WithExcludeGroupKinds ¶
func WithExcludeGroupKinds(groupKinds ...schema.GroupKind) TestOption
WithExcludeGroupKinds excludes the given GroupKinds from the conversion round-trip test.
func WithExcludeGroups ¶
func WithExcludeGroups(groups ...string) TestOption
WithExcludeGroups excludes the given API groups from the conversion round-trip test.
func WithExtraFuzzFuncs ¶
func WithExtraFuzzFuncs(fns ...FuzzFunc) TestOption
WithExtraFuzzFuncs appends additional randfill-compatible fuzzer functions (signature: func(*T, randfill.Continue)) to every fuzzer built by the test suite. This is useful to restrict a field to a valid value domain (e.g. only valid enum strings).
func WithFuzzerConfig ¶
func WithFuzzerConfig(opts ...FuzzerOption) TestOption
WithFuzzerConfig adds a new fuzzer configuration to the test suite. The conversion tests run every registered configuration in sequence for each (kind, version-pair), accumulating coverage across different fuzz parameters.
Multiple calls each add a distinct configuration:
rt, _ := roundtrip.NewRoundTripTest(provider, nil,
roundtrip.WithFuzzerConfig(
roundtrip.FuzzerNilChance(0),
roundtrip.FuzzerIterations(20),
),
roundtrip.WithFuzzerConfig(
roundtrip.FuzzerNilChance(0.5),
roundtrip.FuzzerNumElements(0, 5),
),
)
When no WithFuzzerConfig is provided, a single default configuration is used (NilChance≈0.2, NumElements 0–1, 10 iterations).
func WithIncludeGroupKinds ¶
func WithIncludeGroupKinds(groupKinds ...schema.GroupKind) TestOption
WithIncludeGroupKinds restricts the conversion round-trip test to the given GroupKinds. When neither WithIncludeGroups nor WithIncludeGroupKinds is set, all kinds registered in the scheme are tested.
func WithIncludeGroups ¶
func WithIncludeGroups(groups ...string) TestOption
WithIncludeGroups restricts the conversion round-trip test to the given API groups. When neither WithIncludeGroups nor WithIncludeGroupKinds is set, all groups registered in the scheme are tested.