From e9f6aad68ffe2cbbdd2c3d74aa43c4f922f3048f Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Sat, 27 Dec 2025 21:57:54 +0100 Subject: [PATCH] client-go testing: support List+Watch with ResourceVersion Quite a lot of unit tests set up informers with a fake client, do informerFactory.WaitForCacheSync, then create or modify objects. Such tests suffered from a race: because the fake client only delivered objects to the watch after the watch has been created, creating an object too early caused that object to not get delivered to the informer. Usually the timing worked out okay because WaitForCacheSync typically slept a bit while polling, giving the Watch call time to complete, but this race has also gone wrong occasionally. Now with WaitForCacheSync returning more promptly without polling (work in progress), the race goes wrong more often. Instead of working around this in unit tests it's better to improve the fake client such that List+Watch works reliably, regardless of the timing. The fake client has traditionally not touched ResourceVersion in stored objects and doing so now might break unit tests, so the added support for ResourceVersion is intentionally limited to List+Watch. The test simulates "real" usage of informers. It runs in a synctest bubble and completes quickly: go test -v . === RUN TestListAndWatch listandwatch_test.go:67: I0101 01:00:00.000000] Listed configMaps="&ConfigMapList{ListMeta:{ 1 },Items:[]ConfigMap{ConfigMap{ObjectMeta:{cm1 default 0 0001-01-01 00:00:00 +0000 UTC map[] map[] [] [] []},Data:map[string]string{},BinaryData:map[string][]byte{},Immutable:nil,},},}" err=null listandwatch_test.go:79: I0101 01:00:00.000000] Delaying Watch... listandwatch_test.go:90: I0101 01:00:00.100000] Caches synced listandwatch_test.go:107: I0101 01:00:00.100000] Created second ConfigMap listandwatch_test.go:81: I0101 01:00:00.100000] Continuing Watch... --- PASS: TestListAndWatch (0.00s) PASS ok k8s.io/client-go/testing/internal 0.009s Some users of the fake client need to be updated to avoid test failures: - ListMeta comparisons have to be updated. - Optional: pass ListOptions into tracker.Watch. It's optional because the implementation behaves as before when options are missing, but the List+Watch race fix only works when options are passed. Kubernetes-commit: 56448506075c3db1d16b5bbf0c581b833a4646f1 --- pkg/polymorphichelpers/helpers_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/polymorphichelpers/helpers_test.go b/pkg/polymorphichelpers/helpers_test.go index 76628cf63..1ed27015e 100644 --- a/pkg/polymorphichelpers/helpers_test.go +++ b/pkg/polymorphichelpers/helpers_test.go @@ -50,6 +50,9 @@ func TestGetPodList(t *testing.T) { podList: newPodList(2, -1, -1, labelSet), sortBy: func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) }, expected: &corev1.PodList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "2", // Two objects created. + }, Items: []corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{