knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/tracker/enqueue_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 tracker
    18  
    19  import (
    20  	"regexp"
    21  	"testing"
    22  	"time"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/client-go/tools/cache"
    28  
    29  	"knative.dev/pkg/kmeta"
    30  	. "knative.dev/pkg/testing"
    31  )
    32  
    33  func TestHappyPathsExact(t *testing.T) {
    34  	calls := 0
    35  	f := func(key types.NamespacedName) {
    36  		calls++
    37  	}
    38  
    39  	trk := New(f, 100*time.Millisecond)
    40  
    41  	thing1 := &Resource{
    42  		TypeMeta: metav1.TypeMeta{
    43  			APIVersion: "ref.knative.dev/v1alpha1",
    44  			Kind:       "Thing1",
    45  		},
    46  		ObjectMeta: metav1.ObjectMeta{
    47  			Namespace: "ns",
    48  			Name:      "foo",
    49  		},
    50  	}
    51  	or := kmeta.ObjectReference(thing1)
    52  	ref := Reference{
    53  		APIVersion: or.APIVersion,
    54  		Kind:       or.Kind,
    55  		Namespace:  or.Namespace,
    56  		Name:       or.Name,
    57  	}
    58  
    59  	thing2 := &Resource{
    60  		TypeMeta: metav1.TypeMeta{
    61  			APIVersion: "refer.knative.dev/v1alpha1",
    62  			Kind:       "Thing2",
    63  		},
    64  		ObjectMeta: metav1.ObjectMeta{
    65  			Namespace: "default",
    66  			Name:      "bar.baz.this-is-fine",
    67  		},
    68  	}
    69  
    70  	// Not tracked yet
    71  	{
    72  		trk.OnChanged(thing1)
    73  		if got, want := calls, 0; got != want {
    74  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
    75  		}
    76  		obs := trk.GetObservers(thing1)
    77  		if got, want := len(obs), 0; got != want {
    78  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
    79  		}
    80  	}
    81  
    82  	// Tracked gets called
    83  	{
    84  		if err := trk.Track(ref.ObjectReference(), thing2); err != nil {
    85  			t.Fatal("Track() =", err)
    86  		}
    87  		// New registrations should result in an immediate callback.
    88  		if got, want := calls, 1; got != want {
    89  			t.Fatalf("Track() = %v, wanted %v", got, want)
    90  		}
    91  
    92  		trk.OnChanged(thing1)
    93  		if got, want := calls, 2; got != want {
    94  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
    95  		}
    96  		obs := trk.GetObservers(thing1)
    97  		if got, want := len(obs), 1; got != want {
    98  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
    99  		}
   100  	}
   101  
   102  	// Still gets called
   103  	{
   104  		trk.OnChanged(thing1)
   105  		if got, want := calls, 3; got != want {
   106  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   107  		}
   108  	}
   109  
   110  	// Check that after the sleep duration, we stop getting called.
   111  	time.Sleep(101 * time.Millisecond)
   112  	{
   113  		trk.OnChanged(thing1)
   114  		if got, want := calls, 3; got != want {
   115  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   116  		}
   117  		if _, stillThere := trk.(*impl).exact[ref]; stillThere {
   118  			t.Fatal("Timeout passed, but exact for objectReference is still there")
   119  		}
   120  		obs := trk.GetObservers(thing1)
   121  		if got, want := len(obs), 0; got != want {
   122  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   123  		}
   124  	}
   125  
   126  	// Starts getting called again
   127  	{
   128  		if err := trk.Track(ref.ObjectReference(), thing2); err != nil {
   129  			t.Fatal("Track() =", err)
   130  		}
   131  		// New registrations should result in an immediate callback.
   132  		if got, want := calls, 4; got != want {
   133  			t.Fatalf("Track() = %v, wanted %v", got, want)
   134  		}
   135  
   136  		trk.OnChanged(thing1)
   137  		if got, want := calls, 5; got != want {
   138  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   139  		}
   140  		obs := trk.GetObservers(thing1)
   141  		if got, want := len(obs), 1; got != want {
   142  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   143  		}
   144  	}
   145  
   146  	// OnChanged non-accessor
   147  	{
   148  		// Check that passing in a resource that doesn't implement
   149  		// accessor won't panic.
   150  		trk.OnChanged("not an accessor")
   151  
   152  		if got, want := calls, 5; got != want {
   153  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   154  		}
   155  	}
   156  
   157  	// OnChanged non-accessor in DeletedFinalStateUnknown
   158  	{
   159  		// Check that passing in a DeletedFinalStateUnknown instance
   160  		// with a resource that doesn't implement accessor won't get
   161  		// Tracked called, and won't panic.
   162  		trk.OnChanged(cache.DeletedFinalStateUnknown{
   163  			Key: "ns/foo",
   164  			Obj: "not an accessor",
   165  		})
   166  
   167  		if got, want := calls, 5; got != want {
   168  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   169  		}
   170  	}
   171  
   172  	// Tracked gets called by DeletedFinalStateUnknown
   173  	{
   174  		trk.OnChanged(cache.DeletedFinalStateUnknown{
   175  			Key: "ns/foo",
   176  			Obj: thing1,
   177  		})
   178  		if got, want := calls, 6; got != want {
   179  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   180  		}
   181  	}
   182  
   183  	// Stops tracking explicitly
   184  	{
   185  		trk.OnDeletedObserver(thing2)
   186  		trk.OnChanged(thing1)
   187  		if got, want := calls, 6; got != want {
   188  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   189  		}
   190  		obs := trk.GetObservers(thing1)
   191  		if got, want := len(obs), 0; got != want {
   192  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   193  		}
   194  	}
   195  
   196  	// Track bad object
   197  	{
   198  		if err := trk.Track(ref.ObjectReference(), struct{}{}); err == nil {
   199  			t.Fatal("Track() = nil, wanted error")
   200  		}
   201  		obs := trk.GetObservers(thing1)
   202  		if got, want := len(obs), 0; got != want {
   203  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   204  		}
   205  	}
   206  }
   207  
   208  func TestAllowedObjectReferences(t *testing.T) {
   209  	trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond)
   210  	thing1 := &Resource{
   211  		TypeMeta: metav1.TypeMeta{
   212  			APIVersion: "ref.knative.dev/v1alpha1",
   213  			Kind:       "Thing1",
   214  		},
   215  		ObjectMeta: metav1.ObjectMeta{
   216  			Namespace: "ns",
   217  			Name:      "foo",
   218  		},
   219  	}
   220  	tests := []struct {
   221  		name   string
   222  		objRef corev1.ObjectReference
   223  	}{{
   224  		name: "Pod",
   225  		objRef: corev1.ObjectReference{
   226  			APIVersion: "v1",
   227  			Kind:       "Pod",
   228  			Namespace:  "default",
   229  			Name:       "test",
   230  		},
   231  	}, {
   232  		name: "Namespace",
   233  		objRef: corev1.ObjectReference{
   234  			APIVersion: "v1",
   235  			Kind:       "Namespace",
   236  			Name:       "default",
   237  		},
   238  	}, {
   239  		name: "Non-core resource",
   240  		objRef: corev1.ObjectReference{
   241  			APIVersion: "custom.example.com/v1alpha17",
   242  			Kind:       "Widget",
   243  			Namespace:  "default",
   244  			Name:       "test",
   245  		},
   246  	}, {
   247  		name: "Complex Kind",
   248  		objRef: corev1.ObjectReference{
   249  			APIVersion: "custom.example.com/v1alpha17",
   250  			Kind:       "Widget_v3",
   251  			Namespace:  "default",
   252  			Name:       "test",
   253  		},
   254  	}, {
   255  		name: "Dashed Namespace",
   256  		objRef: corev1.ObjectReference{
   257  			APIVersion: "v1",
   258  			Kind:       "ConfigMap",
   259  			Namespace:  "not-default",
   260  			Name:       "test",
   261  		},
   262  	}, {
   263  		name: "Complex Name",
   264  		objRef: corev1.ObjectReference{
   265  			APIVersion: "v1",
   266  			Kind:       "ConfigMap",
   267  			Namespace:  "default",
   268  			Name:       "test.example.cluster.local",
   269  		},
   270  	}}
   271  
   272  	for _, test := range tests {
   273  		t.Run(test.name, func(t *testing.T) {
   274  			if err := trk.Track(test.objRef, thing1); err != nil {
   275  				t.Fatalf("Track() on %v returned error: %v", test.objRef, err)
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestBadObjectReferences(t *testing.T) {
   282  	trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond)
   283  	thing1 := &Resource{
   284  		TypeMeta: metav1.TypeMeta{
   285  			APIVersion: "ref.knative.dev/v1alpha1",
   286  			Kind:       "Thing1",
   287  		},
   288  		ObjectMeta: metav1.ObjectMeta{
   289  			Namespace: "ns",
   290  			Name:      "foo",
   291  		},
   292  	}
   293  
   294  	tests := []struct {
   295  		name   string
   296  		objRef corev1.ObjectReference
   297  		match  string
   298  	}{{
   299  		name: "Missing APIVersion",
   300  		objRef: corev1.ObjectReference{
   301  			// APIVersion: "build.knative.dev/v1alpha1",
   302  			Kind:      "Build",
   303  			Namespace: "default",
   304  			Name:      "kaniko",
   305  		},
   306  		match: "APIVersion",
   307  	}, {
   308  		name: "Bad char in APIVersion",
   309  		objRef: corev1.ObjectReference{
   310  			APIVersion: "build.knative.dev%v1alpha1",
   311  			Kind:       "Build",
   312  			Namespace:  "default",
   313  			Name:       "kaniko",
   314  		},
   315  		match: "APIVersion",
   316  	}, {
   317  		name: "Extra slashes in APIVersion",
   318  		objRef: corev1.ObjectReference{
   319  			APIVersion: "build.knative.dev/v1/alpha1",
   320  			Kind:       "Build",
   321  			Namespace:  "default",
   322  			Name:       "kaniko",
   323  		},
   324  		match: "APIVersion",
   325  	}, {
   326  		name: "Missing Kind",
   327  		objRef: corev1.ObjectReference{
   328  			APIVersion: "build.knative.dev/v1alpha1",
   329  			// Kind:      "Build",
   330  			Namespace: "default",
   331  			Name:      "kaniko",
   332  		},
   333  		match: "Kind",
   334  	}, {
   335  		name: "Invalid Kind",
   336  		objRef: corev1.ObjectReference{
   337  			APIVersion: "build.knative.dev/v1alpha1",
   338  			Kind:       "Build.1",
   339  			Namespace:  "default",
   340  			Name:       "kaniko",
   341  		},
   342  		match: "Kind",
   343  	}, {
   344  		name: "Capital in Namespace",
   345  		objRef: corev1.ObjectReference{
   346  			APIVersion: "build.knative.dev/v1alpha1",
   347  			Kind:       "Build",
   348  			Namespace:  "Default",
   349  			Name:       "kaniko",
   350  		},
   351  		match: "Namespace",
   352  	}, {
   353  		name: "Domain-separated Namespace",
   354  		objRef: corev1.ObjectReference{
   355  			APIVersion: "build.knative.dev/v1alpha1",
   356  			Kind:       "Build",
   357  			Namespace:  "not.default",
   358  			Name:       "kaniko",
   359  		},
   360  		match: "Namespace",
   361  	}, {
   362  		name: "Missing Name",
   363  		objRef: corev1.ObjectReference{
   364  			APIVersion: "build.knative.dev/v1alpha1",
   365  			Kind:       "Build",
   366  			Namespace:  "default",
   367  			// Name:      "kaniko",
   368  		},
   369  		match: "Name",
   370  	}, {
   371  		name: "Capital in Name",
   372  		objRef: corev1.ObjectReference{
   373  			APIVersion: "build.knative.dev/v1alpha1",
   374  			Kind:       "Build",
   375  			Namespace:  "default",
   376  			Name:       "Kaniko",
   377  		},
   378  		match: "Name",
   379  	}, {
   380  		name: "Bad char in Name",
   381  		objRef: corev1.ObjectReference{
   382  			APIVersion: "build.knative.dev/v1alpha1",
   383  			Kind:       "Build",
   384  			Namespace:  "default",
   385  			Name:       "kaniko_small",
   386  		},
   387  		match: "Name",
   388  	}, {
   389  		name:   "Missing All",
   390  		objRef: corev1.ObjectReference{
   391  			// APIVersion: "build.knative.dev/v1alpha1",
   392  			// Kind:       "Build",
   393  			// Namespace:  "default",
   394  			// Name:      "kaniko",
   395  		},
   396  		match: "\nAPIVersion:.*\nKind:",
   397  	}}
   398  
   399  	for _, test := range tests {
   400  		t.Run(test.name, func(t *testing.T) {
   401  			if err := trk.Track(test.objRef, thing1); err == nil {
   402  				t.Fatal("Track() = nil, wanted error")
   403  			} else {
   404  				match, e2 := regexp.MatchString(test.match, err.Error())
   405  				if e2 != nil {
   406  					t.Fatalf("Failed to compile %q: %v", e2, test.match)
   407  				} else if !match {
   408  					t.Fatalf("Track() = %v, wanted match: %s", err, test.match)
   409  				}
   410  			}
   411  		})
   412  	}
   413  }
   414  
   415  func TestBadReferences(t *testing.T) {
   416  	trk := New(func(key types.NamespacedName) {}, 10*time.Millisecond)
   417  	thing1 := &Resource{
   418  		TypeMeta: metav1.TypeMeta{
   419  			APIVersion: "ref.knative.dev/v1alpha1",
   420  			Kind:       "Thing1",
   421  		},
   422  		ObjectMeta: metav1.ObjectMeta{
   423  			Namespace: "ns",
   424  			Name:      "foo",
   425  		},
   426  	}
   427  
   428  	tests := []struct {
   429  		name   string
   430  		objRef Reference
   431  		match  string
   432  	}{{
   433  		name:   "Missing All",
   434  		objRef: Reference{},
   435  		match:  "\nAPIVersion:.*\nKind:",
   436  	}, {
   437  		name: "Name and Selector",
   438  		objRef: Reference{
   439  			APIVersion: "apps/v1",
   440  			Kind:       "Deployment",
   441  			Namespace:  "default",
   442  			Name:       "foo",
   443  			Selector:   &metav1.LabelSelector{},
   444  		},
   445  		match: "both Name and Selector",
   446  	}, {
   447  		name: "bad label key",
   448  		objRef: Reference{
   449  			APIVersion: "apps/v1",
   450  			Kind:       "Deployment",
   451  			Namespace:  "default",
   452  			Selector: &metav1.LabelSelector{
   453  				MatchLabels: map[string]string{
   454  					"a bad key": "bar",
   455  				},
   456  			},
   457  		},
   458  		match: "a bad key",
   459  	}}
   460  
   461  	for _, test := range tests {
   462  		t.Run(test.name, func(t *testing.T) {
   463  			if err := trk.TrackReference(test.objRef, thing1); err == nil {
   464  				t.Fatal("Track() = nil, wanted error")
   465  			} else {
   466  				match, e2 := regexp.MatchString(test.match, err.Error())
   467  				if e2 != nil {
   468  					t.Fatalf("Failed to compile %q: %v", e2, test.match)
   469  				} else if !match {
   470  					t.Fatalf("Track() = %v, wanted match: %s", err, test.match)
   471  				}
   472  			}
   473  		})
   474  	}
   475  }
   476  
   477  func TestHappyPathsInexact(t *testing.T) {
   478  	calls := 0
   479  	f := func(key types.NamespacedName) {
   480  		calls++
   481  	}
   482  
   483  	trk := New(f, 100*time.Millisecond)
   484  
   485  	thing1 := &Resource{
   486  		TypeMeta: metav1.TypeMeta{
   487  			APIVersion: "ref.knative.dev/v1alpha1",
   488  			Kind:       "Thing1",
   489  		},
   490  		ObjectMeta: metav1.ObjectMeta{
   491  			Namespace: "ns",
   492  			Name:      "foo",
   493  			Labels: map[string]string{
   494  				"foo": "bar",
   495  				// An extra label.
   496  				"baz": "blah",
   497  			},
   498  		},
   499  	}
   500  	or := kmeta.ObjectReference(thing1)
   501  	ref := Reference{
   502  		APIVersion: or.APIVersion,
   503  		Kind:       or.Kind,
   504  		Namespace:  or.Namespace,
   505  		Selector: &metav1.LabelSelector{
   506  			MatchLabels: map[string]string{
   507  				"foo": "bar",
   508  			},
   509  		},
   510  	}
   511  
   512  	thing2 := &Resource{
   513  		TypeMeta: metav1.TypeMeta{
   514  			APIVersion: "refer.knative.dev/v1alpha1",
   515  			Kind:       "Thing2",
   516  		},
   517  		ObjectMeta: metav1.ObjectMeta{
   518  			Namespace: "default",
   519  			Name:      "bar.baz.this-is-fine",
   520  		},
   521  	}
   522  
   523  	// Not tracked yet
   524  	{
   525  		trk.OnChanged(thing1)
   526  		if got, want := calls, 0; got != want {
   527  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   528  		}
   529  		obs := trk.GetObservers(thing1)
   530  		if got, want := len(obs), 0; got != want {
   531  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   532  		}
   533  	}
   534  
   535  	// Tracked gets called
   536  	{
   537  		if err := trk.TrackReference(ref, thing2); err != nil {
   538  			t.Fatal("Track() =", err)
   539  		}
   540  		// New registrations should result in an immediate callback.
   541  		if got, want := calls, 1; got != want {
   542  			t.Fatalf("Track() = %v, wanted %v", got, want)
   543  		}
   544  
   545  		trk.OnChanged(thing1)
   546  		if got, want := calls, 2; got != want {
   547  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   548  		}
   549  		obs := trk.GetObservers(thing1)
   550  		if got, want := len(obs), 1; got != want {
   551  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   552  		}
   553  	}
   554  
   555  	// Still gets called
   556  	{
   557  		trk.OnChanged(thing1)
   558  		if got, want := calls, 3; got != want {
   559  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   560  		}
   561  	}
   562  
   563  	// Check that after the sleep duration, we stop getting called.
   564  	time.Sleep(101 * time.Millisecond)
   565  	{
   566  		trk.OnChanged(thing1)
   567  		if got, want := calls, 3; got != want {
   568  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   569  		}
   570  		if _, stillThere := trk.(*impl).exact[ref]; stillThere {
   571  			t.Fatal("Timeout passed, but exact for objectReference is still there")
   572  		}
   573  		obs := trk.GetObservers(thing1)
   574  		if got, want := len(obs), 0; got != want {
   575  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   576  		}
   577  	}
   578  
   579  	// Starts getting called again
   580  	{
   581  		if err := trk.TrackReference(ref, thing2); err != nil {
   582  			t.Fatal("Track() =", err)
   583  		}
   584  		// New registrations should result in an immediate callback.
   585  		if got, want := calls, 4; got != want {
   586  			t.Fatalf("Track() = %v, wanted %v", got, want)
   587  		}
   588  
   589  		trk.OnChanged(thing1)
   590  		if got, want := calls, 5; got != want {
   591  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   592  		}
   593  		obs := trk.GetObservers(thing1)
   594  		if got, want := len(obs), 1; got != want {
   595  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   596  		}
   597  	}
   598  
   599  	// OnChanged non-accessor
   600  	{
   601  		// Check that passing in a resource that doesn't implement
   602  		// accessor won't panic.
   603  		trk.OnChanged("not an accessor")
   604  
   605  		if got, want := calls, 5; got != want {
   606  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   607  		}
   608  	}
   609  
   610  	// OnChanged non-accessor in DeletedFinalStateUnknown
   611  	{
   612  		// Check that passing in a DeletedFinalStateUnknown instance
   613  		// with a resource that doesn't implement accessor won't get
   614  		// Tracked called, and won't panic.
   615  		trk.OnChanged(cache.DeletedFinalStateUnknown{
   616  			Key: "ns/foo",
   617  			Obj: "not an accessor",
   618  		})
   619  
   620  		if got, want := calls, 5; got != want {
   621  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   622  		}
   623  	}
   624  
   625  	// Tracked gets called by DeletedFinalStateUnknown
   626  	{
   627  		trk.OnChanged(cache.DeletedFinalStateUnknown{
   628  			Key: "ns/foo",
   629  			Obj: thing1,
   630  		})
   631  		if got, want := calls, 6; got != want {
   632  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   633  		}
   634  	}
   635  
   636  	// Not called when something about the reference matching changes.
   637  	{
   638  		if err := trk.TrackReference(ref, thing2); err != nil {
   639  			t.Fatal("Track() =", err)
   640  		}
   641  		// New registrations should result in an immediate callback.
   642  		if got, want := calls, 6; got != want {
   643  			t.Fatalf("Track() = %v, wanted %v", got, want)
   644  		}
   645  		obs := trk.GetObservers(thing1)
   646  		if got, want := len(obs), 1; got != want {
   647  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   648  		}
   649  
   650  		thing1unlabeled := thing1.DeepCopy()
   651  		thing1unlabeled.Labels = nil
   652  		trk.OnChanged(thing1unlabeled)
   653  		if got, want := calls, 6; got != want {
   654  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   655  		}
   656  
   657  		thing1othernamespace := thing1.DeepCopy()
   658  		thing1othernamespace.Namespace = "another"
   659  		trk.OnChanged(thing1othernamespace)
   660  		if got, want := calls, 6; got != want {
   661  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   662  		}
   663  
   664  		thing1othergroup := thing1.DeepCopy()
   665  		thing1othergroup.APIVersion = "apps/v1"
   666  		trk.OnChanged(thing1othergroup)
   667  		if got, want := calls, 6; got != want {
   668  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   669  		}
   670  
   671  		thing1otherkind := thing1.DeepCopy()
   672  		thing1otherkind.Kind = "deployment"
   673  		trk.OnChanged(thing1otherkind)
   674  		if got, want := calls, 6; got != want {
   675  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   676  		}
   677  
   678  		// But with labels is still called
   679  		trk.OnChanged(thing1)
   680  		if got, want := calls, 7; got != want {
   681  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   682  		}
   683  	}
   684  
   685  	// Stops tracking explicitly
   686  	{
   687  		trk.OnDeletedObserver(thing2)
   688  		trk.OnChanged(thing1)
   689  		if got, want := calls, 7; got != want {
   690  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   691  		}
   692  		obs := trk.GetObservers(thing1)
   693  		if got, want := len(obs), 0; got != want {
   694  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   695  		}
   696  	}
   697  
   698  	// Track bad object
   699  	{
   700  		if err := trk.TrackReference(ref, struct{}{}); err == nil {
   701  			t.Fatal("Track() = nil, wanted error")
   702  		}
   703  		obs := trk.GetObservers(thing1)
   704  		if got, want := len(obs), 0; got != want {
   705  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   706  		}
   707  	}
   708  }
   709  
   710  func TestHappyPathsByBoth(t *testing.T) {
   711  	calls := 0
   712  	f := func(key types.NamespacedName) {
   713  		calls++
   714  	}
   715  
   716  	trk := New(f, 100*time.Millisecond)
   717  
   718  	thing1 := &Resource{
   719  		TypeMeta: metav1.TypeMeta{
   720  			APIVersion: "ref.knative.dev/v1alpha1",
   721  			Kind:       "Thing1",
   722  		},
   723  		ObjectMeta: metav1.ObjectMeta{
   724  			Namespace: "ns",
   725  			Name:      "foo",
   726  			Labels: map[string]string{
   727  				"foo": "bar",
   728  				// An extra label.
   729  				"baz": "blah",
   730  			},
   731  		},
   732  	}
   733  	or := kmeta.ObjectReference(thing1)
   734  	ref1 := Reference{
   735  		APIVersion: or.APIVersion,
   736  		Kind:       or.Kind,
   737  		Namespace:  or.Namespace,
   738  		Name:       or.Name,
   739  	}
   740  	ref2 := Reference{
   741  		APIVersion: or.APIVersion,
   742  		Kind:       or.Kind,
   743  		Namespace:  or.Namespace,
   744  		Selector: &metav1.LabelSelector{
   745  			MatchLabels: map[string]string{
   746  				"foo": "bar",
   747  			},
   748  		},
   749  	}
   750  
   751  	thing2 := &Resource{
   752  		TypeMeta: metav1.TypeMeta{
   753  			APIVersion: "refer.knative.dev/v1alpha1",
   754  			Kind:       "Thing2",
   755  		},
   756  		ObjectMeta: metav1.ObjectMeta{
   757  			Namespace: "default",
   758  			Name:      "bar.baz.this-is-fine",
   759  		},
   760  	}
   761  
   762  	// Not tracked yet
   763  	{
   764  		trk.OnChanged(thing1)
   765  		if got, want := calls, 0; got != want {
   766  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   767  		}
   768  	}
   769  
   770  	// Tracked gets called
   771  	{
   772  		if err := trk.TrackReference(ref1, thing2); err != nil {
   773  			t.Fatal("Track() =", err)
   774  		}
   775  		// New registrations should result in an immediate callback.
   776  		if got, want := calls, 1; got != want {
   777  			t.Fatalf("Track() = %v, wanted %v", got, want)
   778  		}
   779  		if got, want := len(trk.GetObservers(thing1)), 1; got != want {
   780  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   781  		}
   782  
   783  		if err := trk.TrackReference(ref2, thing2); err != nil {
   784  			t.Fatal("Track() =", err)
   785  		}
   786  		// New registrations should result in an immediate callback.
   787  		if got, want := calls, 2; got != want {
   788  			t.Fatalf("Track() = %v, wanted %v", got, want)
   789  		}
   790  		if got, want := len(trk.GetObservers(thing1)), 2; got != want {
   791  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   792  		}
   793  
   794  		// The callback should be called for each of the tracks (exact and inexact)
   795  		trk.OnChanged(thing1)
   796  		if got, want := calls, 4; got != want {
   797  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   798  		}
   799  	}
   800  
   801  	// Stops tracking explicitly
   802  	{
   803  		trk.OnDeletedObserver(thing2)
   804  		trk.OnChanged(thing1)
   805  		if got, want := calls, 4; got != want {
   806  			t.Fatalf("OnChanged() = %v, wanted %v", got, want)
   807  		}
   808  		if got, want := len(trk.GetObservers(thing1)), 0; got != want {
   809  			t.Fatalf("len(GetObservers()) = %v, wanted %v", got, want)
   810  		}
   811  		if got, want := len(trk.(*impl).exact), 0; got != want {
   812  			t.Fatalf("OnDeletedObserver() did not clean up exact map: len = %v, wanted %v", got, want)
   813  		}
   814  		if got, want := len(trk.(*impl).inexact), 0; got != want {
   815  			t.Fatalf("OnDeletedObserver() did not clean up inexact map: len = %v, wanted %v", got, want)
   816  		}
   817  	}
   818  
   819  	// Verify inexact map cleanup on expiry when exact tracker is also present.
   820  	{
   821  		if err := trk.TrackReference(ref1, thing2); err != nil {
   822  			t.Fatal("Track() =", err)
   823  		}
   824  		if err := trk.TrackReference(ref2, thing2); err != nil {
   825  			t.Fatal("Track() =", err)
   826  		}
   827  
   828  		time.Sleep(101 * time.Millisecond)
   829  
   830  		trk.GetObservers(thing1)
   831  
   832  		if got, want := len(trk.(*impl).exact), 0; got != want {
   833  			t.Fatalf("Timeout passed, but exact map not cleaned up: len = %v, wanted %v", got, want)
   834  		}
   835  		if got, want := len(trk.(*impl).inexact), 0; got != want {
   836  			t.Fatalf("Timeout passed, but inexact map not cleaned up: len = %v, wanted %v", got, want)
   837  		}
   838  	}
   839  }