* Remove "shortcut" in Title::userCanRead, it prevents $wgRevokePermissions and exten...
[lhc/web/wiklou.git] / t / Test.php
1 <?php
2 # See the end of this file for documentation
3
4 # The latest release of this test framework can always be found on CPAN:
5 # http://search.cpan.org/search?query=Test.php
6
7 register_shutdown_function('_test_ends');
8
9 $__Test = array(
10 # How many tests are planned
11 'planned' => null,
12
13 # How many tests we've run, if 'planned' is still null by the time we're
14 # done we report the total count at the end
15 'run' => 0,
16
17 # Are are we currently within todo_start()/todo_end() ?
18 'todo' => array(),
19 );
20
21 function plan($plan, $why = '')
22 {
23 global $__Test;
24
25 $__Test['planned'] = true;
26
27 switch ($plan)
28 {
29 case 'no_plan':
30 $__Test['planned'] = false;
31 break;
32 case 'skip_all';
33 printf("1..0%s\n", $why ? " # Skip $why" : '');
34 exit;
35 default:
36 printf("1..%d\n", $plan);
37 break;
38 }
39 }
40
41 function pass($desc = '')
42 {
43 return _proclaim(true, $desc);
44 }
45
46 function fail($desc = '')
47 {
48 return _proclaim(false, $desc);
49 }
50
51 function ok($cond, $desc = '') {
52 return _proclaim($cond, $desc);
53 }
54
55 function is($got, $expected, $desc = '') {
56 $pass = $got == $expected;
57 return _proclaim($pass, $desc, /* todo */ false, $got, $expected);
58 }
59
60 function isnt($got, $expected, $desc = '') {
61 $pass = $got != $expected;
62 return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true);
63 }
64
65 function like($got, $expected, $desc = '') {
66 $pass = preg_match($expected, $got);
67 return _proclaim($pass, $desc, /* todo */ false, $got, $expected);
68 }
69
70 function unlike($got, $expected, $desc = '') {
71 $pass = !preg_match($expected, $got);
72 return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true);
73 }
74
75 function cmp_ok($got, $op, $expected, $desc = '')
76 {
77 $pass = null;
78
79 # See http://www.php.net/manual/en/language.operators.comparison.php
80 switch ($op)
81 {
82 case '==':
83 $pass = $got == $expected;
84 break;
85 case '===':
86 $pass = $got === $expected;
87 break;
88 case '!=':
89 case '<>':
90 $pass = $got != $expected;
91 break;
92 case '!==':
93 $pass = $got !== $expected;
94 break;
95 case '<':
96 $pass = $got < $expected;
97 break;
98 case '>':
99 $pass = $got > $expected;
100 break;
101 case '<=':
102 $pass = $got <= $expected;
103 break;
104 case '>=':
105 $pass = $got >= $expected;
106 break;
107 default:
108 if (function_exists($op)) {
109 $pass = $op($got, $expected);
110 } else {
111 die("No such operator or function $op\n");
112 }
113 }
114
115 return _proclaim($pass, $desc, /* todo */ false, $got, "$got $op $expected");
116 }
117
118 function diag($message)
119 {
120 if (is_array($message))
121 {
122 $message = implode("\n", $message);
123 }
124
125 foreach (explode("\n", $message) as $line)
126 {
127 echo "# $line\n";
128 }
129 }
130
131 function include_ok($file, $desc = '')
132 {
133 $pass = include $file;
134 return _proclaim($pass, $desc == '' ? "include $file" : $desc);
135 }
136
137 function require_ok($file, $desc = '')
138 {
139 $pass = require $file;
140 return _proclaim($pass, $desc == '' ? "require $file" : $desc);
141 }
142
143 function is_deeply($got, $expected, $desc = '')
144 {
145 $diff = _cmp_deeply($got, $expected);
146 $pass = is_null($diff);
147
148 if (!$pass) {
149 $got = strlen($diff['gpath']) ? ($diff['gpath'] . ' = ' . $diff['got'])
150 : _repl($got);
151 $expected = strlen($diff['epath']) ? ($diff['epath'] . ' = ' . $diff['expected'])
152 : _repl($expected);
153 }
154
155 _proclaim($pass, $desc, /* todo */ false, $got, $expected);
156 }
157
158 function isa_ok($obj, $expected, $desc = '')
159 {
160 $pass = is_a($obj, $expected);
161 _proclaim($pass, $desc, /* todo */ false, $name, $expected);
162 }
163
164 function todo_start($why = '')
165 {
166 global $__Test;
167
168 $__Test['todo'][] = $why;
169 }
170
171 function todo_end()
172 {
173 global $__Test;
174
175 if (count($__Test['todo']) == 0) {
176 die("todo_end() called without a matching todo_start() call");
177 } else {
178 array_pop($__Test['todo']);
179 }
180 }
181
182 #
183 # The code below consists of private utility functions for the above functions
184 #
185
186 function _proclaim(
187 $cond, # bool
188 $desc = '',
189 $todo = false,
190 $got = null,
191 $expected = null,
192 $negate = false) {
193
194 global $__Test;
195
196 $__Test['run'] += 1;
197
198 # We're in a TODO block via todo_start()/todo_end(). TODO via specific
199 # functions is currently unimplemented and will probably stay that way
200 if (count($__Test['todo'])) {
201 $todo = true;
202 }
203
204 # Everything after the first # is special, so escape user-supplied messages
205 $desc = str_replace('#', '\\#', $desc);
206 $desc = str_replace("\n", '\\n', $desc);
207
208 $ok = $cond ? "ok" : "not ok";
209 $directive = '';
210
211 if ($todo) {
212 $todo_idx = count($__Test['todo']) - 1;
213 $directive .= ' # TODO ' . $__Test['todo'][$todo_idx];
214 }
215
216 printf("%s %d %s%s\n", $ok, $__Test['run'], $desc, $directive);
217
218 # report a failure
219 if (!$cond) {
220 # Every public function in this file calls _proclaim so our culprit is
221 # the second item in the stack
222 $caller = debug_backtrace();
223 $call = $caller['1'];
224
225 diag(
226 sprintf(" Failed%stest '%s'\n in %s at line %d\n got: %s\n expected: %s",
227 $todo ? ' TODO ' : ' ',
228 $desc,
229 $call['file'],
230 $call['line'],
231 $got,
232 $expected
233 )
234 );
235 }
236
237 return $cond;
238 }
239
240 function _test_ends()
241 {
242 global $__Test;
243
244 if (count($__Test['todo']) != 0) {
245 $todos = join("', '", $__Test['todo']);
246 die("Missing todo_end() for '$todos'");
247 }
248
249 if (!$__Test['planned']) {
250 printf("1..%d\n", $__Test['run']);
251 }
252 }
253
254 #
255 # All of the below is for is_deeply()
256 #
257
258 function _repl($obj, $deep = true) {
259 if (is_string($obj)) {
260 return "'" . $obj . "'";
261 } else if (is_numeric($obj)) {
262 return $obj;
263 } else if (is_null($obj)) {
264 return 'null';
265 } else if (is_bool($obj)) {
266 return $obj ? 'true' : 'false';
267 } else if (is_array($obj)) {
268 return _repl_array($obj, $deep);
269 }else {
270 return gettype($obj);
271 }
272 }
273
274 function _diff($gpath, $got, $epath, $expected) {
275 return array(
276 'gpath' => $gpath,
277 'got' => $got,
278 'epath' => $epath,
279 'expected' => $expected
280 );
281 }
282
283 function _idx($obj, $path = '') {
284 return $path . '[' . _repl($obj) . ']';
285 }
286
287 function _cmp_deeply($got, $exp, $path = '') {
288 if (is_array($exp)) {
289
290 if (!is_array($got)) {
291 return _diff($path, _repl($got), $path, _repl($exp));
292 }
293
294 $gk = array_keys($got);
295 $ek = array_keys($exp);
296 $mc = max(count($gk), count($ek));
297
298 for ($el = 0; $el < $mc; $el++) {
299 # One array shorter than the other?
300 if ($el >= count($ek)) {
301 return _diff(_idx($gk[$el], $path), _repl($got[$gk[$el]]),
302 'missing', 'nothing');
303 } else if ($el >= count($gk)) {
304 return _diff('missing', 'nothing',
305 _idx($ek[$el], $path), _repl($exp[$ek[$el]]));
306 }
307
308 # Keys differ?
309 if ($gk[$el] != $ek[$el]) {
310 return _diff(_idx($gk[$el], $path), _repl($got[$gk[$el]]),
311 _idx($ek[$el], $path), _repl($exp[$ek[$el]]));
312 }
313
314 # Recurse
315 $rc = _cmp_deeply($got[$gk[$el]], $exp[$ek[$el]], _idx($gk[$el], $path));
316 if (!is_null($rc)) {
317 return $rc;
318 }
319 }
320 }
321 else {
322 # Default to serialize hack
323 if (serialize($got) != serialize($exp)) {
324 return _diff($path, _repl($got), $path, _repl($exp));
325 }
326 }
327
328 return null;
329 }
330
331 function _plural($n, $singular, $plural = null) {
332 if (is_null($plural)) {
333 $plural = $singular . 's';
334 }
335 return $n == 1 ? "$n $singular" : "$n $plural";
336 }
337
338 function _repl_array($obj, $deep) {
339 if ($deep) {
340 $slice = array_slice($obj, 0, 3); # Increase from 3 to show more
341 $repl = array();
342 $next = 0;
343 foreach ($slice as $idx => $el) {
344 $elrep = _repl($el, false);
345 if (is_numeric($idx) && $next == $idx) {
346 // Numeric index
347 $next++;
348 } else {
349 // Out of sequence or non-numeric
350 $elrep = _repl($idx, false) . ' => ' . $elrep;
351 }
352 $repl[] = $elrep;
353 }
354 $more = count($obj) - count($slice);
355 if ($more > 0) {
356 $repl[] = '... ' . _plural($more, 'more element') . ' ...';
357 }
358 return 'array(' . join(', ', $repl) . ')';
359 }
360 else {
361 return 'array(' . count($obj) . ')';
362 }
363 }
364
365 /*
366
367 =head1 NAME
368
369 Test.php - TAP test framework for PHP with a L<Test::More>-like interface
370
371 =head1 SYNOPSIS
372
373 #!/usr/bin/env php
374 <?php
375 require 'Test.php';
376
377 plan($num); # plan $num tests
378 # or
379 plan('no_plan'); # We don't know how many
380 # or
381 plan('skip_all'); # Skip all tests
382 # or
383 plan('skip_all', $reason); # Skip all tests with a reason
384
385 diag('message in test output') # Trailing \n not required
386
387 # $test_name is always optional and should be a short description of
388 # the test, e.g. "some_function() returns an integer"
389
390 # Various ways to say "ok"
391 ok($got == $expected, $test_name);
392
393 # Compare with == and !=
394 is($got, $expected, $test_name);
395 isnt($got, $expected, $test_name);
396
397 # Run a preg regex match on some data
398 like($got, $regex, $test_name);
399 unlike($got, $regex, $test_name);
400
401 # Compare something with a given comparison operator
402 cmp_ok($got, '==', $expected, $test_name);
403 # Compare something with a comparison function (should return bool)
404 cmp_ok($got, $func, $expected, $test_name);
405
406 # Recursively check datastructures for equalness
407 is_deeply($got, $expected, $test_name);
408
409 # Always pass or fail a test under an optional name
410 pass($test_name);
411 fail($test_name);
412
413 # TODO tests, these are expected to fail but won't fail the test run,
414 # unexpected success will be reported
415 todo_start("integer arithmetic still working");
416 ok(1 + 2 == 3);
417 {
418 # TODOs can be nested
419 todo_start("string comparison still working")
420 is("foo", "bar");
421 todo_end();
422 }
423 todo_end();
424 ?>
425
426 =head1 DESCRIPTION
427
428 F<Test.php> is an implementation of Perl's L<Test::More> for PHP. Like
429 Test::More it produces language agnostic TAP output (see L<TAP>) which
430 can then be gathered, formatted and summarized by a program that
431 understands TAP such as prove(1).
432
433 =head1 HOWTO
434
435 First place the F<Test.php> in the project root or somewhere else in
436 the include path where C<require> and C<include> will find it.
437
438 Then make a place to put your tests in, it's customary to place TAP
439 tests in a directory named F<t> under the root but they can be
440 anywhere you like. Make a test in this directory or one of its subdirs
441 and try running it with php(1):
442
443 $ php t/pass.t
444 1..1
445 ok 1 This dummy test passed
446
447 The TAP output consists of very simple output, of course reading
448 larger output is going to be harder which is where prove(1) comes
449 in. prove is a harness program that reads test output and produces
450 reports based on it:
451
452 $ prove t/pass.t
453 t/pass....ok
454 All tests successful.
455 Files=1, Tests=1, 0 wallclock secs ( 0.03 cusr + 0.02 csys = 0.05 CPU)
456
457 To run all the tests in the F<t> directory recursively use C<prove -r
458 t>. This can be put in a F<Makefile> under a I<test> target, for
459 example:
460
461 test: Test.php
462 prove -r t
463
464 For reference the example test file above looks like this, the shebang
465 on the first line is needed so that prove(1) and other test harness
466 programs know they're dealing with a PHP file.
467
468 #!/usr/bin/env php
469 <?php
470
471 require 'Test.php';
472
473 plan(1);
474 pass('This dummy test passed');
475 ?>
476
477 =head1 SEE ALSO
478
479 L<TAP> - The TAP protocol
480
481 =head1 AUTHOR
482
483 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org> and Andy Armstrong <andy@hexten.net>
484
485 =head1 LICENSING
486
487 The author or authors of this code dedicate any and all copyright
488 interest in this code to the public domain. We make this dedication
489 for the benefit of the public at large and to the detriment of our
490 heirs and successors. We intend this dedication to be an overt act of
491 relinquishment in perpetuity of all present and future rights this
492 code under copyright law.
493
494 =cut
495
496 */