Complete test coverage of Hooks class
[lhc/web/wiklou.git] / tests / phpunit / includes / HooksTest.php
1 <?php
2
3 class HooksTest extends MediaWikiTestCase {
4
5 function setUp() {
6 global $wgHooks;
7 parent::setUp();
8 Hooks::clear( 'MediaWikiHooksTest001' );
9 unset( $wgHooks['MediaWikiHooksTest001'] );
10 }
11
12 public static function provideHooks() {
13 $i = new NothingClass();
14
15 return [
16 [
17 'Object and method',
18 [ $i, 'someNonStatic' ],
19 'changed-nonstatic',
20 'changed-nonstatic'
21 ],
22 [ 'Object and no method', [ $i ], 'changed-onevent', 'original' ],
23 [
24 'Object and method with data',
25 [ $i, 'someNonStaticWithData', 'data' ],
26 'data',
27 'original'
28 ],
29 [ 'Object and static method', [ $i, 'someStatic' ], 'changed-static', 'original' ],
30 [
31 'Class::method static call',
32 [ 'NothingClass::someStatic' ],
33 'changed-static',
34 'original'
35 ],
36 [
37 'Class::method static call as array',
38 [ [ 'NothingClass::someStatic' ] ],
39 'changed-static',
40 'original'
41 ],
42 [ 'Global function', [ 'NothingFunction' ], 'changed-func', 'original' ],
43 [ 'Global function with data', [ 'NothingFunctionData', 'data' ], 'data', 'original' ],
44 [ 'Closure', [ function ( &$foo, $bar ) {
45 $foo = 'changed-closure';
46
47 return true;
48 } ], 'changed-closure', 'original' ],
49 [ 'Closure with data', [ function ( $data, &$foo, $bar ) {
50 $foo = $data;
51
52 return true;
53 }, 'data' ], 'data', 'original' ]
54 ];
55 }
56
57 /**
58 * @dataProvider provideHooks
59 * @covers ::wfRunHooks
60 */
61 public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) {
62 global $wgHooks;
63
64 $this->hideDeprecated( 'wfRunHooks' );
65 $foo = $bar = 'original';
66
67 $wgHooks['MediaWikiHooksTest001'][] = $hook;
68 wfRunHooks( 'MediaWikiHooksTest001', [ &$foo, &$bar ] );
69
70 $this->assertSame( $expectedFoo, $foo, $msg );
71 $this->assertSame( $expectedBar, $bar, $msg );
72 }
73
74 /**
75 * @dataProvider provideHooks
76 * @covers Hooks::register
77 * @covers Hooks::run
78 * @covers Hooks::callHook
79 */
80 public function testNewStyleHooks( $msg, $hook, $expectedFoo, $expectedBar ) {
81 $foo = $bar = 'original';
82
83 Hooks::register( 'MediaWikiHooksTest001', $hook );
84 Hooks::run( 'MediaWikiHooksTest001', [ &$foo, &$bar ] );
85
86 $this->assertSame( $expectedFoo, $foo, $msg );
87 $this->assertSame( $expectedBar, $bar, $msg );
88 }
89
90 /**
91 * @covers Hooks::getHandlers
92 */
93 public function testGetHandlers() {
94 global $wgHooks;
95
96 $this->assertSame(
97 [],
98 Hooks::getHandlers( 'MediaWikiHooksTest001' ),
99 'No hooks registered'
100 );
101
102 $a = new NothingClass();
103 $b = new NothingClass();
104
105 $wgHooks['MediaWikiHooksTest001'][] = $a;
106
107 $this->assertSame(
108 [ $a ],
109 Hooks::getHandlers( 'MediaWikiHooksTest001' ),
110 'Hook registered by $wgHooks'
111 );
112
113 Hooks::register( 'MediaWikiHooksTest001', $b );
114 $this->assertSame(
115 [ $b, $a ],
116 Hooks::getHandlers( 'MediaWikiHooksTest001' ),
117 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register'
118 );
119
120 Hooks::clear( 'MediaWikiHooksTest001' );
121 unset( $wgHooks['MediaWikiHooksTest001'] );
122
123 Hooks::register( 'MediaWikiHooksTest001', $b );
124 $this->assertSame(
125 [ $b ],
126 Hooks::getHandlers( 'MediaWikiHooksTest001' ),
127 'Hook registered by Hook::register'
128 );
129 }
130
131 /**
132 * @covers Hooks::isRegistered
133 * @covers Hooks::register
134 * @covers Hooks::run
135 * @covers Hooks::callHook
136 */
137 public function testNewStyleHookInteraction() {
138 global $wgHooks;
139
140 $a = new NothingClass();
141 $b = new NothingClass();
142
143 $wgHooks['MediaWikiHooksTest001'][] = $a;
144 $this->assertTrue(
145 Hooks::isRegistered( 'MediaWikiHooksTest001' ),
146 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered'
147 );
148
149 Hooks::register( 'MediaWikiHooksTest001', $b );
150 $this->assertEquals(
151 2,
152 count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ),
153 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register'
154 );
155
156 $foo = 'quux';
157 $bar = 'qaax';
158
159 Hooks::run( 'MediaWikiHooksTest001', [ &$foo, &$bar ] );
160 $this->assertEquals(
161 1,
162 $a->calls,
163 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register'
164 );
165 $this->assertEquals(
166 1,
167 $b->calls,
168 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register'
169 );
170 }
171
172 /**
173 * @expectedException MWException
174 * @covers Hooks::run
175 * @covers Hooks::callHook
176 */
177 public function testUncallableFunction() {
178 Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' );
179 Hooks::run( 'MediaWikiHooksTest001', [] );
180 }
181
182 /**
183 * @covers Hooks::run
184 * @covers Hooks::callHook
185 */
186 public function testFalseReturn() {
187 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
188 return false;
189 } );
190 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
191 $foo = 'test';
192
193 return true;
194 } );
195 $foo = 'original';
196 Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] );
197 $this->assertSame( 'original', $foo, 'Hooks abort after a false return.' );
198 }
199
200 /**
201 * @covers Hooks::run
202 */
203 public function testNullReturn() {
204 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
205 return;
206 } );
207 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
208 $foo = 'test';
209
210 return true;
211 } );
212 $foo = 'original';
213 Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] );
214 $this->assertSame( 'test', $foo, 'Hooks continue after a null return.' );
215 }
216
217 /**
218 * @covers Hooks::callHook
219 */
220 public function testCallHook_FalseHook() {
221 Hooks::register( 'MediaWikiHooksTest001', false );
222 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
223 $foo = 'test';
224
225 return true;
226 } );
227 $foo = 'original';
228 Hooks::run( 'MediaWikiHooksTest001', [ &$foo ] );
229 $this->assertSame( 'test', $foo, 'Hooks that are falsey are skipped.' );
230 }
231
232 /**
233 * @covers Hooks::callHook
234 * @expectedException MWException
235 */
236 public function testCallHook_UnknownDatatype() {
237 Hooks::register( 'MediaWikiHooksTest001', 12345 );
238 Hooks::run( 'MediaWikiHooksTest001' );
239 }
240
241 /**
242 * @covers Hooks::callHook
243 * @expectedException PHPUnit_Framework_Error_Deprecated
244 */
245 public function testCallHook_Deprecated() {
246 Hooks::register( 'MediaWikiHooksTest001', 'NothingClass::someStatic' );
247 Hooks::run( 'MediaWikiHooksTest001', [], '1.31' );
248 }
249
250 /**
251 * @covers Hooks::runWithoutAbort
252 * @covers Hooks::callHook
253 */
254 public function testRunWithoutAbort() {
255 $list = [];
256 Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) {
257 $list[] = 1;
258 return true; // Explicit true
259 } );
260 Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) {
261 $list[] = 2;
262 return; // Implicit null
263 } );
264 Hooks::register( 'MediaWikiHooksTest001', function ( &$list ) {
265 $list[] = 3;
266 // No return
267 } );
268
269 Hooks::runWithoutAbort( 'MediaWikiHooksTest001', [ &$list ] );
270 $this->assertSame( [ 1, 2, 3 ], $list, 'All hooks ran.' );
271 }
272
273 /**
274 * @covers Hooks::runWithoutAbort
275 * @covers Hooks::callHook
276 */
277 public function testRunWithoutAbortWarning() {
278 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
279 return false;
280 } );
281 Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
282 $foo = 'test';
283 return true;
284 } );
285 $foo = 'original';
286
287 $this->setExpectedException(
288 UnexpectedValueException::class,
289 'Invalid return from hook-MediaWikiHooksTest001-closure for ' .
290 'unabortable MediaWikiHooksTest001'
291 );
292 Hooks::runWithoutAbort( 'MediaWikiHooksTest001', [ &$foo ] );
293 }
294
295 /**
296 * @expectedException FatalError
297 * @covers Hooks::run
298 */
299 public function testFatalError() {
300 Hooks::register( 'MediaWikiHooksTest001', function () {
301 return 'test';
302 } );
303 Hooks::run( 'MediaWikiHooksTest001', [] );
304 }
305 }
306
307 function NothingFunction( &$foo, &$bar ) {
308 $foo = 'changed-func';
309
310 return true;
311 }
312
313 function NothingFunctionData( $data, &$foo, &$bar ) {
314 $foo = $data;
315
316 return true;
317 }
318
319 class NothingClass {
320 public $calls = 0;
321
322 public static function someStatic( &$foo, &$bar ) {
323 $foo = 'changed-static';
324
325 return true;
326 }
327
328 public function someNonStatic( &$foo, &$bar ) {
329 $this->calls++;
330 $foo = 'changed-nonstatic';
331 $bar = 'changed-nonstatic';
332
333 return true;
334 }
335
336 public function onMediaWikiHooksTest001( &$foo, &$bar ) {
337 $this->calls++;
338 $foo = 'changed-onevent';
339
340 return true;
341 }
342
343 public function someNonStaticWithData( $data, &$foo, &$bar ) {
344 $this->calls++;
345 $foo = $data;
346
347 return true;
348 }
349 }