9c189d12930a933e2fd635ffaf77ac0aab8ca8ec
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / ProcessCacheLRUTest.php
1 <?php
2
3 /**
4 * Test for ProcessCacheLRU class.
5 *
6 * Note that it uses the ProcessCacheLRUTestable class which extends some
7 * properties and methods visibility. That class is defined at the end of the
8 * file containing this class.
9 *
10 * @group Cache
11 */
12 class ProcessCacheLRUTest extends PHPUnit_Framework_TestCase {
13
14 /**
15 * Helper to verify emptiness of a cache object.
16 * Compare against an array so we get the cache content difference.
17 */
18 protected function assertCacheEmpty( $cache, $msg = 'Cache should be empty' ) {
19 $this->assertAttributeEquals( [], 'cache', $cache, $msg );
20 }
21
22 /**
23 * Helper to fill a cache object passed by reference
24 */
25 protected function fillCache( &$cache, $numEntries ) {
26 // Fill cache with three values
27 for ( $i = 1; $i <= $numEntries; $i++ ) {
28 $cache->set( "cache-key-$i", "prop-$i", "value-$i" );
29 }
30 }
31
32 /**
33 * Generates an array of what would be expected in cache for a given cache
34 * size and a number of entries filled in sequentially
35 */
36 protected function getExpectedCache( $cacheMaxEntries, $entryToFill ) {
37 $expected = [];
38
39 if ( $entryToFill === 0 ) {
40 // The cache is empty!
41 return [];
42 } elseif ( $entryToFill <= $cacheMaxEntries ) {
43 // Cache is not fully filled
44 $firstKey = 1;
45 } else {
46 // Cache overflowed
47 $firstKey = 1 + $entryToFill - $cacheMaxEntries;
48 }
49
50 $lastKey = $entryToFill;
51
52 for ( $i = $firstKey; $i <= $lastKey; $i++ ) {
53 $expected["cache-key-$i"] = [ "prop-$i" => "value-$i" ];
54 }
55
56 return $expected;
57 }
58
59 /**
60 * Highlight diff between assertEquals and assertNotSame
61 */
62 public function testPhpUnitArrayEquality() {
63 $one = [ 'A' => 1, 'B' => 2 ];
64 $two = [ 'B' => 2, 'A' => 1 ];
65 // ==
66 $this->assertEquals( $one, $two );
67 // ===
68 $this->assertNotSame( $one, $two );
69 }
70
71 /**
72 * @dataProvider provideInvalidConstructorArg
73 * @expectedException Wikimedia\Assert\ParameterAssertionException
74 * @covers ProcessCacheLRU::__construct
75 */
76 public function testConstructorGivenInvalidValue( $maxSize ) {
77 new ProcessCacheLRUTestable( $maxSize );
78 }
79
80 /**
81 * Value which are forbidden by the constructor
82 */
83 public static function provideInvalidConstructorArg() {
84 return [
85 [ null ],
86 [ [] ],
87 [ new stdClass() ],
88 [ 0 ],
89 [ '5' ],
90 [ -1 ],
91 ];
92 }
93
94 /**
95 * @covers ProcessCacheLRU::get
96 * @covers ProcessCacheLRU::set
97 * @covers ProcessCacheLRU::has
98 */
99 public function testAddAndGetAKey() {
100 $oneCache = new ProcessCacheLRUTestable( 1 );
101 $this->assertCacheEmpty( $oneCache );
102
103 // First set just one value
104 $oneCache->set( 'cache-key', 'prop1', 'value1' );
105 $this->assertEquals( 1, $oneCache->getEntriesCount() );
106 $this->assertTrue( $oneCache->has( 'cache-key', 'prop1' ) );
107 $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) );
108 }
109
110 /**
111 * @covers ProcessCacheLRU::set
112 * @covers ProcessCacheLRU::get
113 */
114 public function testDeleteOldKey() {
115 $oneCache = new ProcessCacheLRUTestable( 1 );
116 $this->assertCacheEmpty( $oneCache );
117
118 $oneCache->set( 'cache-key', 'prop1', 'value1' );
119 $oneCache->set( 'cache-key', 'prop1', 'value2' );
120 $this->assertEquals( 'value2', $oneCache->get( 'cache-key', 'prop1' ) );
121 }
122
123 /**
124 * This test that we properly overflow when filling a cache with
125 * a sequence of always different cache-keys. Meant to verify we correclty
126 * delete the older key.
127 *
128 * @covers ProcessCacheLRU::set
129 * @dataProvider provideCacheFilling
130 * @param int $cacheMaxEntries Maximum entry the created cache will hold
131 * @param int $entryToFill Number of entries to insert in the created cache.
132 */
133 public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) {
134 $cache = new ProcessCacheLRUTestable( $cacheMaxEntries );
135 $this->fillCache( $cache, $entryToFill );
136
137 $this->assertSame(
138 $this->getExpectedCache( $cacheMaxEntries, $entryToFill ),
139 $cache->getCache(),
140 "Filling a $cacheMaxEntries entries cache with $entryToFill entries"
141 );
142 }
143
144 /**
145 * Provider for testFillingCache
146 */
147 public static function provideCacheFilling() {
148 // ($cacheMaxEntries, $entryToFill, $msg='')
149 return [
150 [ 1, 0 ],
151 [ 1, 1 ],
152 // overflow
153 [ 1, 2 ],
154 // overflow
155 [ 5, 33 ],
156 ];
157 }
158
159 /**
160 * Create a cache with only one remaining entry then update
161 * the first inserted entry. Should bump it to the top.
162 *
163 * @covers ProcessCacheLRU::set
164 */
165 public function testReplaceExistingKeyShouldBumpEntryToTop() {
166 $maxEntries = 3;
167
168 $cache = new ProcessCacheLRUTestable( $maxEntries );
169 // Fill cache leaving just one remaining slot
170 $this->fillCache( $cache, $maxEntries - 1 );
171
172 // Set an existing cache key
173 $cache->set( "cache-key-1", "prop-1", "new-value-for-1" );
174
175 $this->assertSame(
176 [
177 'cache-key-2' => [ 'prop-2' => 'value-2' ],
178 'cache-key-1' => [ 'prop-1' => 'new-value-for-1' ],
179 ],
180 $cache->getCache()
181 );
182 }
183
184 /**
185 * @covers ProcessCacheLRU::get
186 * @covers ProcessCacheLRU::set
187 * @covers ProcessCacheLRU::has
188 */
189 public function testRecentlyAccessedKeyStickIn() {
190 $cache = new ProcessCacheLRUTestable( 2 );
191 $cache->set( 'first', 'prop1', 'value1' );
192 $cache->set( 'second', 'prop2', 'value2' );
193
194 // Get first
195 $cache->get( 'first', 'prop1' );
196 // Cache a third value, should invalidate the least used one
197 $cache->set( 'third', 'prop3', 'value3' );
198
199 $this->assertFalse( $cache->has( 'second', 'prop2' ) );
200 }
201
202 /**
203 * This first create a full cache then update the value for the 2nd
204 * filled entry.
205 * Given a cache having 1,2,3 as key, updating 2 should bump 2 to
206 * the top of the queue with the new value: 1,3,2* (* = updated).
207 *
208 * @covers ProcessCacheLRU::set
209 * @covers ProcessCacheLRU::get
210 */
211 public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() {
212 $maxEntries = 3;
213
214 $cache = new ProcessCacheLRUTestable( $maxEntries );
215 $this->fillCache( $cache, $maxEntries );
216
217 // Set an existing cache key
218 $cache->set( "cache-key-2", "prop-2", "new-value-for-2" );
219 $this->assertSame(
220 [
221 'cache-key-1' => [ 'prop-1' => 'value-1' ],
222 'cache-key-3' => [ 'prop-3' => 'value-3' ],
223 'cache-key-2' => [ 'prop-2' => 'new-value-for-2' ],
224 ],
225 $cache->getCache()
226 );
227 $this->assertEquals( 'new-value-for-2',
228 $cache->get( 'cache-key-2', 'prop-2' )
229 );
230 }
231
232 /**
233 * @covers ProcessCacheLRU::set
234 */
235 public function testBumpExistingKeyToTop() {
236 $cache = new ProcessCacheLRUTestable( 3 );
237 $this->fillCache( $cache, 3 );
238
239 // Set the very first cache key to a new value
240 $cache->set( "cache-key-1", "prop-1", "new value for 1" );
241 $this->assertEquals(
242 [
243 'cache-key-2' => [ 'prop-2' => 'value-2' ],
244 'cache-key-3' => [ 'prop-3' => 'value-3' ],
245 'cache-key-1' => [ 'prop-1' => 'new value for 1' ],
246 ],
247 $cache->getCache()
248 );
249 }
250 }
251
252 /**
253 * Overrides some ProcessCacheLRU methods and properties accessibility.
254 */
255 class ProcessCacheLRUTestable extends ProcessCacheLRU {
256 public $cache = [];
257
258 public function getCache() {
259 return $this->cache;
260 }
261
262 public function getEntriesCount() {
263 return count( $this->cache );
264 }
265 }