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

     1  /*
     2  Copyright 2014 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  	"reflect"
    22  	"runtime"
    23  	"testing"
    24  	"time"
    25  )
    26  
    27  func testFifoObjectKeyFunc(obj interface{}) (string, error) {
    28  	return obj.(testFifoObject).name, nil
    29  }
    30  
    31  type testFifoObject struct {
    32  	name string
    33  	val  interface{}
    34  }
    35  
    36  func mkFifoObj(name string, val interface{}) testFifoObject {
    37  	return testFifoObject{name: name, val: val}
    38  }
    39  
    40  func TestFIFO_basic(t *testing.T) {
    41  	f := NewFIFO(testFifoObjectKeyFunc)
    42  	const amount = 500
    43  	go func() {
    44  		for i := 0; i < amount; i++ {
    45  			f.Add(mkFifoObj(string([]rune{'a', rune(i)}), i+1))
    46  		}
    47  	}()
    48  	go func() {
    49  		for u := uint64(0); u < amount; u++ {
    50  			f.Add(mkFifoObj(string([]rune{'b', rune(u)}), u+1))
    51  		}
    52  	}()
    53  
    54  	lastInt := int(0)
    55  	lastUint := uint64(0)
    56  	for i := 0; i < amount*2; i++ {
    57  		switch obj := Pop(f).(testFifoObject).val.(type) {
    58  		case int:
    59  			if obj <= lastInt {
    60  				t.Errorf("got %v (int) out of order, last was %v", obj, lastInt)
    61  			}
    62  			lastInt = obj
    63  		case uint64:
    64  			if obj <= lastUint {
    65  				t.Errorf("got %v (uint) out of order, last was %v", obj, lastUint)
    66  			} else {
    67  				lastUint = obj
    68  			}
    69  		default:
    70  			t.Fatalf("unexpected type %#v", obj)
    71  		}
    72  	}
    73  }
    74  
    75  func TestFIFO_requeueOnPop(t *testing.T) {
    76  	f := NewFIFO(testFifoObjectKeyFunc)
    77  
    78  	f.Add(mkFifoObj("foo", 10))
    79  	_, err := f.Pop(func(obj interface{}, isInInitialList bool) error {
    80  		if obj.(testFifoObject).name != "foo" {
    81  			t.Fatalf("unexpected object: %#v", obj)
    82  		}
    83  		return ErrRequeue{Err: nil}
    84  	})
    85  	if err != nil {
    86  		t.Fatalf("unexpected error: %v", err)
    87  	}
    88  	if _, ok, err := f.GetByKey("foo"); !ok || err != nil {
    89  		t.Fatalf("object should have been requeued: %t %v", ok, err)
    90  	}
    91  
    92  	_, err = f.Pop(func(obj interface{}, isInInitialList bool) error {
    93  		if obj.(testFifoObject).name != "foo" {
    94  			t.Fatalf("unexpected object: %#v", obj)
    95  		}
    96  		return ErrRequeue{Err: fmt.Errorf("test error")}
    97  	})
    98  	if err == nil || err.Error() != "test error" {
    99  		t.Fatalf("unexpected error: %v", err)
   100  	}
   101  	if _, ok, err := f.GetByKey("foo"); !ok || err != nil {
   102  		t.Fatalf("object should have been requeued: %t %v", ok, err)
   103  	}
   104  
   105  	_, err = f.Pop(func(obj interface{}, isInInitialList bool) error {
   106  		if obj.(testFifoObject).name != "foo" {
   107  			t.Fatalf("unexpected object: %#v", obj)
   108  		}
   109  		return nil
   110  	})
   111  	if err != nil {
   112  		t.Fatalf("unexpected error: %v", err)
   113  	}
   114  	if _, ok, err := f.GetByKey("foo"); ok || err != nil {
   115  		t.Fatalf("object should have been removed: %t %v", ok, err)
   116  	}
   117  }
   118  
   119  func TestFIFO_addUpdate(t *testing.T) {
   120  	f := NewFIFO(testFifoObjectKeyFunc)
   121  	f.Add(mkFifoObj("foo", 10))
   122  	f.Update(mkFifoObj("foo", 15))
   123  
   124  	if e, a := []interface{}{mkFifoObj("foo", 15)}, f.List(); !reflect.DeepEqual(e, a) {
   125  		t.Errorf("Expected %+v, got %+v", e, a)
   126  	}
   127  	if e, a := []string{"foo"}, f.ListKeys(); !reflect.DeepEqual(e, a) {
   128  		t.Errorf("Expected %+v, got %+v", e, a)
   129  	}
   130  
   131  	got := make(chan testFifoObject, 2)
   132  	go func() {
   133  		for {
   134  			got <- Pop(f).(testFifoObject)
   135  		}
   136  	}()
   137  
   138  	first := <-got
   139  	if e, a := 15, first.val; e != a {
   140  		t.Errorf("Didn't get updated value (%v), got %v", e, a)
   141  	}
   142  	select {
   143  	case unexpected := <-got:
   144  		t.Errorf("Got second value %v", unexpected.val)
   145  	case <-time.After(50 * time.Millisecond):
   146  	}
   147  	_, exists, _ := f.Get(mkFifoObj("foo", ""))
   148  	if exists {
   149  		t.Errorf("item did not get removed")
   150  	}
   151  }
   152  
   153  func TestFIFO_addReplace(t *testing.T) {
   154  	f := NewFIFO(testFifoObjectKeyFunc)
   155  	f.Add(mkFifoObj("foo", 10))
   156  	f.Replace([]interface{}{mkFifoObj("foo", 15)}, "15")
   157  	got := make(chan testFifoObject, 2)
   158  	go func() {
   159  		for {
   160  			got <- Pop(f).(testFifoObject)
   161  		}
   162  	}()
   163  
   164  	first := <-got
   165  	if e, a := 15, first.val; e != a {
   166  		t.Errorf("Didn't get updated value (%v), got %v", e, a)
   167  	}
   168  	select {
   169  	case unexpected := <-got:
   170  		t.Errorf("Got second value %v", unexpected.val)
   171  	case <-time.After(50 * time.Millisecond):
   172  	}
   173  	_, exists, _ := f.Get(mkFifoObj("foo", ""))
   174  	if exists {
   175  		t.Errorf("item did not get removed")
   176  	}
   177  }
   178  
   179  func TestFIFO_detectLineJumpers(t *testing.T) {
   180  	f := NewFIFO(testFifoObjectKeyFunc)
   181  
   182  	f.Add(mkFifoObj("foo", 10))
   183  	f.Add(mkFifoObj("bar", 1))
   184  	f.Add(mkFifoObj("foo", 11))
   185  	f.Add(mkFifoObj("foo", 13))
   186  	f.Add(mkFifoObj("zab", 30))
   187  
   188  	if e, a := 13, Pop(f).(testFifoObject).val; a != e {
   189  		t.Fatalf("expected %d, got %d", e, a)
   190  	}
   191  
   192  	f.Add(mkFifoObj("foo", 14)) // ensure foo doesn't jump back in line
   193  
   194  	if e, a := 1, Pop(f).(testFifoObject).val; a != e {
   195  		t.Fatalf("expected %d, got %d", e, a)
   196  	}
   197  
   198  	if e, a := 30, Pop(f).(testFifoObject).val; a != e {
   199  		t.Fatalf("expected %d, got %d", e, a)
   200  	}
   201  
   202  	if e, a := 14, Pop(f).(testFifoObject).val; a != e {
   203  		t.Fatalf("expected %d, got %d", e, a)
   204  	}
   205  }
   206  
   207  func TestFIFO_addIfNotPresent(t *testing.T) {
   208  	f := NewFIFO(testFifoObjectKeyFunc)
   209  
   210  	f.Add(mkFifoObj("a", 1))
   211  	f.Add(mkFifoObj("b", 2))
   212  	f.AddIfNotPresent(mkFifoObj("b", 3))
   213  	f.AddIfNotPresent(mkFifoObj("c", 4))
   214  
   215  	if e, a := 3, len(f.items); a != e {
   216  		t.Fatalf("expected queue length %d, got %d", e, a)
   217  	}
   218  
   219  	expectedValues := []int{1, 2, 4}
   220  	for _, expected := range expectedValues {
   221  		if actual := Pop(f).(testFifoObject).val; actual != expected {
   222  			t.Fatalf("expected value %d, got %d", expected, actual)
   223  		}
   224  	}
   225  }
   226  
   227  func TestFIFO_HasSynced(t *testing.T) {
   228  	tests := []struct {
   229  		actions        []func(f *FIFO)
   230  		expectedSynced bool
   231  	}{
   232  		{
   233  			actions:        []func(f *FIFO){},
   234  			expectedSynced: false,
   235  		},
   236  		{
   237  			actions: []func(f *FIFO){
   238  				func(f *FIFO) { f.Add(mkFifoObj("a", 1)) },
   239  			},
   240  			expectedSynced: true,
   241  		},
   242  		{
   243  			actions: []func(f *FIFO){
   244  				func(f *FIFO) { f.Replace([]interface{}{}, "0") },
   245  			},
   246  			expectedSynced: true,
   247  		},
   248  		{
   249  			actions: []func(f *FIFO){
   250  				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
   251  			},
   252  			expectedSynced: false,
   253  		},
   254  		{
   255  			actions: []func(f *FIFO){
   256  				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
   257  				func(f *FIFO) { Pop(f) },
   258  			},
   259  			expectedSynced: false,
   260  		},
   261  		{
   262  			actions: []func(f *FIFO){
   263  				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") },
   264  				func(f *FIFO) { Pop(f) },
   265  				func(f *FIFO) { Pop(f) },
   266  			},
   267  			expectedSynced: true,
   268  		},
   269  	}
   270  
   271  	for i, test := range tests {
   272  		f := NewFIFO(testFifoObjectKeyFunc)
   273  
   274  		for _, action := range test.actions {
   275  			action(f)
   276  		}
   277  		if e, a := test.expectedSynced, f.HasSynced(); a != e {
   278  			t.Errorf("test case %v failed, expected: %v , got %v", i, e, a)
   279  		}
   280  	}
   281  }
   282  
   283  // TestFIFO_PopShouldUnblockWhenClosed checks that any blocking Pop on an empty queue
   284  // should unblock and return after Close is called.
   285  func TestFIFO_PopShouldUnblockWhenClosed(t *testing.T) {
   286  	f := NewFIFO(testFifoObjectKeyFunc)
   287  
   288  	c := make(chan struct{})
   289  	const jobs = 10
   290  	for i := 0; i < jobs; i++ {
   291  		go func() {
   292  			f.Pop(func(obj interface{}, isInInitialList bool) error {
   293  				return nil
   294  			})
   295  			c <- struct{}{}
   296  		}()
   297  	}
   298  
   299  	runtime.Gosched()
   300  	f.Close()
   301  
   302  	for i := 0; i < jobs; i++ {
   303  		select {
   304  		case <-c:
   305  		case <-time.After(500 * time.Millisecond):
   306  			t.Fatalf("timed out waiting for Pop to return after Close")
   307  		}
   308  	}
   309  }