Merge "Show dimensions in TraditionalImageGallery"
[lhc/web/wiklou.git] / tests / phpunit / includes / config / EtcdConfigTest.php
1 <?php
2
3 class EtcConfigTest extends PHPUnit_Framework_TestCase {
4
5 private function createConfigMock( array $options = [] ) {
6 return $this->getMockBuilder( EtcdConfig::class )
7 ->setConstructorArgs( [ $options + [
8 'host' => 'etcd-tcp.example.net',
9 'directory' => '/',
10 'timeout' => 0.1,
11 ] ] )
12 ->setMethods( [ 'fetchAllFromEtcd' ] )
13 ->getMock();
14 }
15
16 private function createSimpleConfigMock( array $config ) {
17 $mock = $this->createConfigMock();
18 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
19 ->willReturn( [
20 $config,
21 null, // error
22 false // retry?
23 ] );
24 return $mock;
25 }
26
27 /**
28 * @covers EtcdConfig::has
29 */
30 public function testHasKnown() {
31 $config = $this->createSimpleConfigMock( [
32 'known' => 'value'
33 ] );
34 $this->assertSame( true, $config->has( 'known' ) );
35 }
36
37 /**
38 * @covers EtcdConfig::__construct
39 * @covers EtcdConfig::get
40 */
41 public function testGetKnown() {
42 $config = $this->createSimpleConfigMock( [
43 'known' => 'value'
44 ] );
45 $this->assertSame( 'value', $config->get( 'known' ) );
46 }
47
48 /**
49 * @covers EtcdConfig::has
50 */
51 public function testHasUnknown() {
52 $config = $this->createSimpleConfigMock( [
53 'known' => 'value'
54 ] );
55 $this->assertSame( false, $config->has( 'unknown' ) );
56 }
57
58 /**
59 * @covers EtcdConfig::get
60 */
61 public function testGetUnknown() {
62 $config = $this->createSimpleConfigMock( [
63 'known' => 'value'
64 ] );
65 $this->setExpectedException( ConfigException::class );
66 $config->get( 'unknown' );
67 }
68
69 /**
70 * @covers EtcdConfig::__construct
71 */
72 public function testConstructCacheObj() {
73 $cache = $this->getMockBuilder( HashBagOStuff::class )
74 ->setMethods( [ 'get' ] )
75 ->getMock();
76 $cache->expects( $this->once() )->method( 'get' )
77 ->willReturn( [
78 'config' => [ 'known' => 'from-cache' ],
79 'expires' => INF,
80 ] );
81 $config = $this->createConfigMock( [ 'cache' => $cache ] );
82
83 $this->assertSame( 'from-cache', $config->get( 'known' ) );
84 }
85
86 /**
87 * @covers EtcdConfig::__construct
88 */
89 public function testConstructCacheSpec() {
90 $config = $this->createConfigMock( [ 'cache' => [
91 'class' => HashBagOStuff::class
92 ] ] );
93 $config->expects( $this->once() )->method( 'fetchAllFromEtcd' )
94 ->willReturn( [
95 [ 'known' => 'from-fetch' ],
96 null, // error
97 false // retry?
98 ] );
99
100 $this->assertSame( 'from-fetch', $config->get( 'known' ) );
101 }
102
103 /**
104 * Test matrix
105 *
106 * - [x] Cache miss
107 * Result: Fetched value
108 * > cache miss | gets lock | backend succeeds
109 *
110 * - [x] Cache miss with backend error
111 * Result: ConfigException
112 * > cache miss | gets lock | backend error (no retry)
113 *
114 * - [x] Cache hit after retry
115 * Result: Cached value (populated by process holding lock)
116 * > cache miss | no lock | cache retry
117 *
118 * - [x] Cache hit
119 * Result: Cached value
120 * > cache hit
121 *
122 * - [x] Process cache hit
123 * Result: Cached value
124 * > process cache hit
125 *
126 * - [x] Cache expired
127 * Result: Fetched value
128 * > cache expired | gets lock | backend succeeds
129 *
130 * - [x] Cache expired with backend failure
131 * Result: Cached value (stale)
132 * > cache expired | gets lock | backend fails (allows retry)
133 *
134 * - [x] Cache expired and no lock
135 * Result: Cached value (stale)
136 * > cache expired | no lock
137 *
138 * Other notable scenarios:
139 *
140 * - [ ] Cache miss with backend retry
141 * Result: Fetched value
142 * > cache expired | gets lock | backend failure (allows retry)
143 */
144
145 /**
146 * @covers EtcdConfig::load
147 */
148 public function testLoadCacheMiss() {
149 // Create cache mock
150 $cache = $this->getMockBuilder( HashBagOStuff::class )
151 ->setMethods( [ 'get', 'lock' ] )
152 ->getMock();
153 // .. misses cache
154 $cache->expects( $this->once() )->method( 'get' )
155 ->willReturn( false );
156 // .. gets lock
157 $cache->expects( $this->once() )->method( 'lock' )
158 ->willReturn( true );
159
160 // Create config mock
161 $mock = $this->createConfigMock( [
162 'cache' => $cache,
163 ] );
164 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
165 ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
166
167 $this->assertSame( 'from-fetch', $mock->get( 'known' ) );
168 }
169
170 /**
171 * @covers EtcdConfig::load
172 */
173 public function testLoadCacheMissBackendError() {
174 // Create cache mock
175 $cache = $this->getMockBuilder( HashBagOStuff::class )
176 ->setMethods( [ 'get', 'lock' ] )
177 ->getMock();
178 // .. misses cache
179 $cache->expects( $this->once() )->method( 'get' )
180 ->willReturn( false );
181 // .. gets lock
182 $cache->expects( $this->once() )->method( 'lock' )
183 ->willReturn( true );
184
185 // Create config mock
186 $mock = $this->createConfigMock( [
187 'cache' => $cache,
188 ] );
189 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
190 ->willReturn( [ null, 'Fake error', false ] );
191
192 $this->setExpectedException( ConfigException::class );
193 $mock->get( 'key' );
194 }
195
196 /**
197 * @covers EtcdConfig::load
198 */
199 public function testLoadCacheMissWithoutLock() {
200 // Create cache mock
201 $cache = $this->getMockBuilder( HashBagOStuff::class )
202 ->setMethods( [ 'get', 'lock' ] )
203 ->getMock();
204 $cache->expects( $this->exactly( 2 ) )->method( 'get' )
205 ->will( $this->onConsecutiveCalls(
206 // .. misses cache first time
207 false,
208 // .. hits cache on retry
209 [
210 'config' => [ 'known' => 'from-cache' ],
211 'expires' => INF,
212 ]
213 ) );
214 // .. misses lock
215 $cache->expects( $this->once() )->method( 'lock' )
216 ->willReturn( false );
217
218 // Create config mock
219 $mock = $this->createConfigMock( [
220 'cache' => $cache,
221 ] );
222 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
223
224 $this->assertSame( 'from-cache', $mock->get( 'known' ) );
225 }
226
227 /**
228 * @covers EtcdConfig::load
229 */
230 public function testLoadCacheHit() {
231 // Create cache mock
232 $cache = $this->getMockBuilder( HashBagOStuff::class )
233 ->setMethods( [ 'get', 'lock' ] )
234 ->getMock();
235 $cache->expects( $this->once() )->method( 'get' )
236 // .. hits cache
237 ->willReturn( [
238 'config' => [ 'known' => 'from-cache' ],
239 'expires' => INF,
240 ] );
241 $cache->expects( $this->never() )->method( 'lock' );
242
243 // Create config mock
244 $mock = $this->createConfigMock( [
245 'cache' => $cache,
246 ] );
247 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
248
249 $this->assertSame( 'from-cache', $mock->get( 'known' ) );
250 }
251
252 /**
253 * @covers EtcdConfig::load
254 */
255 public function testLoadProcessCacheHit() {
256 // Create cache mock
257 $cache = $this->getMockBuilder( HashBagOStuff::class )
258 ->setMethods( [ 'get', 'lock' ] )
259 ->getMock();
260 $cache->expects( $this->once() )->method( 'get' )
261 // .. hits cache
262 ->willReturn( [
263 'config' => [ 'known' => 'from-cache' ],
264 'expires' => INF,
265 ] );
266 $cache->expects( $this->never() )->method( 'lock' );
267
268 // Create config mock
269 $mock = $this->createConfigMock( [
270 'cache' => $cache,
271 ] );
272 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
273
274 $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Cache hit' );
275 $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Process cache hit' );
276 }
277
278 /**
279 * @covers EtcdConfig::load
280 */
281 public function testLoadCacheExpiredLockFetchSucceeded() {
282 // Create cache mock
283 $cache = $this->getMockBuilder( HashBagOStuff::class )
284 ->setMethods( [ 'get', 'lock' ] )
285 ->getMock();
286 $cache->expects( $this->once() )->method( 'get' )->willReturn(
287 // .. stale cache
288 [
289 'config' => [ 'known' => 'from-cache-expired' ],
290 'expires' => -INF,
291 ]
292 );
293 // .. gets lock
294 $cache->expects( $this->once() )->method( 'lock' )
295 ->willReturn( true );
296
297 // Create config mock
298 $mock = $this->createConfigMock( [
299 'cache' => $cache,
300 ] );
301 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
302 ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
303
304 $this->assertSame( 'from-fetch', $mock->get( 'known' ) );
305 }
306
307 /**
308 * @covers EtcdConfig::load
309 */
310 public function testLoadCacheExpiredLockFetchFails() {
311 // Create cache mock
312 $cache = $this->getMockBuilder( HashBagOStuff::class )
313 ->setMethods( [ 'get', 'lock' ] )
314 ->getMock();
315 $cache->expects( $this->once() )->method( 'get' )->willReturn(
316 // .. stale cache
317 [
318 'config' => [ 'known' => 'from-cache-expired' ],
319 'expires' => -INF,
320 ]
321 );
322 // .. gets lock
323 $cache->expects( $this->once() )->method( 'lock' )
324 ->willReturn( true );
325
326 // Create config mock
327 $mock = $this->createConfigMock( [
328 'cache' => $cache,
329 ] );
330 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
331 ->willReturn( [ null, 'Fake failure', true ] );
332
333 $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
334 }
335
336 /**
337 * @covers EtcdConfig::load
338 */
339 public function testLoadCacheExpiredNoLock() {
340 // Create cache mock
341 $cache = $this->getMockBuilder( HashBagOStuff::class )
342 ->setMethods( [ 'get', 'lock' ] )
343 ->getMock();
344 $cache->expects( $this->once() )->method( 'get' )
345 // .. hits cache (expired value)
346 ->willReturn( [
347 'config' => [ 'known' => 'from-cache-expired' ],
348 'expires' => -INF,
349 ] );
350 // .. misses lock
351 $cache->expects( $this->once() )->method( 'lock' )
352 ->willReturn( false );
353
354 // Create config mock
355 $mock = $this->createConfigMock( [
356 'cache' => $cache,
357 ] );
358 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
359
360 $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
361 }
362 }