]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - t/Test.php
MediaWiki 1.14.0
[autoinstalls/mediawiki.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 */