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