|
1 | 1 | import t from 'tap' |
2 | 2 | import LRU from '../' |
3 | 3 |
|
| 4 | +const checkSize = (c:LRU<any,any>) => { |
| 5 | + const sizes = (c as unknown as { sizes: number[] }).sizes |
| 6 | + const {calculatedSize, maxSize} = c |
| 7 | + const sum = [...sizes].reduce((a, b) => a + b, 0) |
| 8 | + if (sum !== calculatedSize) { |
| 9 | + console.error({sum, calculatedSize, sizes}) |
| 10 | + throw new Error('calculatedSize does not equal sum of sizes') |
| 11 | + } |
| 12 | + if (calculatedSize > maxSize) { |
| 13 | + throw new Error('max size exceeded') |
| 14 | + } |
| 15 | +} |
| 16 | + |
4 | 17 | t.test('store strings, size = length', t => { |
5 | 18 | const c = new LRU<any, string>({ |
6 | 19 | max: 100, |
7 | 20 | maxSize: 100, |
8 | 21 | sizeCalculation: n => n.length, |
9 | 22 | }) |
10 | 23 |
|
| 24 | + checkSize(c) |
11 | 25 | c.set(5, 'x'.repeat(5)) |
| 26 | + checkSize(c) |
12 | 27 | c.set(10, 'x'.repeat(10)) |
| 28 | + checkSize(c) |
13 | 29 | c.set(20, 'x'.repeat(20)) |
| 30 | + checkSize(c) |
14 | 31 | t.equal(c.calculatedSize, 35) |
15 | 32 | c.delete(20) |
| 33 | + checkSize(c) |
16 | 34 | t.equal(c.calculatedSize, 15) |
17 | 35 | c.delete(5) |
| 36 | + checkSize(c) |
18 | 37 | t.equal(c.calculatedSize, 10) |
19 | 38 | c.clear() |
| 39 | + checkSize(c) |
20 | 40 | t.equal(c.calculatedSize, 0) |
21 | 41 |
|
22 | 42 | const s = 'x'.repeat(10) |
23 | 43 | for (let i = 0; i < 5; i++) { |
24 | 44 | c.set(i, s) |
| 45 | + checkSize(c) |
25 | 46 | } |
26 | 47 | t.equal(c.calculatedSize, 50) |
27 | 48 |
|
28 | 49 | // the big item goes in, but triggers a prune |
29 | 50 | // we don't preemptively prune until we *cross* the max |
30 | 51 | c.set('big', 'x'.repeat(100)) |
| 52 | + checkSize(c) |
31 | 53 | t.equal(c.calculatedSize, 100) |
32 | 54 | // override the size on set |
33 | 55 | c.set('big', 'y'.repeat(100), { sizeCalculation: () => 10 }) |
| 56 | + checkSize(c) |
34 | 57 | t.equal(c.size, 1) |
| 58 | + checkSize(c) |
35 | 59 | t.equal(c.calculatedSize, 10) |
| 60 | + checkSize(c) |
36 | 61 | c.delete('big') |
| 62 | + checkSize(c) |
37 | 63 | t.equal(c.size, 0) |
38 | 64 | t.equal(c.calculatedSize, 0) |
39 | 65 |
|
40 | 66 | c.set('repeated', 'i'.repeat(10)) |
| 67 | + checkSize(c) |
41 | 68 | c.set('repeated', 'j'.repeat(10)) |
| 69 | + checkSize(c) |
42 | 70 | c.set('repeated', 'i'.repeat(10)) |
| 71 | + checkSize(c) |
43 | 72 | c.set('repeated', 'j'.repeat(10)) |
| 73 | + checkSize(c) |
44 | 74 | c.set('repeated', 'i'.repeat(10)) |
| 75 | + checkSize(c) |
45 | 76 | c.set('repeated', 'j'.repeat(10)) |
| 77 | + checkSize(c) |
46 | 78 | c.set('repeated', 'i'.repeat(10)) |
| 79 | + checkSize(c) |
47 | 80 | c.set('repeated', 'j'.repeat(10)) |
| 81 | + checkSize(c) |
48 | 82 | t.equal(c.size, 1) |
49 | 83 | t.equal(c.calculatedSize, 10) |
50 | 84 | t.equal(c.get('repeated'), 'j'.repeat(10)) |
@@ -81,29 +115,84 @@ t.test('bad size calculation fn throws on set()', t => { |
81 | 115 |
|
82 | 116 | t.test('delete while empty, or missing key, is no-op', t => { |
83 | 117 | const c = new LRU({ max: 5, maxSize: 10, sizeCalculation: () => 2 }) |
| 118 | + checkSize(c) |
84 | 119 | c.set(1, 1) |
| 120 | + checkSize(c) |
85 | 121 | t.equal(c.size, 1) |
86 | 122 | t.equal(c.calculatedSize, 2) |
87 | 123 | c.clear() |
| 124 | + checkSize(c) |
88 | 125 | t.equal(c.size, 0) |
89 | 126 | t.equal(c.calculatedSize, 0) |
90 | 127 | c.delete(1) |
| 128 | + checkSize(c) |
91 | 129 | t.equal(c.size, 0) |
92 | 130 | t.equal(c.calculatedSize, 0) |
93 | 131 |
|
94 | 132 | c.set(1, 1) |
| 133 | + checkSize(c) |
95 | 134 | c.set(1, 1) |
| 135 | + checkSize(c) |
96 | 136 | c.set(1, 1) |
| 137 | + checkSize(c) |
97 | 138 | t.equal(c.size, 1) |
98 | 139 | t.equal(c.calculatedSize, 2) |
99 | 140 | c.delete(99) |
| 141 | + checkSize(c) |
100 | 142 | t.equal(c.size, 1) |
101 | 143 | t.equal(c.calculatedSize, 2) |
102 | 144 | c.delete(1) |
| 145 | + checkSize(c) |
103 | 146 | t.equal(c.size, 0) |
104 | 147 | t.equal(c.calculatedSize, 0) |
105 | 148 | c.delete(1) |
| 149 | + checkSize(c) |
106 | 150 | t.equal(c.size, 0) |
107 | 151 | t.equal(c.calculatedSize, 0) |
108 | 152 | t.end() |
109 | 153 | }) |
| 154 | + |
| 155 | +t.test('large item falls out of cache, sizes are kept correct', t => { |
| 156 | + const c = new LRU<number, number>({ |
| 157 | + maxSize: 10, |
| 158 | + sizeCalculation: () => 100, |
| 159 | + }) |
| 160 | + const sizes:number[] = (c as unknown as { sizes: number[] }).sizes |
| 161 | + |
| 162 | + checkSize(c) |
| 163 | + t.equal(c.size, 0) |
| 164 | + t.equal(c.calculatedSize, 0) |
| 165 | + t.same(sizes, []) |
| 166 | + |
| 167 | + c.set(2, 2, { size: 2 }) |
| 168 | + checkSize(c) |
| 169 | + t.equal(c.size, 1) |
| 170 | + t.equal(c.calculatedSize, 2) |
| 171 | + t.same(sizes, [2]) |
| 172 | + |
| 173 | + c.delete(2) |
| 174 | + checkSize(c) |
| 175 | + t.equal(c.size, 0) |
| 176 | + t.equal(c.calculatedSize, 0) |
| 177 | + t.same(sizes, [0]) |
| 178 | + |
| 179 | + c.set(1, 1) |
| 180 | + checkSize(c) |
| 181 | + t.equal(c.size, 0) |
| 182 | + t.equal(c.calculatedSize, 0) |
| 183 | + t.same(sizes, [0]) |
| 184 | + |
| 185 | + c.set(3, 3, { size: 3 }) |
| 186 | + checkSize(c) |
| 187 | + t.equal(c.size, 1) |
| 188 | + t.equal(c.calculatedSize, 3) |
| 189 | + t.same(sizes, [3]) |
| 190 | + |
| 191 | + c.set(4, 4) |
| 192 | + checkSize(c) |
| 193 | + t.equal(c.size, 1) |
| 194 | + t.equal(c.calculatedSize, 3) |
| 195 | + t.same(sizes, [3]) |
| 196 | + |
| 197 | + t.end() |
| 198 | +}) |
0 commit comments