github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/matcher/cache/cache_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package cache 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "math" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/m3db/m3/src/metrics/matcher/namespace" 33 "github.com/m3db/m3/src/metrics/metric/id" 34 "github.com/m3db/m3/src/metrics/rules" 35 "github.com/m3db/m3/src/x/clock" 36 37 "github.com/stretchr/testify/require" 38 ) 39 40 var ( 41 errTestWaitUntilTimeout = errors.New("test timed out waiting for condition") 42 testEmptyMatchResult = rules.EmptyMatchResult 43 testWaitTimeout = 200 * time.Millisecond 44 testValues = []testValue{ 45 {namespace: []byte("nsfoo"), id: []byte("foo"), result: testValidResults[0]}, 46 {namespace: []byte("nsbar"), id: []byte("bar"), result: testValidResults[1]}, 47 } 48 ) 49 50 func TestCacheMatchNamespaceDoesNotExist(t *testing.T) { 51 opts := testCacheOptions() 52 c := NewCache(opts) 53 54 res, err := c.ForwardMatch(namespace.NewTestID("foo", "nonexistentNs"), 0, 0, rules.MatchOptions{}) 55 require.NoError(t, err) 56 require.Equal(t, testEmptyMatchResult, res) 57 } 58 59 func TestCacheMatchIDCachedValidNoPromotion(t *testing.T) { 60 opts := testCacheOptions() 61 c := NewCache(opts).(*cache) 62 now := time.Now() 63 c.nowFn = func() time.Time { return now } 64 source := newMockSource() 65 populateCache(c, testValues, now.Add(time.Minute), source, populateBoth) 66 67 // Get the second id and assert we didn't perform a promotion. 68 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 69 require.NoError(t, err) 70 require.Equal(t, testValues[1].result, res) 71 validateCache(t, c, testValues) 72 } 73 74 func TestCacheMatchIDCachedValidWithPromotion(t *testing.T) { 75 opts := testCacheOptions() 76 c := NewCache(opts).(*cache) 77 now := time.Now() 78 c.nowFn = func() time.Time { return now } 79 source := newMockSource() 80 populateCache(c, testValues, now, source, populateBoth) 81 82 // Move the time and assert we performed a promotion. 83 now = now.Add(time.Minute) 84 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 85 require.NoError(t, err) 86 require.Equal(t, testValues[1].result, res) 87 expected := []testValue{testValues[1], testValues[0]} 88 validateCache(t, c, expected) 89 } 90 91 func TestCacheMatchIDCachedInvalidSourceValidInvalidateAll(t *testing.T) { 92 opts := testCacheOptions() 93 c := NewCache(opts).(*cache) 94 now := time.Now() 95 c.nowFn = func() time.Time { return now } 96 source := newMockSource() 97 input := []testValue{ 98 { 99 namespace: testValues[1].namespace, 100 id: testValues[0].id, 101 result: testValues[0].result, 102 }, 103 { 104 namespace: testValues[1].namespace, 105 id: testValues[1].id, 106 result: rules.NewMatchResult(0, now.Add(time.Second).UnixNano(), nil, nil, true), 107 }, 108 } 109 populateCache(c, input, now, source, populateBoth) 110 entry, ok := c.namespaces.Get(testValues[1].namespace) 111 require.True(t, ok) 112 require.Equal(t, 2, entry.elems.Len()) 113 114 var ( 115 ns = testValues[1].namespace 116 id = testValues[1].id 117 newVersion = 3 118 ) 119 result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true) 120 source.setVersion(newVersion) 121 source.setResult(id, result) 122 123 entry, ok = c.namespaces.Get(ns) 124 require.True(t, ok) 125 require.Equal(t, 2, entry.elems.Len()) 126 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.Add(time.Minute).UnixNano(), rules.MatchOptions{}) 127 require.NoError(t, err) 128 require.Equal(t, result, res) 129 130 // Wait for deletion to happen 131 conditionFn := func() bool { 132 c.list.Lock() 133 len := c.list.Len() 134 c.list.Unlock() 135 return len == 1 136 } 137 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 138 139 expected := []testValue{{namespace: ns, id: id, result: result}} 140 require.Equal(t, 1, c.namespaces.Len()) 141 entry, ok = c.namespaces.Get(ns) 142 require.True(t, ok) 143 require.Equal(t, 1, entry.elems.Len()) 144 elem, exists := entry.elems.Get(id) 145 require.True(t, exists) 146 require.True(t, elem == c.list.Front()) 147 validateCache(t, c, expected) 148 } 149 150 func TestCacheMatchIDCachedInvalidSourceValidInvalidateAllNoEviction(t *testing.T) { 151 opts := testCacheOptions() 152 c := NewCache(opts).(*cache) 153 now := time.Now() 154 c.nowFn = func() time.Time { return now } 155 source := newMockSource() 156 input := []testValue{ 157 {namespace: testValues[1].namespace, id: testValues[0].id, result: testValues[0].result}, 158 {namespace: testValues[1].namespace, id: testValues[1].id, result: testExpiredResults[1]}, 159 } 160 populateCache(c, input, now, source, populateBoth) 161 entry, ok := c.namespaces.Get(testValues[1].namespace) 162 require.True(t, ok) 163 require.Equal(t, 2, entry.elems.Len()) 164 165 var ( 166 ns = testValues[1].namespace 167 id = testValues[1].id 168 newVersion = 3 169 ) 170 result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true) 171 source.setVersion(newVersion) 172 source.setResult(id, result) 173 174 entry, ok = c.namespaces.Get(ns) 175 require.True(t, ok) 176 require.Equal(t, 2, entry.elems.Len()) 177 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 178 require.NoError(t, err) 179 require.Equal(t, result, res) 180 181 // Wait for deletion to happen 182 conditionFn := func() bool { 183 c.list.Lock() 184 len := c.list.Len() 185 c.list.Unlock() 186 return len == 1 187 } 188 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 189 190 expected := []testValue{{namespace: ns, id: id, result: result}} 191 require.Equal(t, 1, c.namespaces.Len()) 192 entry, ok = c.namespaces.Get(ns) 193 require.True(t, ok) 194 require.Equal(t, 1, entry.elems.Len()) 195 elem, exists := entry.elems.Get(id) 196 require.True(t, exists) 197 require.True(t, elem == c.list.Front()) 198 validateCache(t, c, expected) 199 } 200 201 func TestCacheMatchIDCachedInvalidSourceValidInvalidateOneNoEviction(t *testing.T) { 202 opts := testCacheOptions().SetInvalidationMode(InvalidateOne) 203 c := NewCache(opts).(*cache) 204 now := time.Now() 205 c.nowFn = func() time.Time { return now } 206 source := newMockSource() 207 input := []testValue{ 208 {namespace: testValues[1].namespace, id: testValues[0].id, result: testValues[0].result}, 209 {namespace: testValues[1].namespace, id: testValues[1].id, result: testExpiredResults[1]}, 210 } 211 populateCache(c, input, now, source, populateBoth) 212 213 var ( 214 ns = testValues[1].namespace 215 id = testValues[1].id 216 newVersion = 3 217 ) 218 result := rules.NewMatchResult(0, math.MaxInt64, testForExistingID, testForNewRollupIDs, true) 219 source.setVersion(newVersion) 220 source.setResult(id, result) 221 222 entry, ok := c.namespaces.Get(ns) 223 require.True(t, ok) 224 require.Equal(t, 2, entry.elems.Len()) 225 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 226 require.NoError(t, err) 227 require.Equal(t, result, res) 228 229 // Wait for deletion to happen. 230 conditionFn := func() bool { 231 c.list.Lock() 232 len := c.list.Len() 233 c.list.Unlock() 234 return len == 2 235 } 236 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 237 238 expected := []testValue{ 239 {namespace: ns, id: id, result: result}, 240 {namespace: ns, id: testValues[0].id, result: testValues[0].result}, 241 } 242 entry, ok = c.namespaces.Get(ns) 243 require.True(t, ok) 244 require.Equal(t, 1, c.namespaces.Len()) 245 require.Equal(t, 2, entry.elems.Len()) 246 elem, exists := entry.elems.Get(id) 247 require.True(t, exists) 248 require.True(t, elem == c.list.Front()) 249 validateCache(t, c, expected) 250 } 251 252 func TestCacheMatchIDCachedInvalidSourceValidWithEviction(t *testing.T) { 253 opts := testCacheOptions().SetInvalidationMode(InvalidateOne) 254 c := NewCache(opts).(*cache) 255 now := time.Now() 256 c.nowFn = func() time.Time { return now } 257 source := newMockSource() 258 input := []testValue{ 259 {namespace: []byte("ns1"), id: []byte("foo"), result: testExpiredResults[0]}, 260 {namespace: []byte("ns1"), id: []byte("bar"), result: testExpiredResults[0]}, 261 {namespace: []byte("ns2"), id: []byte("baz"), result: testExpiredResults[1]}, 262 {namespace: []byte("ns2"), id: []byte("cat"), result: testExpiredResults[1]}, 263 } 264 populateCache(c, input, now, source, populateBoth) 265 266 newVersion := 3 267 newResult := rules.NewMatchResult( 268 0, 269 math.MaxInt64, 270 testForExistingID, 271 testForNewRollupIDs, 272 true, 273 ) 274 source.setVersion(newVersion) 275 for _, id := range []string{"foo", "bar", "baz", "cat", "lol"} { 276 source.setResult([]byte(id), newResult) 277 } 278 279 // Retrieve a few ids and assert we don't evict due to eviction batching. 280 for _, value := range []testValue{ 281 {namespace: []byte("ns1"), id: []byte("foo")}, 282 {namespace: []byte("ns1"), id: []byte("bar")}, 283 {namespace: []byte("ns2"), id: []byte("baz")}, 284 {namespace: []byte("ns2"), id: []byte("cat")}, 285 } { 286 res, err := c.ForwardMatch(value.ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 287 require.NoError(t, err) 288 require.Equal(t, newResult, res) 289 } 290 conditionFn := func() bool { 291 c.list.Lock() 292 len := c.list.Len() 293 c.list.Unlock() 294 return len == c.capacity 295 } 296 require.Equal(t, errTestWaitUntilTimeout, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 297 expected := []testValue{ 298 {namespace: []byte("ns2"), id: []byte("cat"), result: newResult}, 299 {namespace: []byte("ns2"), id: []byte("baz"), result: newResult}, 300 {namespace: []byte("ns1"), id: []byte("bar"), result: newResult}, 301 {namespace: []byte("ns1"), id: []byte("foo"), result: newResult}, 302 } 303 validateCache(t, c, expected) 304 305 // Retrieve one more id and assert we perform async eviction. 306 c.invalidationMode = InvalidateAll 307 res, err := c.ForwardMatch(namespace.NewTestID("lol", "ns1"), 308 now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 309 require.NoError(t, err) 310 require.Equal(t, newResult, res) 311 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 312 expected = []testValue{ 313 {namespace: []byte("ns1"), id: []byte("lol"), result: newResult}, 314 {namespace: []byte("ns2"), id: []byte("cat"), result: newResult}, 315 } 316 validateCache(t, c, expected) 317 } 318 319 func TestCacheMatchIDNotCachedAndDoesNotExistInSource(t *testing.T) { 320 opts := testCacheOptions() 321 c := NewCache(opts).(*cache) 322 now := time.Now() 323 c.nowFn = func() time.Time { return now } 324 source := newMockSource() 325 populateCache(c, testValues, now.Add(time.Minute), source, populateBoth) 326 327 res, err := c.ForwardMatch(namespace.NewTestID("nonExistent", "nsfoo"), 328 now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 329 require.NoError(t, err) 330 require.Equal(t, testEmptyMatchResult, res) 331 } 332 333 func TestCacheMatchIDNotCachedSourceValidNoEviction(t *testing.T) { 334 opts := testCacheOptions() 335 c := NewCache(opts).(*cache) 336 now := time.Now() 337 c.nowFn = func() time.Time { return now } 338 source := newMockSource() 339 populateCache(c, []testValue{testValues[1]}, now, source, populateSource) 340 341 var ( 342 ns = testValues[1].namespace 343 id = testValues[1].id 344 result = testValues[1].result 345 ) 346 entry, ok := c.namespaces.Get(ns) 347 require.True(t, ok) 348 require.Equal(t, 0, entry.elems.Len()) 349 res, err := c.ForwardMatch(testValues[1].ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 350 require.NoError(t, err) 351 require.Equal(t, result, res) 352 353 expected := []testValue{testValues[1]} 354 elem, exists := entry.elems.Get(id) 355 require.True(t, exists) 356 require.True(t, elem == c.list.Front()) 357 validateCache(t, c, expected) 358 } 359 360 func TestCacheMatchParallel(t *testing.T) { 361 opts := testCacheOptions() 362 c := NewCache(opts).(*cache) 363 now := time.Now() 364 c.nowFn = func() time.Time { return now } 365 source := newMockSource() 366 input := []testValue{ 367 {namespace: []byte("ns1"), id: []byte("foo"), result: testExpiredResults[0]}, 368 {namespace: []byte("ns2"), id: []byte("baz"), result: testExpiredResults[1]}, 369 } 370 populateCache(c, input, now, source, populateBoth) 371 372 newVersion := 3 373 nowNanos := time.Now().UnixNano() 374 newResult := rules.NewMatchResult(0, nowNanos, testForExistingID, testForNewRollupIDs, true) 375 source.setVersion(newVersion) 376 for _, id := range []string{"foo", "baz"} { 377 source.setResult([]byte(id), newResult) 378 } 379 380 var wg sync.WaitGroup 381 for i := 0; i < 1000; i++ { 382 v := input[i%2] 383 wg.Add(1) 384 go func() { 385 defer wg.Done() 386 res, err := c.ForwardMatch(v.ID(), now.UnixNano(), now.UnixNano(), rules.MatchOptions{}) 387 require.NoError(t, err) 388 require.Equal(t, newResult, res) 389 }() 390 } 391 wg.Wait() 392 393 if bytes.Equal(c.list.Front().id, input[0].id) { 394 validateCache(t, c, []testValue{ 395 {namespace: []byte("ns1"), id: []byte("foo"), result: newResult}, 396 {namespace: []byte("ns2"), id: []byte("baz"), result: newResult}, 397 }) 398 } else { 399 validateCache(t, c, []testValue{ 400 {namespace: []byte("ns2"), id: []byte("baz"), result: newResult}, 401 {namespace: []byte("ns1"), id: []byte("foo"), result: newResult}, 402 }) 403 } 404 } 405 406 func TestCacheRegisterNamespaceDoesNotExist(t *testing.T) { 407 opts := testCacheOptions() 408 c := NewCache(opts).(*cache) 409 now := time.Now() 410 c.nowFn = func() time.Time { return now } 411 require.Equal(t, 0, c.namespaces.Len()) 412 413 var ( 414 ns = []byte("ns") 415 source = newMockSource() 416 ) 417 c.Register(ns, source) 418 require.Equal(t, 1, c.namespaces.Len()) 419 entry, ok := c.namespaces.Get(ns) 420 require.True(t, ok) 421 require.Equal(t, 0, entry.elems.Len()) 422 require.Equal(t, source, entry.source) 423 } 424 425 func TestCacheRegisterNamespaceExists(t *testing.T) { 426 opts := testCacheOptions() 427 c := NewCache(opts).(*cache) 428 now := time.Now() 429 c.nowFn = func() time.Time { return now } 430 populateCache(c, []testValue{testValues[0]}, now, nil, populateBoth) 431 432 ns := testValues[0].namespace 433 require.Equal(t, 1, c.namespaces.Len()) 434 entry, ok := c.namespaces.Get(ns) 435 require.True(t, ok) 436 require.Equal(t, 1, entry.elems.Len()) 437 require.Nil(t, entry.source) 438 439 source := newMockSource() 440 c.Register(ns, source) 441 442 // Wait till the outdated cached data are deleted. 443 conditionFn := func() bool { 444 c.list.Lock() 445 len := c.list.Len() 446 c.list.Unlock() 447 return len == 0 448 } 449 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 450 451 require.Equal(t, 1, c.namespaces.Len()) 452 entry, ok = c.namespaces.Get(ns) 453 require.True(t, ok) 454 require.Equal(t, 0, entry.elems.Len()) 455 require.Equal(t, source, entry.source) 456 } 457 458 func TestCacheRefreshNamespaceDoesNotExist(t *testing.T) { 459 opts := testCacheOptions() 460 c := NewCache(opts).(*cache) 461 require.Equal(t, 0, c.namespaces.Len()) 462 463 var ( 464 ns = []byte("ns") 465 source = newMockSource() 466 ) 467 c.Refresh(ns, source) 468 require.Equal(t, 0, c.namespaces.Len()) 469 } 470 471 func TestCacheRefreshStaleSource(t *testing.T) { 472 opts := testCacheOptions() 473 c := NewCache(opts).(*cache) 474 require.Equal(t, 0, c.namespaces.Len()) 475 476 var ( 477 ns = []byte("ns") 478 source1 = newMockSource() 479 source2 = newMockSource() 480 ) 481 c.Register(ns, source1) 482 require.Equal(t, 1, c.namespaces.Len()) 483 484 c.Refresh(ns, source2) 485 require.Equal(t, 1, c.namespaces.Len()) 486 entry, ok := c.namespaces.Get(ns) 487 require.True(t, ok) 488 require.Equal(t, source1, entry.source) 489 } 490 491 func TestCacheRefreshSuccess(t *testing.T) { 492 opts := testCacheOptions() 493 c := NewCache(opts).(*cache) 494 now := time.Now() 495 c.nowFn = func() time.Time { return now } 496 497 var ( 498 ns = testValues[0].namespace 499 src = newMockSource() 500 ) 501 populateCache(c, []testValue{testValues[0]}, now, src, populateBoth) 502 require.Equal(t, 1, c.namespaces.Len()) 503 entry, ok := c.namespaces.Get(ns) 504 require.True(t, ok) 505 require.Equal(t, 1, entry.elems.Len()) 506 require.Equal(t, src, entry.source) 507 508 c.Refresh(ns, src) 509 entry, ok = c.namespaces.Get(ns) 510 require.True(t, ok) 511 require.Equal(t, 1, c.namespaces.Len()) 512 require.Equal(t, 0, entry.elems.Len()) 513 require.Equal(t, src, entry.source) 514 } 515 516 func TestCacheUnregisterNamespaceDoesNotExist(t *testing.T) { 517 opts := testCacheOptions() 518 c := NewCache(opts).(*cache) 519 now := time.Now() 520 c.nowFn = func() time.Time { return now } 521 populateCache(c, testValues, now, nil, populateBoth) 522 523 // Delete a namespace that doesn't exist. 524 c.Unregister([]byte("nonexistent")) 525 526 // Wait a little in case anything unexpected would happen. 527 time.Sleep(100 * time.Millisecond) 528 529 validateCache(t, c, testValues) 530 } 531 532 func TestCacheUnregisterNamespaceExists(t *testing.T) { 533 opts := testCacheOptions() 534 c := NewCache(opts).(*cache) 535 now := time.Now() 536 c.nowFn = func() time.Time { return now } 537 populateCache(c, testValues, now, nil, populateBoth) 538 539 // Delete a namespace. 540 for _, value := range testValues { 541 c.Unregister(value.namespace) 542 } 543 544 // Wait till the namespace is deleted. 545 conditionFn := func() bool { 546 c.list.Lock() 547 len := c.list.Len() 548 c.list.Unlock() 549 return len == 0 550 } 551 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 552 553 // Assert the value has been deleted. 554 validateCache(t, c, nil) 555 } 556 557 func TestCacheDeleteBatching(t *testing.T) { 558 opts := testCacheOptions().SetDeletionBatchSize(10) 559 c := NewCache(opts).(*cache) 560 now := time.Now() 561 c.nowFn = func() time.Time { return now } 562 var intervals []time.Duration 563 c.sleepFn = func(d time.Duration) { 564 intervals = append(intervals, d) 565 } 566 567 var elemMaps []*elemMap 568 for _, value := range testValues { 569 m := newElemMap(elemMapOptions{}) 570 for i := 0; i < 37; i++ { 571 elem := &element{ 572 namespace: value.namespace, 573 id: []byte(fmt.Sprintf("%s%d", value.id, i)), 574 result: value.result, 575 expiryNanos: now.UnixNano(), 576 } 577 m.Set(elem.id, elem) 578 c.list.PushBack(elem) 579 } 580 elemMaps = append(elemMaps, m) 581 } 582 583 c.Lock() 584 c.toDelete = elemMaps 585 c.Unlock() 586 587 // Send the deletion signal. 588 c.deleteCh <- struct{}{} 589 590 // Wait till the namespace is deleted. 591 conditionFn := func() bool { 592 c.list.Lock() 593 len := c.list.Len() 594 c.list.Unlock() 595 return len == 0 596 } 597 require.NoError(t, testWaitUntilWithTimeout(conditionFn, testWaitTimeout)) 598 599 // Assert the value has been deleted. 600 validateCache(t, c, nil) 601 602 // Assert we have slept 7 times. 603 require.Equal(t, 7, len(intervals)) 604 for i := 0; i < 7; i++ { 605 require.Equal(t, deletionThrottleInterval, intervals[i]) 606 } 607 } 608 609 func TestCacheClose(t *testing.T) { 610 opts := testCacheOptions() 611 c := NewCache(opts).(*cache) 612 613 // Make sure we can close multiple times. 614 require.NoError(t, c.Close()) 615 616 // Make sure the workers have exited. 617 c.evictCh <- struct{}{} 618 c.deleteCh <- struct{}{} 619 620 // Sleep a little in case those signals can be consumed. 621 time.Sleep(100 * time.Millisecond) 622 623 // Assert no goroutines are consuming the signals. 624 require.Equal(t, 1, len(c.evictCh)) 625 require.Equal(t, 1, len(c.deleteCh)) 626 627 // Assert closing the cache again will return an error. 628 require.Equal(t, errCacheClosed, c.Close()) 629 } 630 631 type populationMode int 632 633 const ( 634 populateMap populationMode = 1 << 0 635 populateSource populationMode = 1 << 1 636 populateBoth populationMode = populateMap | populateSource 637 ) 638 639 type mockSource struct { 640 sync.Mutex 641 642 idMap map[string]rules.MatchResult 643 currVersion int 644 } 645 646 func newMockSource() *mockSource { 647 return &mockSource{idMap: make(map[string]rules.MatchResult)} 648 } 649 650 func (s *mockSource) IsValid(version int) bool { 651 s.Lock() 652 currVersion := s.currVersion 653 s.Unlock() 654 return version >= currVersion 655 } 656 657 func (s *mockSource) ForwardMatch(id id.ID, _, _ int64, _ rules.MatchOptions) (rules.MatchResult, error) { 658 s.Lock() 659 defer s.Unlock() 660 if res, exists := s.idMap[string(id.Bytes())]; exists { 661 return res, nil 662 } 663 return rules.EmptyMatchResult, nil 664 } 665 666 // nolint: unparam 667 func (s *mockSource) setVersion(version int) { 668 s.Lock() 669 s.currVersion = version 670 s.Unlock() 671 } 672 673 func (s *mockSource) setResult(id []byte, res rules.MatchResult) { 674 s.Lock() 675 s.idMap[string(id)] = res 676 s.Unlock() 677 } 678 679 type conditionFn func() bool 680 681 func testWaitUntilWithTimeout(fn conditionFn, dur time.Duration) error { 682 start := time.Now() 683 for !fn() { 684 time.Sleep(100 * time.Millisecond) 685 end := time.Now() 686 if end.Sub(start) >= dur { 687 return errTestWaitUntilTimeout 688 } 689 } 690 return nil 691 } 692 693 func testCacheOptions() Options { 694 return NewOptions(). 695 SetClockOptions(clock.NewOptions()). 696 SetCapacity(2). 697 SetFreshDuration(5 * time.Second). 698 SetStutterDuration(1 * time.Second). 699 SetEvictionBatchSize(2). 700 SetDeletionBatchSize(2). 701 SetInvalidationMode(InvalidateAll) 702 } 703 704 func populateCache( 705 c *cache, 706 values []testValue, 707 expiry time.Time, 708 source *mockSource, 709 mode populationMode, 710 ) { 711 var resultSource rules.Matcher 712 if source != nil { 713 resultSource = source 714 } 715 for _, value := range values { 716 results, exists := c.namespaces.Get(value.namespace) 717 if !exists { 718 results = newResults(resultSource) 719 c.namespaces.Set(value.namespace, results) 720 } 721 if (mode & populateMap) > 0 { 722 elem := &element{ 723 namespace: value.namespace, 724 id: value.id, 725 result: value.result, 726 expiryNanos: expiry.UnixNano(), 727 } 728 results.elems.Set(elem.id, elem) 729 c.list.PushBack(elem) 730 } 731 if (mode&populateSource) > 0 && source != nil { 732 source.idMap[string(value.id)] = value.result 733 } 734 } 735 } 736 737 func validateCache(t *testing.T, c *cache, expected []testValue) { 738 c.list.Lock() 739 defer c.list.Unlock() 740 741 validateList(t, &c.list.list, expected) 742 validateNamespaces(t, c.namespaces, &c.list.list, expected) 743 } 744 745 func validateNamespaces( 746 t *testing.T, 747 namespaces *namespaceResultsMap, 748 l *list, 749 expected []testValue, 750 ) { 751 expectedNamespaces := make(map[string][]testValue) 752 for _, v := range expected { 753 expectedNamespaces[string(v.namespace)] = append(expectedNamespaces[string(v.namespace)], v) 754 } 755 require.Equal(t, len(expectedNamespaces), namespaces.Len()) 756 for _, entry := range namespaces.Iter() { 757 namespace, results := entry.Key(), entry.Value() 758 expectedResults, exists := expectedNamespaces[string(namespace)] 759 require.True(t, exists) 760 validateResults(t, namespace, results.elems, l, expectedResults) 761 } 762 } 763 764 func validateResults(t *testing.T, namespace []byte, elems *elemMap, l *list, expected []testValue) { 765 require.Equal(t, len(expected), elems.Len(), 766 fmt.Sprintf("mismatch for namespace: %v", string(namespace))) 767 elemMap := make(map[*element]struct{}) 768 for _, v := range expected { 769 e, exists := elems.Get(v.id) 770 require.True(t, exists) 771 elemMap[e] = struct{}{} 772 773 // Assert the element is in the list. 774 found := false 775 for le := l.Front(); le != nil; le = le.next { 776 if le == e { 777 found = true 778 break 779 } 780 } 781 require.True(t, found) 782 } 783 784 // Assert all the elements are unique. 785 require.Equal(t, len(expected), len(elemMap)) 786 }