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

     1  /*
     2  Copyright 2015 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  	"fmt"
    21  	"math/rand"
    22  	"sync"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	"k8s.io/apimachinery/pkg/watch"
    34  	fcache "k8s.io/client-go/tools/cache/testing"
    35  
    36  	fuzz "github.com/google/gofuzz"
    37  )
    38  
    39  func Example() {
    40  	// source simulates an apiserver object endpoint.
    41  	source := fcache.NewFakeControllerSource()
    42  
    43  	// This will hold the downstream state, as we know it.
    44  	downstream := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
    45  
    46  	// This will hold incoming changes. Note how we pass downstream in as a
    47  	// KeyLister, that way resync operations will result in the correct set
    48  	// of update/delete deltas.
    49  	fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
    50  		KeyFunction:  MetaNamespaceKeyFunc,
    51  		KnownObjects: downstream,
    52  	})
    53  
    54  	// Let's do threadsafe output to get predictable test results.
    55  	deletionCounter := make(chan string, 1000)
    56  
    57  	cfg := &Config{
    58  		Queue:            fifo,
    59  		ListerWatcher:    source,
    60  		ObjectType:       &v1.Pod{},
    61  		FullResyncPeriod: time.Millisecond * 100,
    62  		RetryOnError:     false,
    63  
    64  		// Let's implement a simple controller that just deletes
    65  		// everything that comes in.
    66  		Process: func(obj interface{}, isInInitialList bool) error {
    67  			// Obj is from the Pop method of the Queue we make above.
    68  			newest := obj.(Deltas).Newest()
    69  
    70  			if newest.Type != Deleted {
    71  				// Update our downstream store.
    72  				err := downstream.Add(newest.Object)
    73  				if err != nil {
    74  					return err
    75  				}
    76  
    77  				// Delete this object.
    78  				source.Delete(newest.Object.(runtime.Object))
    79  			} else {
    80  				// Update our downstream store.
    81  				err := downstream.Delete(newest.Object)
    82  				if err != nil {
    83  					return err
    84  				}
    85  
    86  				// fifo's KeyOf is easiest, because it handles
    87  				// DeletedFinalStateUnknown markers.
    88  				key, err := fifo.KeyOf(newest.Object)
    89  				if err != nil {
    90  					return err
    91  				}
    92  
    93  				// Report this deletion.
    94  				deletionCounter <- key
    95  			}
    96  			return nil
    97  		},
    98  	}
    99  
   100  	// Create the controller and run it until we close stop.
   101  	stop := make(chan struct{})
   102  	defer close(stop)
   103  	go New(cfg).Run(stop)
   104  
   105  	// Let's add a few objects to the source.
   106  	testIDs := []string{"a-hello", "b-controller", "c-framework"}
   107  	for _, name := range testIDs {
   108  		// Note that these pods are not valid-- the fake source doesn't
   109  		// call validation or anything.
   110  		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}})
   111  	}
   112  
   113  	// Let's wait for the controller to process the things we just added.
   114  	outputSet := sets.String{}
   115  	for i := 0; i < len(testIDs); i++ {
   116  		outputSet.Insert(<-deletionCounter)
   117  	}
   118  
   119  	for _, key := range outputSet.List() {
   120  		fmt.Println(key)
   121  	}
   122  	// Output:
   123  	// a-hello
   124  	// b-controller
   125  	// c-framework
   126  }
   127  
   128  func ExampleNewInformer() {
   129  	// source simulates an apiserver object endpoint.
   130  	source := fcache.NewFakeControllerSource()
   131  
   132  	// Let's do threadsafe output to get predictable test results.
   133  	deletionCounter := make(chan string, 1000)
   134  
   135  	// Make a controller that immediately deletes anything added to it, and
   136  	// logs anything deleted.
   137  	_, controller := NewInformer(
   138  		source,
   139  		&v1.Pod{},
   140  		time.Millisecond*100,
   141  		ResourceEventHandlerDetailedFuncs{
   142  			AddFunc: func(obj interface{}, isInInitialList bool) {
   143  				source.Delete(obj.(runtime.Object))
   144  			},
   145  			DeleteFunc: func(obj interface{}) {
   146  				key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
   147  				if err != nil {
   148  					key = "oops something went wrong with the key"
   149  				}
   150  
   151  				// Report this deletion.
   152  				deletionCounter <- key
   153  			},
   154  		},
   155  	)
   156  
   157  	// Run the controller and run it until we close stop.
   158  	stop := make(chan struct{})
   159  	defer close(stop)
   160  	go controller.Run(stop)
   161  
   162  	// Let's add a few objects to the source.
   163  	testIDs := []string{"a-hello", "b-controller", "c-framework"}
   164  	for _, name := range testIDs {
   165  		// Note that these pods are not valid-- the fake source doesn't
   166  		// call validation or anything.
   167  		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}})
   168  	}
   169  
   170  	// Let's wait for the controller to process the things we just added.
   171  	outputSet := sets.String{}
   172  	for i := 0; i < len(testIDs); i++ {
   173  		outputSet.Insert(<-deletionCounter)
   174  	}
   175  
   176  	for _, key := range outputSet.List() {
   177  		fmt.Println(key)
   178  	}
   179  	// Output:
   180  	// a-hello
   181  	// b-controller
   182  	// c-framework
   183  }
   184  
   185  func TestHammerController(t *testing.T) {
   186  	// This test executes a bunch of requests through the fake source and
   187  	// controller framework to make sure there's no locking/threading
   188  	// errors. If an error happens, it should hang forever or trigger the
   189  	// race detector.
   190  
   191  	// source simulates an apiserver object endpoint.
   192  	source := fcache.NewFakeControllerSource()
   193  
   194  	// Let's do threadsafe output to get predictable test results.
   195  	outputSetLock := sync.Mutex{}
   196  	// map of key to operations done on the key
   197  	outputSet := map[string][]string{}
   198  
   199  	recordFunc := func(eventType string, obj interface{}) {
   200  		key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
   201  		if err != nil {
   202  			t.Errorf("something wrong with key: %v", err)
   203  			key = "oops something went wrong with the key"
   204  		}
   205  
   206  		// Record some output when items are deleted.
   207  		outputSetLock.Lock()
   208  		defer outputSetLock.Unlock()
   209  		outputSet[key] = append(outputSet[key], eventType)
   210  	}
   211  
   212  	// Make a controller which just logs all the changes it gets.
   213  	_, controller := NewInformer(
   214  		source,
   215  		&v1.Pod{},
   216  		time.Millisecond*100,
   217  		ResourceEventHandlerDetailedFuncs{
   218  			AddFunc:    func(obj interface{}, isInInitialList bool) { recordFunc("add", obj) },
   219  			UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) },
   220  			DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) },
   221  		},
   222  	)
   223  
   224  	if controller.HasSynced() {
   225  		t.Errorf("Expected HasSynced() to return false before we started the controller")
   226  	}
   227  
   228  	// Run the controller and run it until we close stop.
   229  	stop := make(chan struct{})
   230  	go controller.Run(stop)
   231  
   232  	// Let's wait for the controller to do its initial sync
   233  	wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
   234  		return controller.HasSynced(), nil
   235  	})
   236  	if !controller.HasSynced() {
   237  		t.Errorf("Expected HasSynced() to return true after the initial sync")
   238  	}
   239  
   240  	wg := sync.WaitGroup{}
   241  	const threads = 3
   242  	wg.Add(threads)
   243  	for i := 0; i < threads; i++ {
   244  		go func() {
   245  			defer wg.Done()
   246  			// Let's add a few objects to the source.
   247  			currentNames := sets.String{}
   248  			rs := rand.NewSource(rand.Int63())
   249  			f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs)
   250  			for i := 0; i < 100; i++ {
   251  				var name string
   252  				var isNew bool
   253  				if currentNames.Len() == 0 || rand.Intn(3) == 1 {
   254  					f.Fuzz(&name)
   255  					isNew = true
   256  				} else {
   257  					l := currentNames.List()
   258  					name = l[rand.Intn(len(l))]
   259  				}
   260  
   261  				pod := &v1.Pod{}
   262  				f.Fuzz(pod)
   263  				pod.ObjectMeta.Name = name
   264  				pod.ObjectMeta.Namespace = "default"
   265  				// Add, update, or delete randomly.
   266  				// Note that these pods are not valid-- the fake source doesn't
   267  				// call validation or perform any other checking.
   268  				if isNew {
   269  					currentNames.Insert(name)
   270  					source.Add(pod)
   271  					continue
   272  				}
   273  				switch rand.Intn(2) {
   274  				case 0:
   275  					currentNames.Insert(name)
   276  					source.Modify(pod)
   277  				case 1:
   278  					currentNames.Delete(name)
   279  					source.Delete(pod)
   280  				}
   281  			}
   282  		}()
   283  	}
   284  	wg.Wait()
   285  
   286  	// Let's wait for the controller to finish processing the things we just added.
   287  	// TODO: look in the queue to see how many items need to be processed.
   288  	time.Sleep(100 * time.Millisecond)
   289  	close(stop)
   290  
   291  	// TODO: Verify that no goroutines were leaked here and that everything shut
   292  	// down cleanly.
   293  
   294  	outputSetLock.Lock()
   295  	t.Logf("got: %#v", outputSet)
   296  }
   297  
   298  func TestUpdate(t *testing.T) {
   299  	// This test is going to exercise the various paths that result in a
   300  	// call to update.
   301  
   302  	// source simulates an apiserver object endpoint.
   303  	source := fcache.NewFakeControllerSource()
   304  
   305  	const (
   306  		FROM = "from"
   307  		TO   = "to"
   308  	)
   309  
   310  	// These are the transitions we expect to see; because this is
   311  	// asynchronous, there are a lot of valid possibilities.
   312  	type pair struct{ from, to string }
   313  	allowedTransitions := map[pair]bool{
   314  		{FROM, TO}: true,
   315  
   316  		// Because a resync can happen when we've already observed one
   317  		// of the above but before the item is deleted.
   318  		{TO, TO}: true,
   319  		// Because a resync could happen before we observe an update.
   320  		{FROM, FROM}: true,
   321  	}
   322  
   323  	pod := func(name, check string, final bool) *v1.Pod {
   324  		p := &v1.Pod{
   325  			ObjectMeta: metav1.ObjectMeta{
   326  				Name:   name,
   327  				Labels: map[string]string{"check": check},
   328  			},
   329  		}
   330  		if final {
   331  			p.Labels["final"] = "true"
   332  		}
   333  		return p
   334  	}
   335  	deletePod := func(p *v1.Pod) bool {
   336  		return p.Labels["final"] == "true"
   337  	}
   338  
   339  	tests := []func(string){
   340  		func(name string) {
   341  			name = "a-" + name
   342  			source.Add(pod(name, FROM, false))
   343  			source.Modify(pod(name, TO, true))
   344  		},
   345  	}
   346  
   347  	const threads = 3
   348  
   349  	var testDoneWG sync.WaitGroup
   350  	testDoneWG.Add(threads * len(tests))
   351  
   352  	// Make a controller that deletes things once it observes an update.
   353  	// It calls Done() on the wait group on deletions so we can tell when
   354  	// everything we've added has been deleted.
   355  	watchCh := make(chan struct{})
   356  	_, controller := NewInformer(
   357  		&testLW{
   358  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   359  				watch, err := source.Watch(options)
   360  				close(watchCh)
   361  				return watch, err
   362  			},
   363  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   364  				return source.List(options)
   365  			},
   366  		},
   367  		&v1.Pod{},
   368  		0,
   369  		ResourceEventHandlerFuncs{
   370  			UpdateFunc: func(oldObj, newObj interface{}) {
   371  				o, n := oldObj.(*v1.Pod), newObj.(*v1.Pod)
   372  				from, to := o.Labels["check"], n.Labels["check"]
   373  				if !allowedTransitions[pair{from, to}] {
   374  					t.Errorf("observed transition %q -> %q for %v", from, to, n.Name)
   375  				}
   376  				if deletePod(n) {
   377  					source.Delete(n)
   378  				}
   379  			},
   380  			DeleteFunc: func(obj interface{}) {
   381  				testDoneWG.Done()
   382  			},
   383  		},
   384  	)
   385  
   386  	// Run the controller and run it until we close stop.
   387  	// Once Run() is called, calls to testDoneWG.Done() might start, so
   388  	// all testDoneWG.Add() calls must happen before this point
   389  	stop := make(chan struct{})
   390  	go controller.Run(stop)
   391  	<-watchCh
   392  
   393  	// run every test a few times, in parallel
   394  	var wg sync.WaitGroup
   395  	wg.Add(threads * len(tests))
   396  	for i := 0; i < threads; i++ {
   397  		for j, f := range tests {
   398  			go func(name string, f func(string)) {
   399  				defer wg.Done()
   400  				f(name)
   401  			}(fmt.Sprintf("%v-%v", i, j), f)
   402  		}
   403  	}
   404  	wg.Wait()
   405  
   406  	// Let's wait for the controller to process the things we just added.
   407  	testDoneWG.Wait()
   408  	close(stop)
   409  }
   410  
   411  func TestPanicPropagated(t *testing.T) {
   412  	// source simulates an apiserver object endpoint.
   413  	source := fcache.NewFakeControllerSource()
   414  
   415  	// Make a controller that just panic if the AddFunc is called.
   416  	_, controller := NewInformer(
   417  		source,
   418  		&v1.Pod{},
   419  		time.Millisecond*100,
   420  		ResourceEventHandlerDetailedFuncs{
   421  			AddFunc: func(obj interface{}, isInInitialList bool) {
   422  				// Create a panic.
   423  				panic("Just panic.")
   424  			},
   425  		},
   426  	)
   427  
   428  	// Run the controller and run it until we close stop.
   429  	stop := make(chan struct{})
   430  	defer close(stop)
   431  
   432  	propagated := make(chan interface{})
   433  	go func() {
   434  		defer func() {
   435  			if r := recover(); r != nil {
   436  				propagated <- r
   437  			}
   438  		}()
   439  		controller.Run(stop)
   440  	}()
   441  	// Let's add a object to the source. It will trigger a panic.
   442  	source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test"}})
   443  
   444  	// Check if the panic propagated up.
   445  	select {
   446  	case p := <-propagated:
   447  		if p == "Just panic." {
   448  			t.Logf("Test Passed")
   449  		} else {
   450  			t.Errorf("unrecognized panic in controller run: %v", p)
   451  		}
   452  	case <-time.After(wait.ForeverTestTimeout):
   453  		t.Errorf("timeout: the panic failed to propagate from the controller run method!")
   454  	}
   455  }
   456  
   457  func TestTransformingInformer(t *testing.T) {
   458  	// source simulates an apiserver object endpoint.
   459  	source := fcache.NewFakeControllerSource()
   460  
   461  	makePod := func(name, generation string) *v1.Pod {
   462  		return &v1.Pod{
   463  			ObjectMeta: metav1.ObjectMeta{
   464  				Name:      name,
   465  				Namespace: "namespace",
   466  				Labels:    map[string]string{"generation": generation},
   467  			},
   468  			Spec: v1.PodSpec{
   469  				Hostname:  "hostname",
   470  				Subdomain: "subdomain",
   471  			},
   472  		}
   473  	}
   474  	expectedPod := func(name, generation string) *v1.Pod {
   475  		pod := makePod(name, generation)
   476  		pod.Spec.Hostname = "new-hostname"
   477  		pod.Spec.Subdomain = ""
   478  		pod.Spec.NodeName = "nodename"
   479  		return pod
   480  	}
   481  
   482  	source.Add(makePod("pod1", "1"))
   483  	source.Modify(makePod("pod1", "2"))
   484  
   485  	type event struct {
   486  		eventType watch.EventType
   487  		previous  interface{}
   488  		current   interface{}
   489  	}
   490  	events := make(chan event, 10)
   491  	recordEvent := func(eventType watch.EventType, previous, current interface{}) {
   492  		events <- event{eventType: eventType, previous: previous, current: current}
   493  	}
   494  	verifyEvent := func(eventType watch.EventType, previous, current interface{}) {
   495  		select {
   496  		case event := <-events:
   497  			if event.eventType != eventType {
   498  				t.Errorf("expected type %v, got %v", eventType, event.eventType)
   499  			}
   500  			if !apiequality.Semantic.DeepEqual(event.previous, previous) {
   501  				t.Errorf("expected previous object %#v, got %#v", previous, event.previous)
   502  			}
   503  			if !apiequality.Semantic.DeepEqual(event.current, current) {
   504  				t.Errorf("expected object %#v, got %#v", current, event.current)
   505  			}
   506  		case <-time.After(wait.ForeverTestTimeout):
   507  			t.Errorf("failed to get event")
   508  		}
   509  	}
   510  
   511  	podTransformer := func(obj interface{}) (interface{}, error) {
   512  		pod, ok := obj.(*v1.Pod)
   513  		if !ok {
   514  			return nil, fmt.Errorf("unexpected object type: %T", obj)
   515  		}
   516  		pod.Spec.Hostname = "new-hostname"
   517  		pod.Spec.Subdomain = ""
   518  		pod.Spec.NodeName = "nodename"
   519  
   520  		// Clear out ResourceVersion to simplify comparisons.
   521  		pod.ResourceVersion = ""
   522  
   523  		return pod, nil
   524  	}
   525  
   526  	store, controller := NewTransformingInformer(
   527  		source,
   528  		&v1.Pod{},
   529  		0,
   530  		ResourceEventHandlerDetailedFuncs{
   531  			AddFunc:    func(obj interface{}, isInInitialList bool) { recordEvent(watch.Added, nil, obj) },
   532  			UpdateFunc: func(oldObj, newObj interface{}) { recordEvent(watch.Modified, oldObj, newObj) },
   533  			DeleteFunc: func(obj interface{}) { recordEvent(watch.Deleted, obj, nil) },
   534  		},
   535  		podTransformer,
   536  	)
   537  
   538  	verifyStore := func(expectedItems []interface{}) {
   539  		items := store.List()
   540  		if len(items) != len(expectedItems) {
   541  			t.Errorf("unexpected items %v, expected %v", items, expectedItems)
   542  		}
   543  		for _, expectedItem := range expectedItems {
   544  			found := false
   545  			for _, item := range items {
   546  				if apiequality.Semantic.DeepEqual(item, expectedItem) {
   547  					found = true
   548  				}
   549  			}
   550  			if !found {
   551  				t.Errorf("expected item %v not found in %v", expectedItem, items)
   552  			}
   553  		}
   554  	}
   555  
   556  	stopCh := make(chan struct{})
   557  	go controller.Run(stopCh)
   558  
   559  	verifyEvent(watch.Added, nil, expectedPod("pod1", "2"))
   560  	verifyStore([]interface{}{expectedPod("pod1", "2")})
   561  
   562  	source.Add(makePod("pod2", "1"))
   563  	verifyEvent(watch.Added, nil, expectedPod("pod2", "1"))
   564  	verifyStore([]interface{}{expectedPod("pod1", "2"), expectedPod("pod2", "1")})
   565  
   566  	source.Add(makePod("pod3", "1"))
   567  	verifyEvent(watch.Added, nil, expectedPod("pod3", "1"))
   568  
   569  	source.Modify(makePod("pod2", "2"))
   570  	verifyEvent(watch.Modified, expectedPod("pod2", "1"), expectedPod("pod2", "2"))
   571  
   572  	source.Delete(makePod("pod1", "2"))
   573  	verifyEvent(watch.Deleted, expectedPod("pod1", "2"), nil)
   574  	verifyStore([]interface{}{expectedPod("pod2", "2"), expectedPod("pod3", "1")})
   575  
   576  	close(stopCh)
   577  }
   578  
   579  func TestTransformingInformerRace(t *testing.T) {
   580  	// source simulates an apiserver object endpoint.
   581  	source := fcache.NewFakeControllerSource()
   582  
   583  	label := "to-be-transformed"
   584  	makePod := func(name string) *v1.Pod {
   585  		return &v1.Pod{
   586  			ObjectMeta: metav1.ObjectMeta{
   587  				Name:      name,
   588  				Namespace: "namespace",
   589  				Labels:    map[string]string{label: "true"},
   590  			},
   591  			Spec: v1.PodSpec{
   592  				Hostname: "hostname",
   593  			},
   594  		}
   595  	}
   596  
   597  	badTransform := atomic.Bool{}
   598  	podTransformer := func(obj interface{}) (interface{}, error) {
   599  		pod, ok := obj.(*v1.Pod)
   600  		if !ok {
   601  			return nil, fmt.Errorf("unexpected object type: %T", obj)
   602  		}
   603  		if pod.ObjectMeta.Labels[label] != "true" {
   604  			badTransform.Store(true)
   605  			return nil, fmt.Errorf("object already transformed: %#v", obj)
   606  		}
   607  		pod.ObjectMeta.Labels[label] = "false"
   608  		return pod, nil
   609  	}
   610  
   611  	numObjs := 5
   612  	for i := 0; i < numObjs; i++ {
   613  		source.Add(makePod(fmt.Sprintf("pod-%d", i)))
   614  	}
   615  
   616  	type event struct{}
   617  	events := make(chan event, numObjs)
   618  	recordEvent := func(eventType watch.EventType, previous, current interface{}) {
   619  		events <- event{}
   620  	}
   621  	checkEvents := func(count int) {
   622  		for i := 0; i < count; i++ {
   623  			<-events
   624  		}
   625  	}
   626  	store, controller := NewTransformingInformer(
   627  		source,
   628  		&v1.Pod{},
   629  		5*time.Millisecond,
   630  		ResourceEventHandlerDetailedFuncs{
   631  			AddFunc:    func(obj interface{}, isInInitialList bool) { recordEvent(watch.Added, nil, obj) },
   632  			UpdateFunc: func(oldObj, newObj interface{}) { recordEvent(watch.Modified, oldObj, newObj) },
   633  			DeleteFunc: func(obj interface{}) { recordEvent(watch.Deleted, obj, nil) },
   634  		},
   635  		podTransformer,
   636  	)
   637  
   638  	stopCh := make(chan struct{})
   639  	go controller.Run(stopCh)
   640  
   641  	checkEvents(numObjs)
   642  
   643  	// Periodically fetch objects to ensure no access races.
   644  	wg := sync.WaitGroup{}
   645  	errors := make(chan error, numObjs)
   646  	for i := 0; i < numObjs; i++ {
   647  		wg.Add(1)
   648  		go func(index int) {
   649  			defer wg.Done()
   650  			key := fmt.Sprintf("namespace/pod-%d", index)
   651  			for {
   652  				select {
   653  				case <-stopCh:
   654  					return
   655  				default:
   656  				}
   657  
   658  				obj, ok, err := store.GetByKey(key)
   659  				if !ok || err != nil {
   660  					errors <- fmt.Errorf("couldn't get the object for %v", key)
   661  					return
   662  				}
   663  				pod := obj.(*v1.Pod)
   664  				if pod.ObjectMeta.Labels[label] != "false" {
   665  					errors <- fmt.Errorf("unexpected object: %#v", pod)
   666  					return
   667  				}
   668  			}
   669  		}(i)
   670  	}
   671  
   672  	// Let resyncs to happen for some time.
   673  	time.Sleep(time.Second)
   674  
   675  	close(stopCh)
   676  	wg.Wait()
   677  	close(errors)
   678  	for err := range errors {
   679  		t.Error(err)
   680  	}
   681  
   682  	if badTransform.Load() {
   683  		t.Errorf("unexpected transformation happened")
   684  	}
   685  }
   686  
   687  func TestDeletionHandlingObjectToName(t *testing.T) {
   688  	cm := &v1.ConfigMap{
   689  		ObjectMeta: metav1.ObjectMeta{
   690  			Name:      "testname",
   691  			Namespace: "testnamespace",
   692  		},
   693  	}
   694  	stringKey, err := MetaNamespaceKeyFunc(cm)
   695  	if err != nil {
   696  		t.Error(err)
   697  	}
   698  	deleted := DeletedFinalStateUnknown{
   699  		Key: stringKey,
   700  		Obj: cm,
   701  	}
   702  	expected, err := ObjectToName(cm)
   703  	if err != nil {
   704  		t.Error(err)
   705  	}
   706  	actual, err := DeletionHandlingObjectToName(deleted)
   707  	if err != nil {
   708  		t.Error(err)
   709  	}
   710  	if expected != actual {
   711  		t.Errorf("Expected %#v, got %#v", expected, actual)
   712  	}
   713  }