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  }