knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/controller/controller_test.go (about)

     1  /*
     2  Copyright 2017 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 controller
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"sync"
    24  	"sync/atomic"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  
    30  	coordinationv1 "k8s.io/api/coordination/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	fakekube "k8s.io/client-go/kubernetes/fake"
    36  	"k8s.io/client-go/tools/cache"
    37  	"k8s.io/client-go/tools/record"
    38  	"k8s.io/client-go/util/workqueue"
    39  
    40  	"knative.dev/pkg/leaderelection"
    41  	"knative.dev/pkg/ptr"
    42  	"knative.dev/pkg/reconciler"
    43  	"knative.dev/pkg/system"
    44  
    45  	. "knative.dev/pkg/logging/testing"
    46  	_ "knative.dev/pkg/system/testing"
    47  	. "knative.dev/pkg/testing"
    48  )
    49  
    50  const (
    51  	oldObj = "foo"
    52  	newObj = "bar"
    53  )
    54  
    55  func TestPassNew(t *testing.T) {
    56  	PassNew(func(got interface{}) {
    57  		if newObj != got.(string) {
    58  			t.Errorf("PassNew() = %v, wanted %v", got, newObj)
    59  		}
    60  	})(oldObj, newObj)
    61  }
    62  
    63  func TestHandleAll(t *testing.T) {
    64  	ha := HandleAll(func(got interface{}) {
    65  		if newObj != got.(string) {
    66  			t.Errorf("HandleAll() = %v, wanted %v", got, newObj)
    67  		}
    68  	})
    69  
    70  	ha.OnAdd(newObj, false)
    71  	ha.OnUpdate(oldObj, newObj)
    72  	ha.OnDelete(newObj)
    73  }
    74  
    75  var gvk = schema.GroupVersionKind{
    76  	Group:   "pkg.knative.dev",
    77  	Version: "v1meta1",
    78  	Kind:    "Parent",
    79  }
    80  
    81  func TestFilterWithNameAndNamespace(t *testing.T) {
    82  	filter := FilterWithNameAndNamespace("test-namespace", "test-name")
    83  
    84  	tests := []struct {
    85  		name  string
    86  		input interface{}
    87  		want  bool
    88  	}{{
    89  		name:  "not a metav1.Object",
    90  		input: "foo",
    91  	}, {
    92  		name:  "nil",
    93  		input: nil,
    94  	}, {
    95  		name: "name matches, namespace does not",
    96  		input: &Resource{
    97  			ObjectMeta: metav1.ObjectMeta{
    98  				Name:      "test-name",
    99  				Namespace: "wrong-namespace",
   100  			},
   101  		},
   102  	}, {
   103  		name: "namespace matches, name does not",
   104  		input: &Resource{
   105  			ObjectMeta: metav1.ObjectMeta{
   106  				Name:      "wrong-name",
   107  				Namespace: "test-namespace",
   108  			},
   109  		},
   110  	}, {
   111  		name: "neither matches",
   112  		input: &Resource{
   113  			ObjectMeta: metav1.ObjectMeta{
   114  				Name:      "wrong-name",
   115  				Namespace: "wrong-namespace",
   116  			},
   117  		},
   118  	}, {
   119  		name: "matches",
   120  		input: &Resource{
   121  			ObjectMeta: metav1.ObjectMeta{
   122  				Name:      "test-name",
   123  				Namespace: "test-namespace",
   124  			},
   125  		},
   126  		want: true,
   127  	}}
   128  
   129  	for _, test := range tests {
   130  		t.Run(test.name, func(t *testing.T) {
   131  			got := filter(test.input)
   132  			if test.want != got {
   133  				t.Errorf("FilterWithNameAndNamespace() = %v, wanted %v", got, test.want)
   134  			}
   135  		})
   136  	}
   137  }
   138  
   139  func TestFilterWithName(t *testing.T) {
   140  	filter := FilterWithName("test-name")
   141  
   142  	tests := []struct {
   143  		name  string
   144  		input interface{}
   145  		want  bool
   146  	}{{
   147  		name:  "not a metav1.Object",
   148  		input: "foo",
   149  	}, {
   150  		name:  "nil",
   151  		input: nil,
   152  	}, {
   153  		name: "name matches, namespace does not",
   154  		input: &Resource{
   155  			ObjectMeta: metav1.ObjectMeta{
   156  				Name:      "test-name",
   157  				Namespace: "wrong-namespace",
   158  			},
   159  		},
   160  		want: true, // Unlike FilterWithNameAndNamespace this passes
   161  	}, {
   162  		name: "namespace matches, name does not",
   163  		input: &Resource{
   164  			ObjectMeta: metav1.ObjectMeta{
   165  				Name:      "wrong-name",
   166  				Namespace: "test-namespace",
   167  			},
   168  		},
   169  	}, {
   170  		name: "neither matches",
   171  		input: &Resource{
   172  			ObjectMeta: metav1.ObjectMeta{
   173  				Name:      "wrong-name",
   174  				Namespace: "wrong-namespace",
   175  			},
   176  		},
   177  	}, {
   178  		name: "matches",
   179  		input: &Resource{
   180  			ObjectMeta: metav1.ObjectMeta{
   181  				Name:      "test-name",
   182  				Namespace: "test-namespace",
   183  			},
   184  		},
   185  		want: true,
   186  	}}
   187  
   188  	for _, test := range tests {
   189  		t.Run(test.name, func(t *testing.T) {
   190  			got := filter(test.input)
   191  			if test.want != got {
   192  				t.Errorf("FilterWithNameAndNamespace() = %v, wanted %v", got, test.want)
   193  			}
   194  		})
   195  	}
   196  }
   197  
   198  func TestFilterGroupKind(t *testing.T) {
   199  	filter := FilterGroupKind(gvk.GroupKind())
   200  
   201  	tests := []struct {
   202  		name  string
   203  		input interface{}
   204  		want  bool
   205  	}{{
   206  		name:  "not a metav1.Object",
   207  		input: "foo",
   208  	}, {
   209  		name:  "nil",
   210  		input: nil,
   211  	}, {
   212  		name: "no owner reference",
   213  		input: &Resource{
   214  			ObjectMeta: metav1.ObjectMeta{
   215  				Name:      "foo",
   216  				Namespace: "bar",
   217  			},
   218  		},
   219  	}, {
   220  		name: "wrong owner reference, not controller",
   221  		input: &Resource{
   222  			ObjectMeta: metav1.ObjectMeta{
   223  				Name:      "foo",
   224  				Namespace: "bar",
   225  				OwnerReferences: []metav1.OwnerReference{{
   226  					APIVersion: "another.knative.dev/v1beta3",
   227  					Kind:       "Parent",
   228  					Controller: ptr.Bool(false),
   229  				}},
   230  			},
   231  		},
   232  	}, {
   233  		name: "right owner reference, not controller",
   234  		input: &Resource{
   235  			ObjectMeta: metav1.ObjectMeta{
   236  				Name:      "foo",
   237  				Namespace: "bar",
   238  				OwnerReferences: []metav1.OwnerReference{{
   239  					APIVersion: gvk.GroupVersion().String(),
   240  					Kind:       gvk.Kind,
   241  					Controller: ptr.Bool(false),
   242  				}},
   243  			},
   244  		},
   245  	}, {
   246  		name: "wrong owner reference, but controller",
   247  		input: &Resource{
   248  			ObjectMeta: metav1.ObjectMeta{
   249  				Name:      "foo",
   250  				Namespace: "bar",
   251  				OwnerReferences: []metav1.OwnerReference{{
   252  					APIVersion: "another.knative.dev/v1beta3",
   253  					Kind:       "Parent",
   254  					Controller: ptr.Bool(true),
   255  				}},
   256  			},
   257  		},
   258  		want: false,
   259  	}, {
   260  		name: "right owner reference, is controller",
   261  		input: &Resource{
   262  			ObjectMeta: metav1.ObjectMeta{
   263  				Name:      "foo",
   264  				Namespace: "bar",
   265  				OwnerReferences: []metav1.OwnerReference{{
   266  					APIVersion: gvk.GroupVersion().String(),
   267  					Kind:       gvk.Kind,
   268  					Controller: ptr.Bool(true),
   269  				}},
   270  			},
   271  		},
   272  		want: true,
   273  	}, {
   274  		name: "right owner reference, is controller, different version",
   275  		input: &Resource{
   276  			ObjectMeta: metav1.ObjectMeta{
   277  				Name:      "foo",
   278  				Namespace: "bar",
   279  				OwnerReferences: []metav1.OwnerReference{{
   280  					APIVersion: schema.GroupVersion{Group: gvk.Group, Version: "other"}.String(),
   281  					Kind:       gvk.Kind,
   282  					Controller: ptr.Bool(true),
   283  				}},
   284  			},
   285  		},
   286  		want: true,
   287  	}}
   288  
   289  	for _, test := range tests {
   290  		t.Run(test.name, func(t *testing.T) {
   291  			got := filter(test.input)
   292  			if test.want != got {
   293  				t.Errorf("Filter() = %v, wanted %v", got, test.want)
   294  			}
   295  		})
   296  	}
   297  }
   298  
   299  func TestFilterGroupVersionKind(t *testing.T) {
   300  	filter := FilterGroupVersionKind(gvk)
   301  
   302  	tests := []struct {
   303  		name  string
   304  		input interface{}
   305  		want  bool
   306  	}{{
   307  		name:  "not a metav1.Object",
   308  		input: "foo",
   309  	}, {
   310  		name:  "nil",
   311  		input: nil,
   312  	}, {
   313  		name: "no owner reference",
   314  		input: &Resource{
   315  			ObjectMeta: metav1.ObjectMeta{
   316  				Name:      "foo",
   317  				Namespace: "bar",
   318  			},
   319  		},
   320  	}, {
   321  		name: "wrong owner reference, not controller",
   322  		input: &Resource{
   323  			ObjectMeta: metav1.ObjectMeta{
   324  				Name:      "foo",
   325  				Namespace: "bar",
   326  				OwnerReferences: []metav1.OwnerReference{{
   327  					APIVersion: "another.knative.dev/v1beta3",
   328  					Kind:       "Parent",
   329  					Controller: ptr.Bool(false),
   330  				}},
   331  			},
   332  		},
   333  	}, {
   334  		name: "right owner reference, not controller",
   335  		input: &Resource{
   336  			ObjectMeta: metav1.ObjectMeta{
   337  				Name:      "foo",
   338  				Namespace: "bar",
   339  				OwnerReferences: []metav1.OwnerReference{{
   340  					APIVersion: gvk.GroupVersion().String(),
   341  					Kind:       gvk.Kind,
   342  					Controller: ptr.Bool(false),
   343  				}},
   344  			},
   345  		},
   346  	}, {
   347  		name: "wrong owner reference, but controller",
   348  		input: &Resource{
   349  			ObjectMeta: metav1.ObjectMeta{
   350  				Name:      "foo",
   351  				Namespace: "bar",
   352  				OwnerReferences: []metav1.OwnerReference{{
   353  					APIVersion: "another.knative.dev/v1beta3",
   354  					Kind:       "Parent",
   355  					Controller: ptr.Bool(true),
   356  				}},
   357  			},
   358  		},
   359  	}, {
   360  		name: "right owner reference, is controller",
   361  		input: &Resource{
   362  			ObjectMeta: metav1.ObjectMeta{
   363  				Name:      "foo",
   364  				Namespace: "bar",
   365  				OwnerReferences: []metav1.OwnerReference{{
   366  					APIVersion: gvk.GroupVersion().String(),
   367  					Kind:       gvk.Kind,
   368  					Controller: ptr.Bool(true),
   369  				}},
   370  			},
   371  		},
   372  		want: true,
   373  	}, {
   374  		name: "right owner reference, is controller, wrong version",
   375  		input: &Resource{
   376  			ObjectMeta: metav1.ObjectMeta{
   377  				Name:      "foo",
   378  				Namespace: "bar",
   379  				OwnerReferences: []metav1.OwnerReference{{
   380  					APIVersion: schema.GroupVersion{Group: gvk.Group, Version: "other"}.String(),
   381  					Kind:       gvk.Kind,
   382  					Controller: ptr.Bool(true),
   383  				}},
   384  			},
   385  		},
   386  	}}
   387  
   388  	for _, test := range tests {
   389  		t.Run(test.name, func(t *testing.T) {
   390  			got := filter(test.input)
   391  			if test.want != got {
   392  				t.Errorf("Filter() = %v, wanted %v", got, test.want)
   393  			}
   394  		})
   395  	}
   396  }
   397  
   398  type nopReconciler struct{}
   399  
   400  func (nr *nopReconciler) Reconcile(context.Context, string) error {
   401  	return nil
   402  }
   403  
   404  type testRateLimiter struct {
   405  	t     *testing.T
   406  	delay time.Duration
   407  }
   408  
   409  func (t testRateLimiter) When(interface{}) time.Duration { return t.delay }
   410  func (t testRateLimiter) Forget(interface{})             {}
   411  func (t testRateLimiter) NumRequeues(interface{}) int    { return 0 }
   412  
   413  var _ workqueue.TypedRateLimiter[any] = (*testRateLimiter)(nil)
   414  
   415  func TestEnqueue(t *testing.T) {
   416  	tests := []struct {
   417  		name      string
   418  		work      func(*Impl)
   419  		wantQueue []types.NamespacedName
   420  	}{{
   421  		name: "do nothing",
   422  		work: func(*Impl) {},
   423  	}, {
   424  		name: "enqueue key",
   425  		work: func(impl *Impl) {
   426  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
   427  		},
   428  		wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}},
   429  	}, {
   430  		name: "enqueue duplicate key",
   431  		work: func(impl *Impl) {
   432  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
   433  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
   434  		},
   435  		// The queue deduplicates.
   436  		wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}},
   437  	}, {
   438  		name: "enqueue different keys",
   439  		work: func(impl *Impl) {
   440  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
   441  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "baz"})
   442  		},
   443  		wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}, {Namespace: "foo", Name: "baz"}},
   444  	}, {
   445  		name: "enqueue resource",
   446  		work: func(impl *Impl) {
   447  			impl.Enqueue(&Resource{
   448  				ObjectMeta: metav1.ObjectMeta{
   449  					Name:      "foo",
   450  					Namespace: "bar",
   451  				},
   452  			})
   453  		},
   454  		wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "foo"}},
   455  	}, {
   456  		name: "enqueue resource slow",
   457  		work: func(impl *Impl) {
   458  			impl.EnqueueSlow(&Resource{
   459  				ObjectMeta: metav1.ObjectMeta{
   460  					Name:      "foo",
   461  					Namespace: "bar",
   462  				},
   463  			})
   464  		},
   465  		wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "foo"}},
   466  	}, {
   467  		name: "enqueue sentinel resource",
   468  		work: func(impl *Impl) {
   469  			e := impl.EnqueueSentinel(types.NamespacedName{Namespace: "foo", Name: "bar"})
   470  			e(&Resource{
   471  				ObjectMeta: metav1.ObjectMeta{
   472  					Namespace: "foo",
   473  					Name:      "baz",
   474  				},
   475  			})
   476  		},
   477  		wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}},
   478  	}, {
   479  		name: "enqueue duplicate sentinel resource",
   480  		work: func(impl *Impl) {
   481  			e := impl.EnqueueSentinel(types.NamespacedName{Namespace: "foo", Name: "bar"})
   482  			e(&Resource{
   483  				ObjectMeta: metav1.ObjectMeta{
   484  					Namespace: "foo",
   485  					Name:      "baz-1",
   486  				},
   487  			})
   488  			e(&Resource{
   489  				ObjectMeta: metav1.ObjectMeta{
   490  					Namespace: "foo",
   491  					Name:      "baz-2",
   492  				},
   493  			})
   494  		},
   495  		wantQueue: []types.NamespacedName{{Namespace: "foo", Name: "bar"}},
   496  	}, {
   497  		name: "enqueue bad resource",
   498  		work: func(impl *Impl) {
   499  			impl.Enqueue("baz/blah")
   500  		},
   501  	}, {
   502  		name: "enqueue controller of bad resource",
   503  		work: func(impl *Impl) {
   504  			impl.EnqueueControllerOf("baz/blah")
   505  		},
   506  	}, {
   507  		name: "enqueue controller of resource without owner",
   508  		work: func(impl *Impl) {
   509  			impl.EnqueueControllerOf(&Resource{
   510  				ObjectMeta: metav1.ObjectMeta{
   511  					Name:      "foo",
   512  					Namespace: "bar",
   513  				},
   514  			})
   515  		},
   516  	}, {
   517  		name: "enqueue controller of resource with owner",
   518  		work: func(impl *Impl) {
   519  			impl.EnqueueControllerOf(&Resource{
   520  				ObjectMeta: metav1.ObjectMeta{
   521  					Name:      "foo",
   522  					Namespace: "bar",
   523  					OwnerReferences: []metav1.OwnerReference{{
   524  						APIVersion: gvk.GroupVersion().String(),
   525  						Kind:       gvk.Kind,
   526  						Name:       "baz",
   527  						Controller: ptr.Bool(true),
   528  					}},
   529  				},
   530  			})
   531  		},
   532  		wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}},
   533  	}, {
   534  		name: "enqueue controller of deleted resource with owner",
   535  		work: func(impl *Impl) {
   536  			impl.EnqueueControllerOf(cache.DeletedFinalStateUnknown{
   537  				Key: "foo/bar",
   538  				Obj: &Resource{
   539  					ObjectMeta: metav1.ObjectMeta{
   540  						Name:      "foo",
   541  						Namespace: "bar",
   542  						OwnerReferences: []metav1.OwnerReference{{
   543  							APIVersion: gvk.GroupVersion().String(),
   544  							Kind:       gvk.Kind,
   545  							Name:       "baz",
   546  							Controller: ptr.Bool(true),
   547  						}},
   548  					},
   549  				},
   550  			})
   551  		},
   552  		wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}},
   553  	}, {
   554  		name: "enqueue controller of deleted bad resource",
   555  		work: func(impl *Impl) {
   556  			impl.EnqueueControllerOf(cache.DeletedFinalStateUnknown{
   557  				Key: "foo/bar",
   558  				Obj: "bad-resource",
   559  			})
   560  		},
   561  	}, {
   562  		name: "enqueue label of namespaced resource bad resource",
   563  		work: func(impl *Impl) {
   564  			impl.EnqueueLabelOfNamespaceScopedResource("test-ns", "test-name")("baz/blah")
   565  		},
   566  	}, {
   567  		name: "enqueue label of namespaced resource without label",
   568  		work: func(impl *Impl) {
   569  			impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{
   570  				ObjectMeta: metav1.ObjectMeta{
   571  					Name:      "foo",
   572  					Namespace: "bar",
   573  					Labels: map[string]string{
   574  						"ns-key": "bar",
   575  					},
   576  				},
   577  			})
   578  		},
   579  	}, {
   580  		name: "enqueue label of namespaced resource without namespace label",
   581  		work: func(impl *Impl) {
   582  			impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{
   583  				ObjectMeta: metav1.ObjectMeta{
   584  					Name:      "foo",
   585  					Namespace: "bar",
   586  					Labels: map[string]string{
   587  						"name-key": "baz",
   588  					},
   589  				},
   590  			})
   591  		},
   592  	}, {
   593  		name: "enqueue label of namespaced resource with labels",
   594  		work: func(impl *Impl) {
   595  			impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(&Resource{
   596  				ObjectMeta: metav1.ObjectMeta{
   597  					Name:      "foo",
   598  					Namespace: "bar",
   599  					Labels: map[string]string{
   600  						"ns-key":   "qux",
   601  						"name-key": "baz",
   602  					},
   603  				},
   604  			})
   605  		},
   606  		wantQueue: []types.NamespacedName{{Namespace: "qux", Name: "baz"}},
   607  	}, {
   608  		name: "enqueue label of namespaced resource with empty namespace label",
   609  		work: func(impl *Impl) {
   610  			impl.EnqueueLabelOfNamespaceScopedResource("", "name-key")(&Resource{
   611  				ObjectMeta: metav1.ObjectMeta{
   612  					Name:      "foo",
   613  					Namespace: "bar",
   614  					Labels: map[string]string{
   615  						"name-key": "baz",
   616  					},
   617  				},
   618  			})
   619  		},
   620  		wantQueue: []types.NamespacedName{{Namespace: "bar", Name: "baz"}},
   621  	}, {
   622  		name: "enqueue label of deleted namespaced resource with label",
   623  		work: func(impl *Impl) {
   624  			impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(cache.DeletedFinalStateUnknown{
   625  				Key: "foo/bar",
   626  				Obj: &Resource{
   627  					ObjectMeta: metav1.ObjectMeta{
   628  						Name:      "foo",
   629  						Namespace: "bar",
   630  						Labels: map[string]string{
   631  							"ns-key":   "qux",
   632  							"name-key": "baz",
   633  						},
   634  					},
   635  				},
   636  			})
   637  		},
   638  		wantQueue: []types.NamespacedName{{Namespace: "qux", Name: "baz"}},
   639  	}, {
   640  		name: "enqueue label of deleted bad namespaced resource",
   641  		work: func(impl *Impl) {
   642  			impl.EnqueueLabelOfNamespaceScopedResource("ns-key", "name-key")(cache.DeletedFinalStateUnknown{
   643  				Key: "foo/bar",
   644  				Obj: "bad-resource",
   645  			})
   646  		},
   647  	}, {
   648  		name: "enqueue label of cluster scoped resource bad resource",
   649  		work: func(impl *Impl) {
   650  			impl.EnqueueLabelOfClusterScopedResource("name-key")("baz")
   651  		},
   652  	}, {
   653  		name: "enqueue label of cluster scoped resource without label",
   654  		work: func(impl *Impl) {
   655  			impl.EnqueueLabelOfClusterScopedResource("name-key")(&Resource{
   656  				ObjectMeta: metav1.ObjectMeta{
   657  					Name:      "foo",
   658  					Namespace: "bar",
   659  					Labels:    map[string]string{},
   660  				},
   661  			})
   662  		},
   663  	}, {
   664  		name: "enqueue label of cluster scoped resource with label",
   665  		work: func(impl *Impl) {
   666  			impl.EnqueueLabelOfClusterScopedResource("name-key")(&Resource{
   667  				ObjectMeta: metav1.ObjectMeta{
   668  					Name:      "foo",
   669  					Namespace: "bar",
   670  					Labels: map[string]string{
   671  						"name-key": "baz",
   672  					},
   673  				},
   674  			})
   675  		},
   676  		wantQueue: []types.NamespacedName{{Namespace: "", Name: "baz"}},
   677  	}, {
   678  		name: "enqueue label of deleted cluster scoped resource with label",
   679  		work: func(impl *Impl) {
   680  			impl.EnqueueLabelOfClusterScopedResource("name-key")(cache.DeletedFinalStateUnknown{
   681  				Key: "foo/bar",
   682  				Obj: &Resource{
   683  					ObjectMeta: metav1.ObjectMeta{
   684  						Name:      "foo",
   685  						Namespace: "bar",
   686  						Labels: map[string]string{
   687  							"name-key": "baz",
   688  						},
   689  					},
   690  				},
   691  			})
   692  		},
   693  		wantQueue: []types.NamespacedName{{Namespace: "", Name: "baz"}},
   694  	}, {
   695  		name: "enqueue namespace of object",
   696  		work: func(impl *Impl) {
   697  			impl.EnqueueNamespaceOf(&Resource{
   698  				ObjectMeta: metav1.ObjectMeta{
   699  					Name:      "foo",
   700  					Namespace: "bar",
   701  				},
   702  			})
   703  		},
   704  		wantQueue: []types.NamespacedName{{Name: "bar"}},
   705  	}, {
   706  		name: "enqueue label of deleted bad cluster scoped resource",
   707  		work: func(impl *Impl) {
   708  			impl.EnqueueLabelOfClusterScopedResource("name-key")(cache.DeletedFinalStateUnknown{
   709  				Key: "bar",
   710  				Obj: "bad-resource",
   711  			})
   712  		},
   713  	}}
   714  
   715  	for _, test := range tests {
   716  		t.Run(test.name, func(t *testing.T) {
   717  			var rl workqueue.TypedRateLimiter[any] = testRateLimiter{t, 100 * time.Millisecond}
   718  			impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{WorkQueueName: "Testing", Logger: TestLogger(t), RateLimiter: rl})
   719  			test.work(impl)
   720  
   721  			impl.WorkQueue().ShutDown()
   722  			gotQueue := drainWorkQueue(impl.WorkQueue())
   723  
   724  			if diff := cmp.Diff(test.wantQueue, gotQueue); diff != "" {
   725  				t.Error("unexpected queue (-want +got):", diff)
   726  			}
   727  		})
   728  	}
   729  }
   730  
   731  const (
   732  	// longDelay is longer than we expect the test to run.
   733  	longDelay = time.Minute
   734  	// shortDelay is short enough for the test to execute quickly, but long
   735  	// enough to reasonably delay the enqueuing of an item.
   736  	shortDelay = 50 * time.Millisecond
   737  
   738  	// time we allow the queue length checker to keep polling the
   739  	// workqueue.
   740  	queueCheckTimeout = shortDelay + 500*time.Millisecond
   741  )
   742  
   743  func pollQ(q workqueue.TypedRateLimitingInterface[any], sig chan int) func(context.Context) (bool, error) {
   744  	return func(context.Context) (bool, error) {
   745  		if ql := q.Len(); ql > 0 {
   746  			sig <- ql
   747  			return true, nil
   748  		}
   749  		return false, nil
   750  	}
   751  }
   752  
   753  func TestEnqueueAfter(t *testing.T) {
   754  	impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{
   755  		Logger:        TestLogger(t),
   756  		WorkQueueName: "Testing",
   757  	})
   758  
   759  	t.Cleanup(func() {
   760  		impl.WorkQueue().ShutDown()
   761  	})
   762  
   763  	// Enqueue two items with a long delay.
   764  	impl.EnqueueAfter(&Resource{
   765  		ObjectMeta: metav1.ObjectMeta{
   766  			Name:      "for",
   767  			Namespace: "waiting",
   768  		},
   769  	}, longDelay)
   770  	impl.EnqueueAfter(&Resource{
   771  		ObjectMeta: metav1.ObjectMeta{
   772  			Name:      "waterfall",
   773  			Namespace: "the",
   774  		},
   775  	}, longDelay)
   776  
   777  	// Enqueue one item with a short delay.
   778  	enqueueTime := time.Now()
   779  	impl.EnqueueAfter(&Resource{
   780  		ObjectMeta: metav1.ObjectMeta{
   781  			Name:      "fall",
   782  			Namespace: "to",
   783  		},
   784  	}, shortDelay)
   785  
   786  	// Keep checking the queue length until 'to/fall' gets enqueued, send to channel to indicate success.
   787  	queuePopulated := make(chan int)
   788  	ctx, cancel := context.WithTimeout(context.Background(), queueCheckTimeout)
   789  
   790  	t.Cleanup(func() {
   791  		close(queuePopulated)
   792  		cancel()
   793  	})
   794  
   795  	go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true,
   796  		pollQ(impl.WorkQueue(), queuePopulated))
   797  
   798  	select {
   799  	case qlen := <-queuePopulated:
   800  		if enqueueDelay := time.Since(enqueueTime); enqueueDelay < shortDelay {
   801  			t.Errorf("Item enqueued within %v, expected at least a %v delay", enqueueDelay, shortDelay)
   802  		}
   803  		if got, want := qlen, 1; got != want {
   804  			t.Errorf("|Queue| = %d, want: %d", got, want)
   805  		}
   806  
   807  	case <-ctx.Done():
   808  		t.Fatal("Timed out waiting for item to be put onto the workqueue")
   809  	}
   810  
   811  	impl.WorkQueue().ShutDown()
   812  
   813  	got, want := drainWorkQueue(impl.WorkQueue()), []types.NamespacedName{{Namespace: "to", Name: "fall"}}
   814  	if diff := cmp.Diff(want, got); diff != "" {
   815  		t.Errorf("Unexpected workqueue state (-:expect, +:got):\n%s", diff)
   816  	}
   817  }
   818  
   819  func TestEnqueueKeyAfter(t *testing.T) {
   820  	impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{
   821  		Logger:        TestLogger(t),
   822  		WorkQueueName: "Testing",
   823  	})
   824  	t.Cleanup(func() {
   825  		impl.WorkQueue().ShutDown()
   826  	})
   827  
   828  	// Enqueue two items with a long delay.
   829  	impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "waiting", Name: "for"}, longDelay)
   830  	impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "the", Name: "waterfall"}, longDelay)
   831  
   832  	// Enqueue one item with a short delay.
   833  	enqueueTime := time.Now()
   834  	impl.EnqueueKeyAfter(types.NamespacedName{Namespace: "to", Name: "fall"}, shortDelay)
   835  
   836  	// Keep checking the queue length until 'to/fall' gets enqueued, send to channel to indicate success.
   837  	queuePopulated := make(chan int)
   838  
   839  	ctx, cancel := context.WithTimeout(context.Background(), queueCheckTimeout)
   840  
   841  	t.Cleanup(func() {
   842  		close(queuePopulated)
   843  		cancel()
   844  	})
   845  
   846  	go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true,
   847  		pollQ(impl.WorkQueue(), queuePopulated))
   848  
   849  	select {
   850  	case qlen := <-queuePopulated:
   851  		if enqueueDelay := time.Since(enqueueTime); enqueueDelay < shortDelay {
   852  			t.Errorf("Item enqueued within %v, expected at least a %v delay", enqueueDelay, shortDelay)
   853  		}
   854  		if got, want := qlen, 1; got != want {
   855  			t.Errorf("|Queue| = %d, want: %d", got, want)
   856  		}
   857  
   858  	case <-ctx.Done():
   859  		t.Fatal("Timed out waiting for item to be put onto the workqueue")
   860  	}
   861  
   862  	impl.WorkQueue().ShutDown()
   863  
   864  	got, want := drainWorkQueue(impl.WorkQueue()), []types.NamespacedName{{Namespace: "to", Name: "fall"}}
   865  	if diff := cmp.Diff(want, got); diff != "" {
   866  		t.Errorf("Unexpected workqueue state (-:expect, +:got):\n%s", diff)
   867  	}
   868  }
   869  
   870  type CountingReconciler struct {
   871  	count atomic.Int32
   872  }
   873  
   874  func (cr *CountingReconciler) Reconcile(context.Context, string) error {
   875  	cr.count.Add(1)
   876  	return nil
   877  }
   878  
   879  func TestStartAndShutdown(t *testing.T) {
   880  	r := &CountingReconciler{}
   881  	impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{
   882  		Logger:        TestLogger(t),
   883  		WorkQueueName: "Testing",
   884  	})
   885  
   886  	ctx, cancel := context.WithCancel(context.Background())
   887  	doneCh := make(chan struct{})
   888  	go func() {
   889  		defer close(doneCh)
   890  		StartAll(ctx, impl)
   891  	}()
   892  	t.Cleanup(func() {
   893  		cancel()
   894  		<-doneCh
   895  	})
   896  
   897  	select {
   898  	case <-time.After(10 * time.Millisecond):
   899  		// We don't expect completion before the context is cancelled.
   900  	case <-doneCh:
   901  		t.Error("StartAll finished early.")
   902  	}
   903  	cancel()
   904  
   905  	select {
   906  	case <-time.After(time.Second):
   907  		t.Error("Timed out waiting for controller to finish.")
   908  	case <-doneCh:
   909  		// We expect the work to complete.
   910  	}
   911  
   912  	if got, want := r.count.Load(), int32(0); got != want {
   913  		t.Errorf("count = %v, wanted %v", got, want)
   914  	}
   915  }
   916  
   917  type countingLeaderAwareReconciler struct {
   918  	reconciler.LeaderAwareFuncs
   919  
   920  	reconcileCount atomic.Int32
   921  	promotionCount atomic.Int32
   922  }
   923  
   924  var _ reconciler.LeaderAware = (*countingLeaderAwareReconciler)(nil)
   925  
   926  func (cr *countingLeaderAwareReconciler) Promote(b reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
   927  	cr.promotionCount.Add(1)
   928  	return cr.LeaderAwareFuncs.Promote(b, enq)
   929  }
   930  
   931  func (cr *countingLeaderAwareReconciler) Reconcile(ctx context.Context, key string) error {
   932  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   933  	if err != nil {
   934  		return err
   935  	}
   936  
   937  	if cr.IsLeaderFor(types.NamespacedName{
   938  		Namespace: namespace,
   939  		Name:      name,
   940  	}) {
   941  		cr.reconcileCount.Add(1)
   942  	}
   943  	return nil
   944  }
   945  
   946  func TestStartAndShutdownWithLeaderAwareNoElection(t *testing.T) {
   947  	r := &countingLeaderAwareReconciler{
   948  		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
   949  			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
   950  				t.Error("Promote should not be called when no leader election is enabled.")
   951  				return nil
   952  			},
   953  		},
   954  	}
   955  
   956  	impl := NewContext(context.TODO(), r, ControllerOptions{
   957  		Logger:        TestLogger(t),
   958  		WorkQueueName: "Testing",
   959  	})
   960  
   961  	ctx, cancel := context.WithCancel(context.Background())
   962  	doneCh := make(chan struct{})
   963  	go func() {
   964  		defer close(doneCh)
   965  		StartAll(ctx, impl)
   966  	}()
   967  	t.Cleanup(func() {
   968  		cancel()
   969  		<-doneCh
   970  	})
   971  
   972  	select {
   973  	case <-doneCh:
   974  		t.Fatal("StartAll finished early.")
   975  	case <-time.After(1 * time.Second):
   976  		// Give it some time to run.
   977  	}
   978  
   979  	cancel()
   980  
   981  	select {
   982  	case <-time.After(time.Second):
   983  		t.Error("Timed out waiting for controller to finish.")
   984  	case <-doneCh:
   985  		// We expect the work to complete.
   986  	}
   987  
   988  	if got, want := r.reconcileCount.Load(), int32(0); got != want {
   989  		t.Errorf("reconcile count = %v, wanted %v", got, want)
   990  	}
   991  
   992  	// Since the elector can't easily be mocked we assume:
   993  	// 1. We are using the unopposed elector
   994  	// 2. It has a single initial bucket (the Universe)
   995  	//
   996  	// Thus we expect two promotions to occur
   997  	// 1. It provides the initial set of buckets and these are promoted
   998  	//    before reconciliation and leader election go routines start
   999  	// 2. When leader election starts the unopposed elector promotes
  1000  	//    that same bucket again
  1001  	if got, want := r.promotionCount.Load(), int32(2); got != want {
  1002  		t.Errorf("promotion count = %v, wanted %v", got, want)
  1003  	}
  1004  }
  1005  
  1006  func TestStartAndShutdownWithLeaderAwareWithLostElection(t *testing.T) {
  1007  	promoted := make(chan struct{})
  1008  	r := &countingLeaderAwareReconciler{
  1009  		LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
  1010  			PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
  1011  				close(promoted)
  1012  				return nil
  1013  			},
  1014  		},
  1015  	}
  1016  	cc := leaderelection.ComponentConfig{
  1017  		Component:     "component",
  1018  		LeaseDuration: 15 * time.Second,
  1019  		RenewDeadline: 10 * time.Second,
  1020  		RetryPeriod:   2 * time.Second,
  1021  	}
  1022  	kc := fakekube.NewSimpleClientset(
  1023  		&coordinationv1.Lease{
  1024  			ObjectMeta: metav1.ObjectMeta{
  1025  				Namespace: system.Namespace(),
  1026  				Name:      "component.testing.00-of-01",
  1027  			},
  1028  			Spec: coordinationv1.LeaseSpec{
  1029  				HolderIdentity:       ptr.String("not-us"),
  1030  				LeaseDurationSeconds: ptr.Int32(3000),
  1031  				AcquireTime:          &metav1.MicroTime{Time: time.Now()},
  1032  				RenewTime:            &metav1.MicroTime{Time: time.Now().Add(3000 * time.Second)},
  1033  			},
  1034  		},
  1035  	)
  1036  
  1037  	impl := NewContext(context.TODO(), &nopReconciler{}, ControllerOptions{
  1038  		Logger:        TestLogger(t),
  1039  		WorkQueueName: "Testing",
  1040  	})
  1041  
  1042  	ctx, cancel := context.WithCancel(context.Background())
  1043  	ctx = leaderelection.WithStandardLeaderElectorBuilder(ctx, kc, cc)
  1044  	doneCh := make(chan struct{})
  1045  	go func() {
  1046  		defer close(doneCh)
  1047  		StartAll(ctx, impl)
  1048  	}()
  1049  	t.Cleanup(func() {
  1050  		cancel()
  1051  		<-doneCh
  1052  	})
  1053  
  1054  	select {
  1055  	case <-promoted:
  1056  		t.Fatal("Unexpected promotion.")
  1057  	case <-time.After(3 * time.Second):
  1058  		// Wait for 3 seconds for good measure.
  1059  	case <-doneCh:
  1060  		t.Error("StartAll finished early.")
  1061  	}
  1062  
  1063  	cancel()
  1064  
  1065  	select {
  1066  	case <-time.After(time.Second):
  1067  		t.Error("Timed out waiting for controller to finish.")
  1068  	case <-doneCh:
  1069  		// We expect the work to complete.
  1070  	}
  1071  
  1072  	if got, want := r.reconcileCount.Load(), int32(0); got != want {
  1073  		t.Errorf("reconcile count = %v, wanted %v", got, want)
  1074  	}
  1075  }
  1076  
  1077  func TestStartAndShutdownWithWork(t *testing.T) {
  1078  	r := &CountingReconciler{}
  1079  	impl := NewContext(context.TODO(), r, ControllerOptions{
  1080  		Logger:        TestLogger(t),
  1081  		WorkQueueName: "Testing",
  1082  	})
  1083  
  1084  	ctx, cancel := context.WithCancel(context.Background())
  1085  	doneCh := make(chan struct{})
  1086  	go func() {
  1087  		defer close(doneCh)
  1088  		StartAll(ctx, impl)
  1089  	}()
  1090  	t.Cleanup(func() {
  1091  		cancel()
  1092  		<-doneCh
  1093  	})
  1094  
  1095  	impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
  1096  
  1097  	select {
  1098  	case <-time.After(10 * time.Millisecond):
  1099  		// We don't expect completion before the context is cancelled.
  1100  	case <-doneCh:
  1101  		t.Error("StartAll finished early.")
  1102  	}
  1103  	cancel()
  1104  
  1105  	select {
  1106  	case <-time.After(time.Second):
  1107  		t.Error("Timed out waiting for controller to finish.")
  1108  	case <-doneCh:
  1109  		// We expect the work to complete.
  1110  	}
  1111  
  1112  	if got, want := r.count.Load(), int32(1); got != want {
  1113  		t.Errorf("reconcile count = %v, wanted %v", got, want)
  1114  	}
  1115  	if got, want := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), 0; got != want {
  1116  		t.Errorf("requeues = %v, wanted %v", got, want)
  1117  	}
  1118  }
  1119  
  1120  type fakeError struct{}
  1121  
  1122  var _ error = (*fakeError)(nil)
  1123  
  1124  func (*fakeError) Error() string {
  1125  	return "I always error"
  1126  }
  1127  
  1128  func TestPermanentError(t *testing.T) {
  1129  	err := new(fakeError)
  1130  	permErr := NewPermanentError(err)
  1131  	if !IsPermanentError(permErr) {
  1132  		t.Errorf("Expected type %T to be a permanentError", permErr)
  1133  	}
  1134  	if IsPermanentError(err) {
  1135  		t.Errorf("Expected type %T to not be a permanentError", err)
  1136  	}
  1137  
  1138  	wrapPermErr := fmt.Errorf("wrapped: %w", permErr)
  1139  	if !IsPermanentError(wrapPermErr) {
  1140  		t.Error("Expected wrapped permanentError to be equivalent to a permanentError")
  1141  	}
  1142  
  1143  	unwrapErr := new(fakeError)
  1144  	if !errors.As(permErr, &unwrapErr) {
  1145  		t.Errorf("Could not unwrap %T from permanentError", unwrapErr)
  1146  	}
  1147  }
  1148  
  1149  func TestRequeueKey(t *testing.T) {
  1150  	err := new(fakeError)
  1151  	reqErr := NewRequeueImmediately()
  1152  	if ok, _ := IsRequeueKey(reqErr); !ok {
  1153  		t.Errorf("Expected type %T to be a requeueKeyError", reqErr)
  1154  	}
  1155  	if ok, _ := IsRequeueKey(err); ok {
  1156  		t.Errorf("Expected type %T to not be a requeueKeyError", err)
  1157  	}
  1158  
  1159  	want := 10 * time.Minute
  1160  	reqErr = NewRequeueAfter(want)
  1161  	wrapReqErr := fmt.Errorf("wrapped: %w", reqErr)
  1162  	if ok, got := IsRequeueKey(wrapReqErr); !ok {
  1163  		t.Error("Expected wrapped requeueKeyError to be equivalent to a requeueKeyError")
  1164  	} else if want != got {
  1165  		t.Errorf("IsRequeueKey() = (true, %v), wanted (true, %v)", got, want)
  1166  	}
  1167  }
  1168  
  1169  type errorReconciler struct{}
  1170  
  1171  func (er *errorReconciler) Reconcile(context.Context, string) error {
  1172  	return new(fakeError)
  1173  }
  1174  
  1175  func TestStartAndShutdownWithErroringWork(t *testing.T) {
  1176  	const testTimeout = 500 * time.Millisecond
  1177  
  1178  	item := types.NamespacedName{Namespace: "", Name: "bar"}
  1179  
  1180  	impl := NewContext(context.TODO(), &errorReconciler{}, ControllerOptions{
  1181  		Logger:        TestLogger(t),
  1182  		WorkQueueName: "Testing",
  1183  	})
  1184  	impl.EnqueueKey(item)
  1185  
  1186  	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
  1187  	doneCh := make(chan struct{})
  1188  	go func() {
  1189  		defer close(doneCh)
  1190  		// StartAll blocks until all the worker threads finish, which shouldn't
  1191  		// be until we cancel the context.
  1192  		StartAll(ctx, impl)
  1193  	}()
  1194  	t.Cleanup(func() {
  1195  		cancel()
  1196  		<-doneCh
  1197  	})
  1198  
  1199  	// Keep checking the number of requeues, send to channel to indicate success.
  1200  	itemRequeued := make(chan struct{})
  1201  	defer close(itemRequeued)
  1202  
  1203  	var successCheck wait.ConditionWithContextFunc = func(context.Context) (bool, error) {
  1204  		// Check that the work was requeued in RateLimiter, as NumRequeues
  1205  		// can't fully reflect the real state of queue length.
  1206  		// Here we need to wait for NumRequeues to be more than 1, to ensure
  1207  		// the key get re-queued and reprocessed as expect.
  1208  		if impl.WorkQueue().NumRequeues(item) > 1 {
  1209  			itemRequeued <- struct{}{}
  1210  			return true, nil
  1211  		}
  1212  		return false, nil
  1213  	}
  1214  	go wait.PollUntilContextCancel(ctx, 5*time.Millisecond, true, successCheck)
  1215  
  1216  	select {
  1217  	case <-itemRequeued:
  1218  		// shut down reconciler
  1219  		cancel()
  1220  
  1221  	case <-doneCh:
  1222  		t.Fatal("StartAll finished early")
  1223  
  1224  	case <-ctx.Done():
  1225  		t.Fatal("Timed out waiting for item to be requeued")
  1226  	}
  1227  }
  1228  
  1229  type permanentErrorReconciler struct{}
  1230  
  1231  func (er *permanentErrorReconciler) Reconcile(context.Context, string) error {
  1232  	return NewPermanentError(new(fakeError))
  1233  }
  1234  
  1235  func TestStartAndShutdownWithPermanentErroringWork(t *testing.T) {
  1236  	r := &permanentErrorReconciler{}
  1237  	impl := NewContext(context.TODO(), r, ControllerOptions{
  1238  		Logger:        TestLogger(t),
  1239  		WorkQueueName: "Testing",
  1240  	})
  1241  
  1242  	ctx, cancel := context.WithCancel(context.Background())
  1243  	doneCh := make(chan struct{})
  1244  	go func() {
  1245  		defer close(doneCh)
  1246  		StartAll(ctx, impl)
  1247  	}()
  1248  	t.Cleanup(func() {
  1249  		cancel()
  1250  		<-doneCh
  1251  	})
  1252  
  1253  	impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
  1254  
  1255  	select {
  1256  	case <-time.After(20 * time.Millisecond):
  1257  		// We don't expect completion before the context is cancelled.
  1258  	case <-doneCh:
  1259  		t.Error("StartAll finished early.")
  1260  	}
  1261  	cancel()
  1262  
  1263  	select {
  1264  	case <-time.After(time.Second):
  1265  		t.Error("Timed out waiting for controller to finish.")
  1266  	case <-doneCh:
  1267  		// We expect the work to complete.
  1268  	}
  1269  
  1270  	// Check that the work was not requeued in RateLimiter.
  1271  	if got, want := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), 0; got != want {
  1272  		t.Errorf("Requeue count = %v, wanted %v", got, want)
  1273  	}
  1274  }
  1275  
  1276  type requeueAfterReconciler struct {
  1277  	duration time.Duration
  1278  }
  1279  
  1280  func (er *requeueAfterReconciler) Reconcile(context.Context, string) error {
  1281  	return NewRequeueAfter(er.duration)
  1282  }
  1283  
  1284  func TestStartAndShutdownWithRequeuingWork(t *testing.T) {
  1285  	tests := []struct {
  1286  		name     string
  1287  		minimum  int
  1288  		duration time.Duration
  1289  	}{{
  1290  		name:    "no duration",
  1291  		minimum: 100,
  1292  	}, {
  1293  		name:     "small duration",
  1294  		minimum:  2,
  1295  		duration: 100 * time.Millisecond,
  1296  	}}
  1297  
  1298  	for _, test := range tests {
  1299  		t.Run(test.name, func(t *testing.T) {
  1300  			r := &requeueAfterReconciler{duration: test.duration}
  1301  			impl := NewContext(context.TODO(), r, ControllerOptions{
  1302  				Logger:        TestLogger(t),
  1303  				WorkQueueName: "Testing",
  1304  			})
  1305  
  1306  			ctx, cancel := context.WithCancel(context.Background())
  1307  			doneCh := make(chan struct{})
  1308  			go func() {
  1309  				defer close(doneCh)
  1310  				StartAll(ctx, impl)
  1311  			}()
  1312  			t.Cleanup(func() {
  1313  				cancel()
  1314  				<-doneCh
  1315  			})
  1316  
  1317  			impl.EnqueueKey(types.NamespacedName{Namespace: "foo", Name: "bar"})
  1318  
  1319  			select {
  1320  			case <-time.After(20 * time.Millisecond):
  1321  				// We don't expect completion before the context is cancelled.
  1322  			case <-doneCh:
  1323  				t.Error("StartAll finished early.")
  1324  			}
  1325  			cancel()
  1326  
  1327  			select {
  1328  			case <-time.After(time.Second):
  1329  				t.Error("Timed out waiting for controller to finish.")
  1330  			case <-doneCh:
  1331  				// We expect the work to complete.
  1332  			}
  1333  
  1334  			// Check that the work was not requeued in RateLimiter.
  1335  			if got, wantAtLeast := impl.WorkQueue().NumRequeues(types.NamespacedName{Namespace: "foo", Name: "bar"}), test.minimum; got >= wantAtLeast {
  1336  				t.Errorf("Requeue count = %v, wanted at least %v", got, wantAtLeast)
  1337  			}
  1338  		})
  1339  	}
  1340  }
  1341  
  1342  func drainWorkQueue(wq workqueue.TypedRateLimitingInterface[any]) (hasQueue []types.NamespacedName) {
  1343  	for {
  1344  		key, shutdown := wq.Get()
  1345  		if key == nil && shutdown {
  1346  			break
  1347  		}
  1348  		hasQueue = append(hasQueue, key.(types.NamespacedName))
  1349  	}
  1350  	return hasQueue
  1351  }
  1352  
  1353  type fakeInformer struct {
  1354  	cache.SharedInformer
  1355  }
  1356  
  1357  type fakeStore struct {
  1358  	cache.Store
  1359  }
  1360  
  1361  func (*fakeInformer) GetStore() cache.Store {
  1362  	return &fakeStore{}
  1363  }
  1364  
  1365  var (
  1366  	fakeKeys = []string{"foo/bar", "bar/foo", "fizz/buzz"}
  1367  	fakeObjs = []interface{}{
  1368  		&Resource{
  1369  			ObjectMeta: metav1.ObjectMeta{
  1370  				Name:      "bar",
  1371  				Namespace: "foo",
  1372  			},
  1373  		},
  1374  		&Resource{
  1375  			ObjectMeta: metav1.ObjectMeta{
  1376  				Name:      "foo",
  1377  				Namespace: "bar",
  1378  			},
  1379  		},
  1380  		&Resource{
  1381  			ObjectMeta: metav1.ObjectMeta{
  1382  				Name:      "buzz",
  1383  				Namespace: "fizz",
  1384  			},
  1385  		},
  1386  	}
  1387  )
  1388  
  1389  func (*fakeStore) ListKeys() []string {
  1390  	return fakeKeys
  1391  }
  1392  
  1393  func (*fakeStore) List() []interface{} {
  1394  	return fakeObjs
  1395  }
  1396  
  1397  func TestImplGlobalResync(t *testing.T) {
  1398  	r := &CountingReconciler{}
  1399  	impl := NewContext(context.TODO(), r, ControllerOptions{
  1400  		Logger:        TestLogger(t),
  1401  		WorkQueueName: "Testing",
  1402  	})
  1403  
  1404  	ctx, cancel := context.WithCancel(context.Background())
  1405  	doneCh := make(chan struct{})
  1406  	go func() {
  1407  		defer close(doneCh)
  1408  		StartAll(ctx, impl)
  1409  	}()
  1410  	t.Cleanup(func() {
  1411  		cancel()
  1412  		<-doneCh
  1413  	})
  1414  
  1415  	impl.GlobalResync(&fakeInformer{})
  1416  
  1417  	// The global resync delays enqueuing things by a second with a jitter that
  1418  	// goes up to len(fakeObjs) times a second: time.Duration(1+len(fakeObjs)) * time.Second.
  1419  	// In this test, the fast lane is empty, so we can assume immediate enqueuing.
  1420  	select {
  1421  	case <-time.After(50 * time.Millisecond):
  1422  		// We don't expect completion before the context is cancelled.
  1423  	case <-doneCh:
  1424  		t.Error("StartAll finished early.")
  1425  	}
  1426  	cancel()
  1427  
  1428  	select {
  1429  	case <-time.After(time.Second):
  1430  		t.Error("Timed out waiting for controller to finish.")
  1431  	case <-doneCh:
  1432  		// We expect the work to complete.
  1433  	}
  1434  
  1435  	if got, want := r.count.Load(), int32(3); want != got {
  1436  		t.Errorf("GlobalResync: want = %v, got = %v", want, got)
  1437  	}
  1438  }
  1439  
  1440  type fixedInformer struct {
  1441  	m    sync.Mutex
  1442  	sunk bool
  1443  	done bool
  1444  }
  1445  
  1446  var _ Informer = (*fixedInformer)(nil)
  1447  
  1448  func (fi *fixedInformer) Run(stopCh <-chan struct{}) {
  1449  	<-stopCh
  1450  
  1451  	fi.m.Lock()
  1452  	defer fi.m.Unlock()
  1453  	fi.done = true
  1454  }
  1455  
  1456  func (fi *fixedInformer) HasSynced() bool {
  1457  	fi.m.Lock()
  1458  	defer fi.m.Unlock()
  1459  	return fi.sunk
  1460  }
  1461  
  1462  func (fi *fixedInformer) ToggleSynced(b bool) {
  1463  	fi.m.Lock()
  1464  	defer fi.m.Unlock()
  1465  	fi.sunk = b
  1466  }
  1467  
  1468  func (fi *fixedInformer) Done() bool {
  1469  	fi.m.Lock()
  1470  	defer fi.m.Unlock()
  1471  	return fi.done
  1472  }
  1473  
  1474  func TestStartInformersSuccess(t *testing.T) {
  1475  	errCh := make(chan error)
  1476  	defer close(errCh)
  1477  
  1478  	fi := &fixedInformer{sunk: true}
  1479  
  1480  	stopCh := make(chan struct{})
  1481  	defer close(stopCh)
  1482  	go func() {
  1483  		errCh <- StartInformers(stopCh, fi)
  1484  	}()
  1485  
  1486  	select {
  1487  	case err := <-errCh:
  1488  		if err != nil {
  1489  			t.Error("Unexpected error:", err)
  1490  		}
  1491  	case <-time.After(time.Second):
  1492  		t.Error("Timed out waiting for informers to sync.")
  1493  	}
  1494  }
  1495  
  1496  func TestStartInformersEventualSuccess(t *testing.T) {
  1497  	errCh := make(chan error)
  1498  	defer close(errCh)
  1499  
  1500  	fi := &fixedInformer{sunk: false}
  1501  
  1502  	stopCh := make(chan struct{})
  1503  	defer close(stopCh)
  1504  	go func() {
  1505  		errCh <- StartInformers(stopCh, fi)
  1506  	}()
  1507  
  1508  	select {
  1509  	case <-time.After(50 * time.Millisecond):
  1510  		// Wait a brief period to ensure nothing is sent.
  1511  	case err := <-errCh:
  1512  		t.Fatal("Unexpected send on errCh:", err)
  1513  	}
  1514  
  1515  	// Let the Sync complete.
  1516  	fi.ToggleSynced(true)
  1517  
  1518  	select {
  1519  	case err := <-errCh:
  1520  		if err != nil {
  1521  			t.Error("Unexpected error:", err)
  1522  		}
  1523  	case <-time.After(time.Second):
  1524  		t.Error("Timed out waiting for informers to sync.")
  1525  	}
  1526  }
  1527  
  1528  func TestStartInformersFailure(t *testing.T) {
  1529  	errCh := make(chan error)
  1530  	defer close(errCh)
  1531  
  1532  	fi := &fixedInformer{sunk: false}
  1533  
  1534  	stopCh := make(chan struct{})
  1535  	go func() {
  1536  		errCh <- StartInformers(stopCh, fi)
  1537  	}()
  1538  
  1539  	select {
  1540  	case <-time.After(50 * time.Millisecond):
  1541  		// Wait a brief period to ensure nothing is sent.
  1542  	case err := <-errCh:
  1543  		t.Fatal("Unexpected send on errCh:", err)
  1544  	}
  1545  
  1546  	// Now close the stopCh and we should see an error sent.
  1547  	close(stopCh)
  1548  
  1549  	select {
  1550  	case err := <-errCh:
  1551  		if err == nil {
  1552  			t.Error("Unexpected success syncing informers after stopCh closed.")
  1553  		}
  1554  	case <-time.After(time.Second):
  1555  		t.Error("Timed out waiting for informers to sync.")
  1556  	}
  1557  }
  1558  
  1559  func TestRunInformersSuccess(t *testing.T) {
  1560  	errCh := make(chan error)
  1561  	defer close(errCh)
  1562  
  1563  	fi := &fixedInformer{sunk: true}
  1564  
  1565  	stopCh := make(chan struct{})
  1566  	go func() {
  1567  		_, err := RunInformers(stopCh, fi)
  1568  		errCh <- err
  1569  	}()
  1570  
  1571  	select {
  1572  	case err := <-errCh:
  1573  		if err != nil {
  1574  			t.Fatal("Unexpected error:", err)
  1575  		}
  1576  	case <-time.After(time.Second):
  1577  		t.Fatal("Timed out waiting for informers to sync.")
  1578  	}
  1579  
  1580  	close(stopCh)
  1581  }
  1582  
  1583  func TestRunInformersEventualSuccess(t *testing.T) {
  1584  	errCh := make(chan error)
  1585  	defer close(errCh)
  1586  
  1587  	fi := &fixedInformer{sunk: false}
  1588  
  1589  	stopCh := make(chan struct{})
  1590  	go func() {
  1591  		_, err := RunInformers(stopCh, fi)
  1592  		errCh <- err
  1593  	}()
  1594  
  1595  	select {
  1596  	case <-time.After(50 * time.Millisecond):
  1597  		// Wait a brief period to ensure nothing is sent.
  1598  	case err := <-errCh:
  1599  		t.Fatal("Unexpected send on errCh:", err)
  1600  	}
  1601  
  1602  	// Let the Sync complete.
  1603  	fi.ToggleSynced(true)
  1604  
  1605  	select {
  1606  	case err := <-errCh:
  1607  		if err != nil {
  1608  			t.Fatal("Unexpected error:", err)
  1609  		}
  1610  	case <-time.After(time.Second):
  1611  		t.Fatal("Timed out waiting for informers to sync.")
  1612  	}
  1613  
  1614  	close(stopCh)
  1615  }
  1616  
  1617  func TestRunInformersFailure(t *testing.T) {
  1618  	errCh := make(chan error)
  1619  	defer close(errCh)
  1620  
  1621  	fi := &fixedInformer{sunk: false}
  1622  
  1623  	stopCh := make(chan struct{})
  1624  	go func() {
  1625  		_, err := RunInformers(stopCh, fi)
  1626  		errCh <- err
  1627  	}()
  1628  
  1629  	select {
  1630  	case <-time.After(50 * time.Millisecond):
  1631  		// Wait a brief period to ensure nothing is sent.
  1632  	case err := <-errCh:
  1633  		t.Fatal("Unexpected send on errCh:", err)
  1634  	}
  1635  
  1636  	// Now close the stopCh and we should see an error sent.
  1637  	close(stopCh)
  1638  
  1639  	select {
  1640  	case err := <-errCh:
  1641  		if err == nil {
  1642  			t.Fatal("Unexpected success syncing informers after stopCh closed.")
  1643  		}
  1644  	case <-time.After(time.Second):
  1645  		t.Fatal("Timed out waiting for informers to sync.")
  1646  	}
  1647  }
  1648  
  1649  func TestRunInformersFinished(t *testing.T) {
  1650  	fi := &fixedInformer{sunk: true}
  1651  	defer func() {
  1652  		if !fi.Done() {
  1653  			t.Fatalf("Test didn't wait for informers to finish")
  1654  		}
  1655  	}()
  1656  
  1657  	ctx, cancel := context.WithCancel(TestContextWithLogger(t))
  1658  	t.Cleanup(cancel)
  1659  
  1660  	waitInformers, err := RunInformers(ctx.Done(), fi)
  1661  	if err != nil {
  1662  		t.Fatal("Failed to start informers:", err)
  1663  	}
  1664  
  1665  	cancel()
  1666  
  1667  	ch := make(chan struct{})
  1668  	go func() {
  1669  		waitInformers()
  1670  		ch <- struct{}{}
  1671  	}()
  1672  
  1673  	select {
  1674  	case <-ch:
  1675  	case <-time.After(time.Second):
  1676  		t.Fatal("Timed out waiting for informers to finish.")
  1677  	}
  1678  }
  1679  
  1680  func TestGetResyncPeriod(t *testing.T) {
  1681  	ctx := context.Background()
  1682  
  1683  	if got := GetResyncPeriod(ctx); got != DefaultResyncPeriod {
  1684  		t.Errorf("GetResyncPeriod() = %v, wanted %v", got, nil)
  1685  	}
  1686  
  1687  	bob := 30 * time.Second
  1688  	ctx = WithResyncPeriod(ctx, bob)
  1689  
  1690  	if want, got := bob, GetResyncPeriod(ctx); got != want {
  1691  		t.Errorf("GetResyncPeriod() = %v, wanted %v", got, want)
  1692  	}
  1693  
  1694  	tribob := 90 * time.Second
  1695  	if want, got := tribob, GetTrackerLease(ctx); got != want {
  1696  		t.Errorf("GetTrackerLease() = %v, wanted %v", got, want)
  1697  	}
  1698  }
  1699  
  1700  func TestGetEventRecorder(t *testing.T) {
  1701  	ctx := context.Background()
  1702  
  1703  	if got := GetEventRecorder(ctx); got != nil {
  1704  		t.Errorf("GetEventRecorder() = %v, wanted nil", got)
  1705  	}
  1706  
  1707  	ctx = WithEventRecorder(ctx, record.NewFakeRecorder(1000))
  1708  
  1709  	if got := GetEventRecorder(ctx); got == nil {
  1710  		t.Error("GetEventRecorder() = nil, wanted non-nil")
  1711  	}
  1712  }