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 }