k8s.io/client-go@v0.31.1/tools/cache/reflector_watchlist_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes 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 cache
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	v1 "k8s.io/api/core/v1"
    32  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/watch"
    38  	testingclock "k8s.io/utils/clock/testing"
    39  	"k8s.io/utils/pointer"
    40  	"k8s.io/utils/ptr"
    41  )
    42  
    43  func TestInitialEventsEndBookmarkTicker(t *testing.T) {
    44  	assertNoEvents := func(t *testing.T, c <-chan time.Time) {
    45  		select {
    46  		case e := <-c:
    47  			t.Errorf("Unexpected: %#v event received, expected no events", e)
    48  		default:
    49  			return
    50  		}
    51  	}
    52  
    53  	t.Run("testing NoopInitialEventsEndBookmarkTicker", func(t *testing.T) {
    54  		clock := testingclock.NewFakeClock(time.Now())
    55  		target := newInitialEventsEndBookmarkTickerInternal("testName", clock, clock.Now(), time.Second, false)
    56  
    57  		clock.Step(30 * time.Second)
    58  		assertNoEvents(t, target.C())
    59  		actualWarning := target.produceWarningIfExpired()
    60  
    61  		require.Empty(t, actualWarning, "didn't expect any warning")
    62  		// validate if the other methods don't produce panic
    63  		target.warnIfExpired()
    64  		target.observeLastEventTimeStamp(clock.Now())
    65  
    66  		// make sure that after calling the other methods
    67  		// nothing hasn't changed
    68  		actualWarning = target.produceWarningIfExpired()
    69  		require.Empty(t, actualWarning, "didn't expect any warning")
    70  		assertNoEvents(t, target.C())
    71  
    72  		target.Stop()
    73  	})
    74  
    75  	t.Run("testing InitialEventsEndBookmarkTicker backed by a fake clock", func(t *testing.T) {
    76  		clock := testingclock.NewFakeClock(time.Now())
    77  		target := newInitialEventsEndBookmarkTickerInternal("testName", clock, clock.Now(), time.Second, true)
    78  		clock.Step(500 * time.Millisecond)
    79  		assertNoEvents(t, target.C())
    80  
    81  		clock.Step(500 * time.Millisecond)
    82  		<-target.C()
    83  		actualWarning := target.produceWarningIfExpired()
    84  		require.Equal(t, errors.New("testName: awaiting required bookmark event for initial events stream, no events received for 1s"), actualWarning)
    85  
    86  		clock.Step(time.Second)
    87  		<-target.C()
    88  		actualWarning = target.produceWarningIfExpired()
    89  		require.Equal(t, errors.New("testName: awaiting required bookmark event for initial events stream, no events received for 2s"), actualWarning)
    90  
    91  		target.observeLastEventTimeStamp(clock.Now())
    92  		clock.Step(500 * time.Millisecond)
    93  		assertNoEvents(t, target.C())
    94  
    95  		clock.Step(500 * time.Millisecond)
    96  		<-target.C()
    97  		actualWarning = target.produceWarningIfExpired()
    98  		require.Equal(t, errors.New("testName: hasn't received required bookmark event marking the end of initial events stream, received last event 1s ago"), actualWarning)
    99  
   100  		clock.Step(time.Second)
   101  		<-target.C()
   102  		actualWarning = target.produceWarningIfExpired()
   103  		require.Equal(t, errors.New("testName: hasn't received required bookmark event marking the end of initial events stream, received last event 2s ago"), actualWarning)
   104  
   105  		target.Stop()
   106  		assertNoEvents(t, target.C())
   107  	})
   108  }
   109  
   110  func TestWatchList(t *testing.T) {
   111  	scenarios := []struct {
   112  		name                string
   113  		disableUseWatchList bool
   114  
   115  		// closes listWatcher after sending the specified number of watch events
   116  		closeAfterWatchEvents int
   117  		// closes listWatcher after getting the specified number of watch requests
   118  		closeAfterWatchRequests int
   119  		// closes listWatcher after getting the specified number of list requests
   120  		closeAfterListRequests int
   121  
   122  		// stops Watcher after sending the specified number of watch events
   123  		stopAfterWatchEvents int
   124  
   125  		watchOptionsPredicate func(options metav1.ListOptions) error
   126  		watchEvents           []watch.Event
   127  		podList               *v1.PodList
   128  
   129  		expectedRequestOptions []metav1.ListOptions
   130  		expectedWatchRequests  int
   131  		expectedListRequests   int
   132  		expectedStoreContent   []v1.Pod
   133  		expectedError          error
   134  	}{
   135  		{
   136  			name:                  "the reflector won't be synced if the bookmark event has been received",
   137  			watchEvents:           []watch.Event{{Type: watch.Added, Object: makePod("p1", "1")}},
   138  			closeAfterWatchEvents: 1,
   139  			expectedWatchRequests: 1,
   140  			expectedRequestOptions: []metav1.ListOptions{{
   141  				SendInitialEvents:    pointer.Bool(true),
   142  				AllowWatchBookmarks:  true,
   143  				ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   144  				TimeoutSeconds:       pointer.Int64(1),
   145  			}},
   146  		},
   147  		{
   148  			name:                    "the reflector uses the old LIST/WATCH semantics if the UseWatchList is turned off",
   149  			disableUseWatchList:     true,
   150  			closeAfterWatchRequests: 1,
   151  			podList: &v1.PodList{
   152  				ListMeta: metav1.ListMeta{ResourceVersion: "1"},
   153  				Items:    []v1.Pod{*makePod("p1", "1")},
   154  			},
   155  			expectedWatchRequests: 1,
   156  			expectedListRequests:  1,
   157  			expectedRequestOptions: []metav1.ListOptions{
   158  				{
   159  					ResourceVersion: "0",
   160  					Limit:           500,
   161  				},
   162  				{
   163  					AllowWatchBookmarks: true,
   164  					ResourceVersion:     "1",
   165  					TimeoutSeconds:      pointer.Int64(1),
   166  				}},
   167  			expectedStoreContent: []v1.Pod{*makePod("p1", "1")},
   168  		},
   169  		{
   170  			name: "returning any other error than apierrors.NewInvalid forces fallback",
   171  			watchOptionsPredicate: func(options metav1.ListOptions) error {
   172  				if options.SendInitialEvents != nil && *options.SendInitialEvents {
   173  					return fmt.Errorf("dummy error")
   174  				}
   175  				return nil
   176  			},
   177  			podList: &v1.PodList{
   178  				ListMeta: metav1.ListMeta{ResourceVersion: "1"},
   179  				Items:    []v1.Pod{*makePod("p1", "1")},
   180  			},
   181  			closeAfterWatchEvents: 1,
   182  			watchEvents:           []watch.Event{{Type: watch.Added, Object: makePod("p2", "2")}},
   183  			expectedWatchRequests: 2,
   184  			expectedListRequests:  1,
   185  			expectedStoreContent:  []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")},
   186  			expectedRequestOptions: []metav1.ListOptions{
   187  				{
   188  					SendInitialEvents:    pointer.Bool(true),
   189  					AllowWatchBookmarks:  true,
   190  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   191  					TimeoutSeconds:       pointer.Int64(1),
   192  				},
   193  				{
   194  					ResourceVersion: "0",
   195  					Limit:           500,
   196  				},
   197  				{
   198  					AllowWatchBookmarks: true,
   199  					ResourceVersion:     "1",
   200  					TimeoutSeconds:      pointer.Int64(1),
   201  				},
   202  			},
   203  		},
   204  		{
   205  			name: "the reflector can fall back to old LIST/WATCH semantics when a server doesn't support streaming",
   206  			watchOptionsPredicate: func(options metav1.ListOptions) error {
   207  				if options.SendInitialEvents != nil && *options.SendInitialEvents {
   208  					return apierrors.NewInvalid(schema.GroupKind{}, "streaming is not allowed", nil)
   209  				}
   210  				return nil
   211  			},
   212  			podList: &v1.PodList{
   213  				ListMeta: metav1.ListMeta{ResourceVersion: "1"},
   214  				Items:    []v1.Pod{*makePod("p1", "1")},
   215  			},
   216  			closeAfterWatchEvents: 1,
   217  			watchEvents:           []watch.Event{{Type: watch.Added, Object: makePod("p2", "2")}},
   218  			expectedWatchRequests: 2,
   219  			expectedListRequests:  1,
   220  			expectedStoreContent:  []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")},
   221  			expectedRequestOptions: []metav1.ListOptions{
   222  				{
   223  					SendInitialEvents:    pointer.Bool(true),
   224  					AllowWatchBookmarks:  true,
   225  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   226  					TimeoutSeconds:       pointer.Int64(1),
   227  				},
   228  				{
   229  					ResourceVersion: "0",
   230  					Limit:           500,
   231  				},
   232  				{
   233  					AllowWatchBookmarks: true,
   234  					ResourceVersion:     "1",
   235  					TimeoutSeconds:      pointer.Int64(1),
   236  				},
   237  			},
   238  		},
   239  		{
   240  			name:                  "prove that the reflector is synced after receiving a bookmark event",
   241  			closeAfterWatchEvents: 3,
   242  			watchEvents: []watch.Event{
   243  				{Type: watch.Added, Object: makePod("p1", "1")},
   244  				{Type: watch.Added, Object: makePod("p2", "2")},
   245  				{Type: watch.Bookmark, Object: &v1.Pod{
   246  					ObjectMeta: metav1.ObjectMeta{
   247  						ResourceVersion: "2",
   248  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   249  					},
   250  				}},
   251  			},
   252  			expectedWatchRequests: 1,
   253  			expectedRequestOptions: []metav1.ListOptions{{
   254  				SendInitialEvents:    pointer.Bool(true),
   255  				AllowWatchBookmarks:  true,
   256  				ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   257  				TimeoutSeconds:       pointer.Int64(1),
   258  			}},
   259  			expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2")},
   260  		},
   261  		{
   262  			name:                  "check if Updates and Deletes events are propagated during streaming (until the bookmark is received)",
   263  			closeAfterWatchEvents: 6,
   264  			watchEvents: []watch.Event{
   265  				{Type: watch.Added, Object: makePod("p1", "1")},
   266  				{Type: watch.Added, Object: makePod("p2", "2")},
   267  				{Type: watch.Modified, Object: func() runtime.Object {
   268  					p1 := makePod("p1", "3")
   269  					p1.Spec.ActiveDeadlineSeconds = pointer.Int64(12)
   270  					return p1
   271  				}()},
   272  				{Type: watch.Added, Object: makePod("p3", "4")},
   273  				{Type: watch.Deleted, Object: makePod("p3", "5")},
   274  				{Type: watch.Bookmark, Object: &v1.Pod{
   275  					ObjectMeta: metav1.ObjectMeta{
   276  						ResourceVersion: "5",
   277  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   278  					},
   279  				}},
   280  			},
   281  			expectedWatchRequests: 1,
   282  			expectedRequestOptions: []metav1.ListOptions{{
   283  				SendInitialEvents:    pointer.Bool(true),
   284  				AllowWatchBookmarks:  true,
   285  				ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   286  				TimeoutSeconds:       pointer.Int64(1),
   287  			}},
   288  			expectedStoreContent: []v1.Pod{
   289  				*makePod("p2", "2"),
   290  				func() v1.Pod {
   291  					p1 := *makePod("p1", "3")
   292  					p1.Spec.ActiveDeadlineSeconds = pointer.Int64(12)
   293  					return p1
   294  				}(),
   295  			},
   296  		},
   297  		{
   298  			name: "checks if the reflector retries 429",
   299  			watchOptionsPredicate: func() func(options metav1.ListOptions) error {
   300  				counter := 1
   301  				return func(options metav1.ListOptions) error {
   302  					if counter < 3 {
   303  						counter++
   304  						return apierrors.NewTooManyRequests("busy, check again later", 1)
   305  					}
   306  					return nil
   307  				}
   308  			}(),
   309  			closeAfterWatchEvents: 2,
   310  			watchEvents: []watch.Event{
   311  				{Type: watch.Added, Object: makePod("p1", "1")},
   312  				{Type: watch.Bookmark, Object: &v1.Pod{
   313  					ObjectMeta: metav1.ObjectMeta{
   314  						ResourceVersion: "2",
   315  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   316  					},
   317  				}},
   318  			},
   319  			expectedWatchRequests: 3,
   320  			expectedRequestOptions: []metav1.ListOptions{
   321  				{
   322  					SendInitialEvents:    pointer.Bool(true),
   323  					AllowWatchBookmarks:  true,
   324  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   325  					TimeoutSeconds:       pointer.Int64(1),
   326  				},
   327  				{
   328  					SendInitialEvents:    pointer.Bool(true),
   329  					AllowWatchBookmarks:  true,
   330  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   331  					TimeoutSeconds:       pointer.Int64(1),
   332  				},
   333  				{
   334  					SendInitialEvents:    pointer.Bool(true),
   335  					AllowWatchBookmarks:  true,
   336  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   337  					TimeoutSeconds:       pointer.Int64(1),
   338  				},
   339  			},
   340  			expectedStoreContent: []v1.Pod{*makePod("p1", "1")},
   341  		},
   342  		{
   343  			name:                  "check if stopping a watcher before sync results in creating a new watch-list request",
   344  			stopAfterWatchEvents:  1,
   345  			closeAfterWatchEvents: 3,
   346  			watchEvents: []watch.Event{
   347  				{Type: watch.Added, Object: makePod("p1", "1")},
   348  				// second request
   349  				{Type: watch.Added, Object: makePod("p1", "1")},
   350  				{Type: watch.Bookmark, Object: &v1.Pod{
   351  					ObjectMeta: metav1.ObjectMeta{
   352  						ResourceVersion: "1",
   353  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   354  					},
   355  				}},
   356  			},
   357  			expectedWatchRequests: 2,
   358  			expectedRequestOptions: []metav1.ListOptions{
   359  				{
   360  					SendInitialEvents:    pointer.Bool(true),
   361  					AllowWatchBookmarks:  true,
   362  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   363  					TimeoutSeconds:       pointer.Int64(1),
   364  				},
   365  				{
   366  					SendInitialEvents:    pointer.Bool(true),
   367  					AllowWatchBookmarks:  true,
   368  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   369  					TimeoutSeconds:       pointer.Int64(1),
   370  				},
   371  			},
   372  			expectedStoreContent: []v1.Pod{*makePod("p1", "1")},
   373  		},
   374  		{
   375  			name:                  "stopping a watcher after synchronization results in creating a new watch request",
   376  			stopAfterWatchEvents:  4,
   377  			closeAfterWatchEvents: 5,
   378  			watchEvents: []watch.Event{
   379  				{Type: watch.Added, Object: makePod("p1", "1")},
   380  				{Type: watch.Added, Object: makePod("p2", "2")},
   381  				{Type: watch.Bookmark, Object: &v1.Pod{
   382  					ObjectMeta: metav1.ObjectMeta{
   383  						ResourceVersion: "2",
   384  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   385  					},
   386  				}},
   387  				{Type: watch.Added, Object: makePod("p3", "3")},
   388  				// second request
   389  				{Type: watch.Added, Object: makePod("p4", "4")},
   390  			},
   391  			expectedWatchRequests: 2,
   392  			expectedRequestOptions: []metav1.ListOptions{
   393  				{
   394  					SendInitialEvents:    pointer.Bool(true),
   395  					AllowWatchBookmarks:  true,
   396  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   397  					TimeoutSeconds:       pointer.Int64(1),
   398  				},
   399  				{
   400  					AllowWatchBookmarks: true,
   401  					ResourceVersion:     "3",
   402  					TimeoutSeconds:      pointer.Int64(1),
   403  				},
   404  			},
   405  			expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p2", "2"), *makePod("p3", "3"), *makePod("p4", "4")},
   406  		},
   407  		{
   408  			name: "expiring an established watcher results in returning an error from the reflector",
   409  			watchOptionsPredicate: func() func(options metav1.ListOptions) error {
   410  				counter := 0
   411  				return func(options metav1.ListOptions) error {
   412  					counter++
   413  					if counter == 2 {
   414  						return apierrors.NewResourceExpired("rv already expired")
   415  					}
   416  					return nil
   417  				}
   418  			}(),
   419  			stopAfterWatchEvents: 3,
   420  			watchEvents: []watch.Event{
   421  				{Type: watch.Added, Object: makePod("p1", "1")},
   422  				{Type: watch.Bookmark, Object: &v1.Pod{
   423  					ObjectMeta: metav1.ObjectMeta{
   424  						ResourceVersion: "2",
   425  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "true"},
   426  					},
   427  				}},
   428  				{Type: watch.Added, Object: makePod("p3", "3")},
   429  			},
   430  			expectedWatchRequests: 2,
   431  			expectedRequestOptions: []metav1.ListOptions{
   432  				{
   433  					SendInitialEvents:    pointer.Bool(true),
   434  					AllowWatchBookmarks:  true,
   435  					ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   436  					TimeoutSeconds:       pointer.Int64(1),
   437  				},
   438  				{
   439  					AllowWatchBookmarks: true,
   440  					ResourceVersion:     "3",
   441  					TimeoutSeconds:      pointer.Int64(1),
   442  				},
   443  			},
   444  			expectedStoreContent: []v1.Pod{*makePod("p1", "1"), *makePod("p3", "3")},
   445  			expectedError:        apierrors.NewResourceExpired("rv already expired"),
   446  		},
   447  		{
   448  			name:                  "prove that the reflector is checking the value of the initialEventsEnd annotation",
   449  			closeAfterWatchEvents: 3,
   450  			watchEvents: []watch.Event{
   451  				{Type: watch.Added, Object: makePod("p1", "1")},
   452  				{Type: watch.Added, Object: makePod("p2", "2")},
   453  				{Type: watch.Bookmark, Object: &v1.Pod{
   454  					ObjectMeta: metav1.ObjectMeta{
   455  						ResourceVersion: "2",
   456  						Annotations:     map[string]string{metav1.InitialEventsAnnotationKey: "false"},
   457  					},
   458  				}},
   459  			},
   460  			expectedWatchRequests: 1,
   461  			expectedRequestOptions: []metav1.ListOptions{{
   462  				SendInitialEvents:    pointer.Bool(true),
   463  				AllowWatchBookmarks:  true,
   464  				ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan,
   465  				TimeoutSeconds:       pointer.Int64(1),
   466  			}},
   467  		},
   468  	}
   469  	for _, s := range scenarios {
   470  		t.Run(s.name, func(t *testing.T) {
   471  			scenario := s // capture as local variable
   472  			listWatcher, store, reflector, stopCh := testData()
   473  			go func() {
   474  				for i, e := range scenario.watchEvents {
   475  					listWatcher.fakeWatcher.Action(e.Type, e.Object)
   476  					if i+1 == scenario.stopAfterWatchEvents {
   477  						listWatcher.StopAndRecreateWatch()
   478  						continue
   479  					}
   480  					if i+1 == scenario.closeAfterWatchEvents {
   481  						close(stopCh)
   482  					}
   483  				}
   484  			}()
   485  			listWatcher.watchOptionsPredicate = scenario.watchOptionsPredicate
   486  			listWatcher.closeAfterWatchRequests = scenario.closeAfterWatchRequests
   487  			listWatcher.customListResponse = scenario.podList
   488  			listWatcher.closeAfterListRequests = scenario.closeAfterListRequests
   489  			if scenario.disableUseWatchList {
   490  				reflector.UseWatchList = ptr.To(false)
   491  			}
   492  
   493  			err := reflector.ListAndWatch(stopCh)
   494  			if scenario.expectedError != nil && err == nil {
   495  				t.Fatalf("expected error %q, got nil", scenario.expectedError)
   496  			}
   497  			if scenario.expectedError == nil && err != nil {
   498  				t.Fatalf("unexpected error: %v", err)
   499  			}
   500  			if scenario.expectedError != nil && err.Error() != scenario.expectedError.Error() {
   501  				t.Fatalf("expected error %q, got %q", scenario.expectedError, err.Error())
   502  			}
   503  
   504  			verifyWatchCounter(t, listWatcher, scenario.expectedWatchRequests)
   505  			verifyListCounter(t, listWatcher, scenario.expectedListRequests)
   506  			verifyRequestOptions(t, listWatcher, scenario.expectedRequestOptions)
   507  			verifyStore(t, store, scenario.expectedStoreContent)
   508  		})
   509  	}
   510  }
   511  
   512  func verifyRequestOptions(t *testing.T, lw *fakeListWatcher, expectedRequestOptions []metav1.ListOptions) {
   513  	if len(lw.requestOptions) != len(expectedRequestOptions) {
   514  		t.Fatalf("expected to receive exactly %v requests, got %v", len(expectedRequestOptions), len(lw.requestOptions))
   515  	}
   516  
   517  	for index, expectedRequestOption := range expectedRequestOptions {
   518  		actualRequestOption := lw.requestOptions[index]
   519  		if actualRequestOption.TimeoutSeconds == nil && expectedRequestOption.TimeoutSeconds != nil {
   520  			t.Fatalf("expected the request to specify TimeoutSeconds option but it didn't, actual = %#v, expected = %#v", actualRequestOption, expectedRequestOption)
   521  		}
   522  		if actualRequestOption.TimeoutSeconds != nil && expectedRequestOption.TimeoutSeconds == nil {
   523  			t.Fatalf("unexpected TimeoutSeconds option specified, actual = %#v, expected = %#v", actualRequestOption, expectedRequestOption)
   524  		}
   525  		// ignore actual values
   526  		actualRequestOption.TimeoutSeconds = nil
   527  		expectedRequestOption.TimeoutSeconds = nil
   528  		if !cmp.Equal(actualRequestOption, expectedRequestOption) {
   529  			t.Fatalf("expected %#v, got %#v", expectedRequestOption, actualRequestOption)
   530  		}
   531  	}
   532  }
   533  
   534  func verifyListCounter(t *testing.T, lw *fakeListWatcher, expectedListCounter int) {
   535  	if lw.listCounter != expectedListCounter {
   536  		t.Fatalf("unexpected number of LIST requests, got: %v, expected: %v", lw.listCounter, expectedListCounter)
   537  	}
   538  }
   539  
   540  func verifyWatchCounter(t *testing.T, lw *fakeListWatcher, expectedWatchCounter int) {
   541  	if lw.watchCounter != expectedWatchCounter {
   542  		t.Fatalf("unexpected number of WATCH requests, got: %v, expected: %v", lw.watchCounter, expectedWatchCounter)
   543  	}
   544  }
   545  
   546  type byName []v1.Pod
   547  
   548  func (a byName) Len() int           { return len(a) }
   549  func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name }
   550  func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   551  
   552  func verifyStore(t *testing.T, s Store, expectedPods []v1.Pod) {
   553  	rawPods := s.List()
   554  	actualPods := []v1.Pod{}
   555  	for _, p := range rawPods {
   556  		actualPods = append(actualPods, *p.(*v1.Pod))
   557  	}
   558  
   559  	sort.Sort(byName(actualPods))
   560  	sort.Sort(byName(expectedPods))
   561  	if !cmp.Equal(actualPods, expectedPods, cmpopts.EquateEmpty()) {
   562  		t.Fatalf("unexpected store content, diff: %s", cmp.Diff(actualPods, expectedPods))
   563  	}
   564  }
   565  
   566  func makePod(name, rv string) *v1.Pod {
   567  	return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: rv, UID: types.UID(name)}}
   568  }
   569  
   570  func testData() (*fakeListWatcher, Store, *Reflector, chan struct{}) {
   571  	s := NewStore(MetaNamespaceKeyFunc)
   572  	stopCh := make(chan struct{})
   573  	lw := &fakeListWatcher{
   574  		fakeWatcher: watch.NewFake(),
   575  		stop: func() {
   576  			close(stopCh)
   577  		},
   578  	}
   579  	r := NewReflector(lw, &v1.Pod{}, s, 0)
   580  	r.UseWatchList = ptr.To(true)
   581  
   582  	return lw, s, r, stopCh
   583  }
   584  
   585  type fakeListWatcher struct {
   586  	lock                    sync.Mutex
   587  	fakeWatcher             *watch.FakeWatcher
   588  	listCounter             int
   589  	watchCounter            int
   590  	closeAfterWatchRequests int
   591  	closeAfterListRequests  int
   592  	stop                    func()
   593  
   594  	requestOptions []metav1.ListOptions
   595  
   596  	customListResponse    *v1.PodList
   597  	watchOptionsPredicate func(options metav1.ListOptions) error
   598  }
   599  
   600  func (lw *fakeListWatcher) List(options metav1.ListOptions) (runtime.Object, error) {
   601  	lw.listCounter++
   602  	lw.requestOptions = append(lw.requestOptions, options)
   603  	if lw.listCounter == lw.closeAfterListRequests {
   604  		lw.stop()
   605  	}
   606  	if lw.customListResponse != nil {
   607  		return lw.customListResponse, nil
   608  	}
   609  	return nil, fmt.Errorf("not implemented")
   610  }
   611  
   612  func (lw *fakeListWatcher) Watch(options metav1.ListOptions) (watch.Interface, error) {
   613  	lw.watchCounter++
   614  	lw.requestOptions = append(lw.requestOptions, options)
   615  	if lw.watchCounter == lw.closeAfterWatchRequests {
   616  		lw.stop()
   617  	}
   618  	if lw.watchOptionsPredicate != nil {
   619  		if err := lw.watchOptionsPredicate(options); err != nil {
   620  			return nil, err
   621  		}
   622  	}
   623  	lw.lock.Lock()
   624  	defer lw.lock.Unlock()
   625  	return lw.fakeWatcher, nil
   626  }
   627  
   628  func (lw *fakeListWatcher) StopAndRecreateWatch() {
   629  	lw.lock.Lock()
   630  	defer lw.lock.Unlock()
   631  	lw.fakeWatcher.Stop()
   632  	lw.fakeWatcher = watch.NewFake()
   633  }