knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/cached_test.go (about) 1 /* 2 Copyright 2018 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package duck 18 19 import ( 20 "context" 21 "fmt" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "golang.org/x/sync/errgroup" 27 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/client-go/tools/cache" 30 ) 31 32 type BlockingInformerFactory struct { 33 block chan struct{} 34 nCalls atomic.Int32 35 } 36 37 var _ InformerFactory = (*BlockingInformerFactory)(nil) 38 39 func (bif *BlockingInformerFactory) Get(ctx context.Context, gvr schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error) { 40 bif.nCalls.Add(1) 41 // Wait here until we can acquire the lock 42 <-bif.block 43 44 // return dummies to avoid subsequent calls to informerCache.init 45 inf := &fakeSharedIndexInformer{} 46 lister := fakeGenericLister(gvr.GroupResource()) 47 48 return inf, lister, nil 49 } 50 51 func TestSameGVR(t *testing.T) { 52 bif := &BlockingInformerFactory{ 53 block: make(chan struct{}), 54 } 55 56 cif := &CachedInformerFactory{ 57 Delegate: bif, 58 } 59 60 // counts the number of calls to cif.Get that returned 61 var retGetCount atomic.Int32 62 63 errGrp, ctx := errgroup.WithContext(context.Background()) 64 65 // Use the same GVR each iteration to ensure we hit the cache and don't 66 // initialize the informerCache for that GVR multiple times through our 67 // Delegate. 68 gvr := schema.GroupVersionResource{ 69 Group: "testing.knative.dev", 70 Version: "v3", 71 Resource: "caches", 72 } 73 74 const iter = 10 75 for range iter { 76 errGrp.Go(func() error { 77 _, _, err := cif.Get(ctx, gvr) 78 retGetCount.Add(1) 79 return err 80 }) 81 } 82 83 // Give the goroutines time to make progress. 84 time.Sleep(100 * time.Millisecond) 85 86 // Check that no call to cif.Get have returned and bif.Get was called 87 // only once. 88 if got, want := retGetCount.Load(), int32(0); got != want { 89 t.Errorf("Got %d returned call(s) to cif.Get, wanted %d", got, want) 90 } 91 if got, want := bif.nCalls.Load(), int32(1); got != want { 92 t.Errorf("Got %d call(s) to bif.Get, wanted %d", got, want) 93 } 94 95 // Allow the Get calls to proceed. 96 close(bif.block) 97 98 if err := errGrp.Wait(); err != nil { 99 t.Fatal("Error while calling cif.Get:", err) 100 } 101 102 // Check that all calls to cif.Get have returned and calls to bif.Get 103 // didn't increase. 104 if got, want := retGetCount.Load(), int32(iter); got != want { 105 t.Errorf("Got %d returned call(s) to cif.Get, wanted %d", got, want) 106 } 107 if got, want := bif.nCalls.Load(), int32(1); got != want { 108 t.Errorf("Got %d call(s) to bif.Get, wanted %d", got, want) 109 } 110 } 111 112 func TestDifferentGVRs(t *testing.T) { 113 bif := &BlockingInformerFactory{ 114 block: make(chan struct{}), 115 } 116 117 cif := &CachedInformerFactory{ 118 Delegate: bif, 119 } 120 121 // counts the number of calls to cif.Get that returned 122 var retGetCount atomic.Int32 123 124 errGrp, ctx := errgroup.WithContext(context.Background()) 125 126 const iter = 10 127 for i := range iter { 128 // Use a different GVR each iteration to check that calls 129 // to bif.Get can proceed even if a call is in progress 130 // for another GVR. 131 gvr := schema.GroupVersionResource{ 132 Group: "testing.knative.dev", 133 Version: fmt.Sprint("v", i), 134 Resource: "caches", 135 } 136 137 errGrp.Go(func() error { 138 _, _, err := cif.Get(ctx, gvr) 139 retGetCount.Add(1) 140 return err 141 }) 142 } 143 144 // Give the goroutines time to make progress. 145 time.Sleep(100 * time.Millisecond) 146 147 // Check that no call to cif.Get have returned and bif.Get was called 148 // once per iteration. 149 if got, want := retGetCount.Load(), int32(0); got != want { 150 t.Errorf("Got %d returned call(s) to cif.Get, wanted %d", got, want) 151 } 152 if got, want := bif.nCalls.Load(), int32(iter); got != want { 153 t.Errorf("Got %d call(s) to bif.Get, wanted %d", got, want) 154 } 155 156 // Allow the Get calls to proceed. 157 close(bif.block) 158 159 if err := errGrp.Wait(); err != nil { 160 t.Fatal("Error while calling cif.Get:", err) 161 } 162 163 // Check that all calls to cif.Get have returned and the number of 164 // calls to bif.Get didn't increase. 165 if got, want := retGetCount.Load(), int32(iter); got != want { 166 t.Errorf("Got %d returned call(s) to cif.Get, wanted %d", got, want) 167 } 168 if got, want := bif.nCalls.Load(), int32(iter); got != want { 169 t.Errorf("Got %d call(s) to bif.Get, wanted %d", got, want) 170 } 171 } 172 173 // fakeGenericLister returns a fake cache.GenericLister. 174 func fakeGenericLister(gr schema.GroupResource) cache.GenericLister { 175 var fakeKeyFunc cache.KeyFunc = func(interface{}) (string, error) { 176 return "", nil 177 } 178 179 fakeIndexer := cache.NewIndexer(fakeKeyFunc, cache.Indexers{}) 180 return cache.NewGenericLister(fakeIndexer, gr) 181 }