Fix flaky MessageBlobStoreTest assertion failures
[lhc/web/wiklou.git] / tests / phpunit / includes / resourceloader / MessageBlobStoreTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @group Cache
7 * @covers MessageBlobStore
8 */
9 class MessageBlobStoreTest extends PHPUnit\Framework\TestCase {
10
11 use MediaWikiCoversValidator;
12
13 protected function setUp() {
14 parent::setUp();
15 // MediaWiki tests defaults $wgMainWANCache to CACHE_NONE.
16 // Use hash instead so that caching is observed
17 $this->wanCache = $this->getMockBuilder( WANObjectCache::class )
18 ->setConstructorArgs( [ [
19 'cache' => new HashBagOStuff(),
20 'pool' => 'test',
21 'relayer' => new EventRelayerNull( [] )
22 ] ] )
23 ->setMethods( [ 'makePurgeValue' ] )
24 ->getMock();
25
26 $this->wanCache->expects( $this->any() )
27 ->method( 'makePurgeValue' )
28 ->will( $this->returnCallback( function ( $timestamp, $holdoff ) {
29 // Disable holdoff as it messes with testing. Aside from a 0-second holdoff,
30 // make sure that "time" passes between getMulti() check init and the set()
31 // in recacheMessageBlob(). This especially matters for Windows clocks.
32 $ts = (float)$timestamp - 0.0001;
33
34 return WANObjectCache::PURGE_VAL_PREFIX . $ts . ':0';
35 } ) );
36 }
37
38 protected function makeBlobStore( $methods = null, $rl = null ) {
39 $blobStore = $this->getMockBuilder( MessageBlobStore::class )
40 ->setConstructorArgs( [ $rl ] )
41 ->setMethods( $methods )
42 ->getMock();
43
44 $access = TestingAccessWrapper::newFromObject( $blobStore );
45 $access->wanCache = $this->wanCache;
46 return $blobStore;
47 }
48
49 protected function makeModule( array $messages ) {
50 $module = new ResourceLoaderTestModule( [ 'messages' => $messages ] );
51 $module->setName( 'test.blobstore' );
52 return $module;
53 }
54
55 /** @covers MessageBlobStore::setLogger */
56 public function testSetLogger() {
57 $blobStore = $this->makeBlobStore();
58 $this->assertSame( null, $blobStore->setLogger( new Psr\Log\NullLogger() ) );
59 }
60
61 /** @covers MessageBlobStore::getResourceLoader */
62 public function testGetResourceLoader() {
63 // Call protected method
64 $blobStore = TestingAccessWrapper::newFromObject( $this->makeBlobStore() );
65 $this->assertInstanceOf(
66 ResourceLoader::class,
67 $blobStore->getResourceLoader()
68 );
69 }
70
71 /** @covers MessageBlobStore::fetchMessage */
72 public function testFetchMessage() {
73 $module = $this->makeModule( [ 'mainpage' ] );
74 $rl = new ResourceLoader();
75 $rl->register( $module->getName(), $module );
76
77 $blobStore = $this->makeBlobStore( null, $rl );
78 $blob = $blobStore->getBlob( $module, 'en' );
79
80 $this->assertEquals( '{"mainpage":"Main Page"}', $blob, 'Generated blob' );
81 }
82
83 /** @covers MessageBlobStore::fetchMessage */
84 public function testFetchMessageFail() {
85 $module = $this->makeModule( [ 'i-dont-exist' ] );
86 $rl = new ResourceLoader();
87 $rl->register( $module->getName(), $module );
88
89 $blobStore = $this->makeBlobStore( null, $rl );
90 $blob = $blobStore->getBlob( $module, 'en' );
91
92 $this->assertEquals( '{"i-dont-exist":"\u29fci-dont-exist\u29fd"}', $blob, 'Generated blob' );
93 }
94
95 public function testGetBlob() {
96 $module = $this->makeModule( [ 'foo' ] );
97 $rl = new ResourceLoader();
98 $rl->register( $module->getName(), $module );
99
100 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
101 $blobStore->expects( $this->once() )
102 ->method( 'fetchMessage' )
103 ->will( $this->returnValue( 'Example' ) );
104
105 $blob = $blobStore->getBlob( $module, 'en' );
106
107 $this->assertEquals( '{"foo":"Example"}', $blob, 'Generated blob' );
108 }
109
110 /**
111 * Seems to fail sometimes (T176097).
112 *
113 * @group Broken
114 */
115 public function testGetBlobCached() {
116 $module = $this->makeModule( [ 'example' ] );
117 $rl = new ResourceLoader();
118 $rl->register( $module->getName(), $module );
119
120 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
121 $blobStore->expects( $this->once() )
122 ->method( 'fetchMessage' )
123 ->will( $this->returnValue( 'First' ) );
124
125 $module = $this->makeModule( [ 'example' ] );
126 $blob = $blobStore->getBlob( $module, 'en' );
127 $this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
128
129 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
130 $blobStore->expects( $this->never() )
131 ->method( 'fetchMessage' )
132 ->will( $this->returnValue( 'Second' ) );
133
134 $module = $this->makeModule( [ 'example' ] );
135 $blob = $blobStore->getBlob( $module, 'en' );
136 $this->assertEquals( '{"example":"First"}', $blob, 'Cache hit' );
137 }
138
139 public function testUpdateMessage() {
140 $module = $this->makeModule( [ 'example' ] );
141 $rl = new ResourceLoader();
142 $rl->register( $module->getName(), $module );
143 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
144 $blobStore->expects( $this->once() )
145 ->method( 'fetchMessage' )
146 ->will( $this->returnValue( 'First' ) );
147
148 $blob = $blobStore->getBlob( $module, 'en' );
149 $this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
150
151 $blobStore->updateMessage( 'example' );
152
153 $module = $this->makeModule( [ 'example' ] );
154 $rl = new ResourceLoader();
155 $rl->register( $module->getName(), $module );
156 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
157 $blobStore->expects( $this->once() )
158 ->method( 'fetchMessage' )
159 ->will( $this->returnValue( 'Second' ) );
160
161 $blob = $blobStore->getBlob( $module, 'en' );
162 $this->assertEquals( '{"example":"Second"}', $blob, 'Updated blob' );
163 }
164
165 public function testValidation() {
166 $module = $this->makeModule( [ 'foo' ] );
167 $rl = new ResourceLoader();
168 $rl->register( $module->getName(), $module );
169
170 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
171 $blobStore->expects( $this->once() )
172 ->method( 'fetchMessage' )
173 ->will( $this->returnValueMap( [
174 [ 'foo', 'en', 'Hello' ],
175 ] ) );
176
177 $blob = $blobStore->getBlob( $module, 'en' );
178 $this->assertEquals( '{"foo":"Hello"}', $blob, 'Generated blob' );
179
180 // Now, imagine a change to the module is deployed. The module now contains
181 // message 'foo' and 'bar'. While updateMessage() was not called (since no
182 // message values were changed) it should detect the change in list of
183 // message keys.
184 $module = $this->makeModule( [ 'foo', 'bar' ] );
185 $rl = new ResourceLoader();
186 $rl->register( $module->getName(), $module );
187
188 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
189 $blobStore->expects( $this->exactly( 2 ) )
190 ->method( 'fetchMessage' )
191 ->will( $this->returnValueMap( [
192 [ 'foo', 'en', 'Hello' ],
193 [ 'bar', 'en', 'World' ],
194 ] ) );
195
196 $blob = $blobStore->getBlob( $module, 'en' );
197 $this->assertEquals( '{"foo":"Hello","bar":"World"}', $blob, 'Updated blob' );
198 }
199
200 public function testClear() {
201 $module = $this->makeModule( [ 'example' ] );
202 $rl = new ResourceLoader();
203 $rl->register( $module->getName(), $module );
204 $blobStore = $this->makeBlobStore( [ 'fetchMessage' ], $rl );
205 $blobStore->expects( $this->exactly( 2 ) )
206 ->method( 'fetchMessage' )
207 ->will( $this->onConsecutiveCalls( 'First', 'Second' ) );
208
209 $now = microtime( true );
210 $this->wanCache->setMockTime( $now );
211
212 $blob = $blobStore->getBlob( $module, 'en' );
213 $this->assertEquals( '{"example":"First"}', $blob, 'Generated blob' );
214
215 $blob = $blobStore->getBlob( $module, 'en' );
216 $this->assertEquals( '{"example":"First"}', $blob, 'Cache-hit' );
217
218 $now += 1;
219 $blobStore->clear();
220
221 $blob = $blobStore->getBlob( $module, 'en' );
222 $this->assertEquals( '{"example":"Second"}', $blob, 'Updated blob' );
223 }
224 }