3 class EtcConfigTest
extends PHPUnit_Framework_TestCase
{
5 private function createConfigMock( array $options = [] ) {
6 return $this->getMockBuilder( EtcdConfig
::class )
7 ->setConstructorArgs( [ $options +
[
8 'host' => 'etcd-tcp.example.net',
12 ->setMethods( [ 'fetchAllFromEtcd' ] )
16 private function createSimpleConfigMock( array $config ) {
17 $mock = $this->createConfigMock();
18 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
28 * @covers EtcdConfig::has
30 public function testHasKnown() {
31 $config = $this->createSimpleConfigMock( [
34 $this->assertSame( true, $config->has( 'known' ) );
38 * @covers EtcdConfig::__construct
39 * @covers EtcdConfig::get
41 public function testGetKnown() {
42 $config = $this->createSimpleConfigMock( [
45 $this->assertSame( 'value', $config->get( 'known' ) );
49 * @covers EtcdConfig::has
51 public function testHasUnknown() {
52 $config = $this->createSimpleConfigMock( [
55 $this->assertSame( false, $config->has( 'unknown' ) );
59 * @covers EtcdConfig::get
61 public function testGetUnknown() {
62 $config = $this->createSimpleConfigMock( [
65 $this->setExpectedException( ConfigException
::class );
66 $config->get( 'unknown' );
70 * @covers EtcdConfig::__construct
72 public function testConstructCacheObj() {
73 $cache = $this->getMockBuilder( HashBagOStuff
::class )
74 ->setMethods( [ 'get' ] )
76 $cache->expects( $this->once() )->method( 'get' )
78 'config' => [ 'known' => 'from-cache' ],
81 $config = $this->createConfigMock( [ 'cache' => $cache ] );
83 $this->assertSame( 'from-cache', $config->get( 'known' ) );
87 * @covers EtcdConfig::__construct
89 public function testConstructCacheSpec() {
90 $config = $this->createConfigMock( [ 'cache' => [
91 'class' => HashBagOStuff
::class
93 $config->expects( $this->once() )->method( 'fetchAllFromEtcd' )
95 [ 'known' => 'from-fetch' ],
100 $this->assertSame( 'from-fetch', $config->get( 'known' ) );
107 * Result: Fetched value
108 * > cache miss | gets lock | backend succeeds
110 * - [x] Cache miss with backend error
111 * Result: ConfigException
112 * > cache miss | gets lock | backend error (no retry)
114 * - [x] Cache hit after retry
115 * Result: Cached value (populated by process holding lock)
116 * > cache miss | no lock | cache retry
119 * Result: Cached value
122 * - [x] Process cache hit
123 * Result: Cached value
124 * > process cache hit
126 * - [x] Cache expired
127 * Result: Fetched value
128 * > cache expired | gets lock | backend succeeds
130 * - [x] Cache expired with backend failure
131 * Result: Cached value (stale)
132 * > cache expired | gets lock | backend fails (allows retry)
134 * - [x] Cache expired and no lock
135 * Result: Cached value (stale)
136 * > cache expired | no lock
138 * Other notable scenarios:
140 * - [ ] Cache miss with backend retry
141 * Result: Fetched value
142 * > cache expired | gets lock | backend failure (allows retry)
146 * @covers EtcdConfig::load
148 public function testLoadCacheMiss() {
150 $cache = $this->getMockBuilder( HashBagOStuff
::class )
151 ->setMethods( [ 'get', 'lock' ] )
154 $cache->expects( $this->once() )->method( 'get' )
155 ->willReturn( false );
157 $cache->expects( $this->once() )->method( 'lock' )
158 ->willReturn( true );
160 // Create config mock
161 $mock = $this->createConfigMock( [
164 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
165 ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
167 $this->assertSame( 'from-fetch', $mock->get( 'known' ) );
171 * @covers EtcdConfig::load
173 public function testLoadCacheMissBackendError() {
175 $cache = $this->getMockBuilder( HashBagOStuff
::class )
176 ->setMethods( [ 'get', 'lock' ] )
179 $cache->expects( $this->once() )->method( 'get' )
180 ->willReturn( false );
182 $cache->expects( $this->once() )->method( 'lock' )
183 ->willReturn( true );
185 // Create config mock
186 $mock = $this->createConfigMock( [
189 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
190 ->willReturn( [ null, 'Fake error', false ] );
192 $this->setExpectedException( ConfigException
::class );
197 * @covers EtcdConfig::load
199 public function testLoadCacheMissWithoutLock() {
201 $cache = $this->getMockBuilder( HashBagOStuff
::class )
202 ->setMethods( [ 'get', 'lock' ] )
204 $cache->expects( $this->exactly( 2 ) )->method( 'get' )
205 ->will( $this->onConsecutiveCalls(
206 // .. misses cache first time
208 // .. hits cache on retry
210 'config' => [ 'known' => 'from-cache' ],
215 $cache->expects( $this->once() )->method( 'lock' )
216 ->willReturn( false );
218 // Create config mock
219 $mock = $this->createConfigMock( [
222 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
224 $this->assertSame( 'from-cache', $mock->get( 'known' ) );
228 * @covers EtcdConfig::load
230 public function testLoadCacheHit() {
232 $cache = $this->getMockBuilder( HashBagOStuff
::class )
233 ->setMethods( [ 'get', 'lock' ] )
235 $cache->expects( $this->once() )->method( 'get' )
238 'config' => [ 'known' => 'from-cache' ],
241 $cache->expects( $this->never() )->method( 'lock' );
243 // Create config mock
244 $mock = $this->createConfigMock( [
247 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
249 $this->assertSame( 'from-cache', $mock->get( 'known' ) );
253 * @covers EtcdConfig::load
255 public function testLoadProcessCacheHit() {
257 $cache = $this->getMockBuilder( HashBagOStuff
::class )
258 ->setMethods( [ 'get', 'lock' ] )
260 $cache->expects( $this->once() )->method( 'get' )
263 'config' => [ 'known' => 'from-cache' ],
266 $cache->expects( $this->never() )->method( 'lock' );
268 // Create config mock
269 $mock = $this->createConfigMock( [
272 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
274 $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Cache hit' );
275 $this->assertSame( 'from-cache', $mock->get( 'known' ), 'Process cache hit' );
279 * @covers EtcdConfig::load
281 public function testLoadCacheExpiredLockFetchSucceeded() {
283 $cache = $this->getMockBuilder( HashBagOStuff
::class )
284 ->setMethods( [ 'get', 'lock' ] )
286 $cache->expects( $this->once() )->method( 'get' )->willReturn(
289 'config' => [ 'known' => 'from-cache-expired' ],
294 $cache->expects( $this->once() )->method( 'lock' )
295 ->willReturn( true );
297 // Create config mock
298 $mock = $this->createConfigMock( [
301 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
302 ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
304 $this->assertSame( 'from-fetch', $mock->get( 'known' ) );
308 * @covers EtcdConfig::load
310 public function testLoadCacheExpiredLockFetchFails() {
312 $cache = $this->getMockBuilder( HashBagOStuff
::class )
313 ->setMethods( [ 'get', 'lock' ] )
315 $cache->expects( $this->once() )->method( 'get' )->willReturn(
318 'config' => [ 'known' => 'from-cache-expired' ],
323 $cache->expects( $this->once() )->method( 'lock' )
324 ->willReturn( true );
326 // Create config mock
327 $mock = $this->createConfigMock( [
330 $mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
331 ->willReturn( [ null, 'Fake failure', true ] );
333 $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
337 * @covers EtcdConfig::load
339 public function testLoadCacheExpiredNoLock() {
341 $cache = $this->getMockBuilder( HashBagOStuff
::class )
342 ->setMethods( [ 'get', 'lock' ] )
344 $cache->expects( $this->once() )->method( 'get' )
345 // .. hits cache (expired value)
347 'config' => [ 'known' => 'from-cache-expired' ],
351 $cache->expects( $this->once() )->method( 'lock' )
352 ->willReturn( false );
354 // Create config mock
355 $mock = $this->createConfigMock( [
358 $mock->expects( $this->never() )->method( 'fetchAllFromEtcd' );
360 $this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );