summaryrefslogtreecommitdiff
path: root/lib/caching/lrucache_test.go
blob: a9d4fe0c8f4eb4ec91a135033f1d3885f5c93a5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright (C) 2023  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

package caching

import (
	"context"
	"runtime/debug"
	"testing"
	"time"

	"github.com/datawire/dlib/dlog"

	"github.com/stretchr/testify/assert"
)

func TestLRUBlocking(t *testing.T) {
	t.Parallel()
	const tick = time.Second / 2

	ctx := dlog.NewTestContext(t, false)

	cache := NewLRUCache[int, int](4,
		FuncSource[int, int](func(_ context.Context, k int, v *int) { *v = k * k }))

	assert.Equal(t, 1, *cache.Acquire(ctx, 1))
	assert.Equal(t, 4, *cache.Acquire(ctx, 2))
	assert.Equal(t, 9, *cache.Acquire(ctx, 3))
	assert.Equal(t, 16, *cache.Acquire(ctx, 4))

	ch := make(chan int)
	start := time.Now()
	go func() {
		ch <- *cache.Acquire(ctx, 5)
	}()
	go func() {
		time.Sleep(tick)
		cache.Release(3)
	}()
	result := <-ch
	dur := time.Since(start)
	assert.Equal(t, 25, result)
	assert.Greater(t, dur, tick)
}

//nolint:paralleltest // Can't be parallel because we test testing.AllocsPerRun.
func TestLRUAllocs(t *testing.T) {
	const (
		cacheLen  = 8
		bigNumber = 128
	)

	ctx := dlog.NewTestContext(t, false)

	evictions := 0
	cache := NewLRUCache[int, int](cacheLen, FuncSource[int, int](func(_ context.Context, k int, v *int) {
		if *v > 0 {
			evictions++
		}
		*v = k
	}))

	i := 1
	store := func() {
		cache.Acquire(ctx, i)
		cache.Release(i)
		i++
	}

	// Disable the GC temporarily to prevent cache.byAge.pool from
	// being cleaned in the middle of an AllocsPerRun and causing
	// spurious allocations.
	percent := debug.SetGCPercent(-1)
	defer debug.SetGCPercent(percent)

	// 1 alloc each as we fill the cache
	assert.Equal(t, float64(1), testing.AllocsPerRun(cacheLen-1, store))
	assert.Equal(t, 0, evictions)
	// after that, it should be alloc-free
	assert.Equal(t, float64(0), testing.AllocsPerRun(1, store))
	assert.Equal(t, 2, evictions)
	assert.Equal(t, float64(0), testing.AllocsPerRun(bigNumber, store))
	assert.Equal(t, 3+bigNumber, evictions)
	// check the len
	assert.Equal(t, cacheLen, len(cache.(*lruCache[int, int]).byName))
	cnt := 0
	for entry := cache.(*lruCache[int, int]).evictable.Oldest(); entry != nil; entry = entry.Newer() {
		cnt++
	}
	assert.Equal(t, cacheLen, cnt)
	// check contents
	cnt = 0
	for j := i - 1; j > 0; j-- {
		entry, ok := cache.(*lruCache[int, int]).byName[j]
		if cnt < cacheLen {
			if assert.True(t, ok, j) {
				val := entry.Value.val
				assert.Equal(t, j, val, j)
			}
			cnt++
		} else {
			assert.False(t, ok, j)
		}
	}
}