Separate MediaWiki unit and integration tests
[lhc/web/wiklou.git] / tests / phpunit / unit / includes / libs / ProcessCacheLRUTest.php
1 <?php
2
3 /**
4 * Note that it uses the ProcessCacheLRUTestable class which extends some
5 * properties and methods visibility. That class is defined at the end of the
6 * file containing this class.
7 *
8 * @group Cache
9 */
10 class ProcessCacheLRUTest extends PHPUnit\Framework\TestCase {
11
12 use MediaWikiCoversValidator;
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->assertEquals( 0, $cache->getEntriesCount(), $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 * @coversNothing
62 */
63 public function testPhpUnitArrayEquality() {
64 $one = [ 'A' => 1, 'B' => 2 ];
65 $two = [ 'B' => 2, 'A' => 1 ];
66 // ==
67 $this->assertEquals( $one, $two );
68 // ===
69 $this->assertNotSame( $one, $two );
70 }
71
72 /**
73 * @dataProvider provideInvalidConstructorArg
74 * @expectedException Wikimedia\Assert\ParameterAssertionException
75 * @covers ProcessCacheLRU::__construct
76 */
77 public function testConstructorGivenInvalidValue( $maxSize ) {
78 new ProcessCacheLRUTestable( $maxSize );
79 }
80
81 /**
82 * Value which are forbidden by the constructor
83 */
84 public static function provideInvalidConstructorArg() {
85 return [
86 [ null ],
87 [ [] ],
88 [ new stdClass() ],
89 [ 0 ],
90 [ '5' ],
91 [ -1 ],
92 ];
93 }
94
95 /**
96 * @covers ProcessCacheLRU::get
97 * @covers ProcessCacheLRU::set
98 * @covers ProcessCacheLRU::has
99 */
100 public function testAddAndGetAKey() {
101 $oneCache = new ProcessCacheLRUTestable( 1 );
102 $this->assertCacheEmpty( $oneCache );
103
104 // First set just one value
105 $oneCache->set( 'cache-key', 'prop1', 'value1' );
106 $this->assertEquals( 1, $oneCache->getEntriesCount() );
107 $this->assertTrue( $oneCache->has( 'cache-key', 'prop1' ) );
108 $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) );
109 }
110
111 /**
112 * @covers ProcessCacheLRU::set
113 * @covers ProcessCacheLRU::get
114 */
115 public function testDeleteOldKey() {
116 $oneCache = new ProcessCacheLRUTestable( 1 );
117 $this->assertCacheEmpty( $oneCache );
118
119 $oneCache->set( 'cache-key', 'prop1', 'value1' );
120 $oneCache->set( 'cache-key', 'prop1', 'value2' );
121 $this->assertEquals( 'value2', $oneCache->get( 'cache-key', 'prop1' ) );
122 }
123
124 /**
125 * This test that we properly overflow when filling a cache with
126 * a sequence of always different cache-keys. Meant to verify we correclty
127 * delete the older key.
128 *
129 * @covers ProcessCacheLRU::set
130 * @dataProvider provideCacheFilling
131 * @param int $cacheMaxEntries Maximum entry the created cache will hold
132 * @param int $entryToFill Number of entries to insert in the created cache.
133 */
134 public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) {
135 $cache = new ProcessCacheLRUTestable( $cacheMaxEntries );
136 $this->fillCache( $cache, $entryToFill );
137
138 $this->assertSame(
139 $this->getExpectedCache( $cacheMaxEntries, $entryToFill ),
140 $cache->getCache(),
141 "Filling a $cacheMaxEntries entries cache with $entryToFill entries"
142 );
143 }
144
145 /**
146 * Provider for testFillingCache
147 */
148 public static function provideCacheFilling() {
149 // ($cacheMaxEntries, $entryToFill, $msg='')
150 return [
151 [ 1, 0 ],
152 [ 1, 1 ],
153 // overflow
154 [ 1, 2 ],
155 // overflow
156 [ 5, 33 ],
157 ];
158 }
159
160 /**
161 * Create a cache with only one remaining entry then update
162 * the first inserted entry. Should bump it to the top.
163 *
164 * @covers ProcessCacheLRU::set
165 */
166 public function testReplaceExistingKeyShouldBumpEntryToTop() {
167 $maxEntries = 3;
168
169 $cache = new ProcessCacheLRUTestable( $maxEntries );
170 // Fill cache leaving just one remaining slot
171 $this->fillCache( $cache, $maxEntries - 1 );
172
173 // Set an existing cache key
174 $cache->set( "cache-key-1", "prop-1", "new-value-for-1" );
175
176 $this->assertSame(
177 [
178 'cache-key-2' => [ 'prop-2' => 'value-2' ],
179 'cache-key-1' => [ 'prop-1' => 'new-value-for-1' ],
180 ],
181 $cache->getCache()
182 );
183 }
184
185 /**
186 * @covers ProcessCacheLRU::get
187 * @covers ProcessCacheLRU::set
188 * @covers ProcessCacheLRU::has
189 */
190 public function testRecentlyAccessedKeyStickIn() {
191 $cache = new ProcessCacheLRUTestable( 2 );
192 $cache->set( 'first', 'prop1', 'value1' );
193 $cache->set( 'second', 'prop2', 'value2' );
194
195 // Get first
196 $cache->get( 'first', 'prop1' );
197 // Cache a third value, should invalidate the least used one
198 $cache->set( 'third', 'prop3', 'value3' );
199
200 $this->assertFalse( $cache->has( 'second', 'prop2' ) );
201 }
202
203 /**
204 * This first create a full cache then update the value for the 2nd
205 * filled entry.
206 * Given a cache having 1,2,3 as key, updating 2 should bump 2 to
207 * the top of the queue with the new value: 1,3,2* (* = updated).
208 *
209 * @covers ProcessCacheLRU::set
210 * @covers ProcessCacheLRU::get
211 */
212 public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() {
213 $maxEntries = 3;
214
215 $cache = new ProcessCacheLRUTestable( $maxEntries );
216 $this->fillCache( $cache, $maxEntries );
217
218 // Set an existing cache key
219 $cache->set( "cache-key-2", "prop-2", "new-value-for-2" );
220 $this->assertSame(
221 [
222 'cache-key-1' => [ 'prop-1' => 'value-1' ],
223 'cache-key-3' => [ 'prop-3' => 'value-3' ],
224 'cache-key-2' => [ 'prop-2' => 'new-value-for-2' ],
225 ],
226 $cache->getCache()
227 );
228 $this->assertEquals( 'new-value-for-2',
229 $cache->get( 'cache-key-2', 'prop-2' )
230 );
231 }
232
233 /**
234 * @covers ProcessCacheLRU::set
235 */
236 public function testBumpExistingKeyToTop() {
237 $cache = new ProcessCacheLRUTestable( 3 );
238 $this->fillCache( $cache, 3 );
239
240 // Set the very first cache key to a new value
241 $cache->set( "cache-key-1", "prop-1", "new value for 1" );
242 $this->assertEquals(
243 [
244 'cache-key-2' => [ 'prop-2' => 'value-2' ],
245 'cache-key-3' => [ 'prop-3' => 'value-3' ],
246 'cache-key-1' => [ 'prop-1' => 'new value for 1' ],
247 ],
248 $cache->getCache()
249 );
250 }
251 }
252
253 /**
254 * Overrides some ProcessCacheLRU methods and properties accessibility.
255 */
256 class ProcessCacheLRUTestable extends ProcessCacheLRU {
257 public function getCache() {
258 return $this->cache->toArray();
259 }
260
261 public function getEntriesCount() {
262 return count( $this->cache->toArray() );
263 }
264 }