2 # See the end of this file for documentation
4 # The latest release of this test framework can always be found on CPAN:
5 # http://search.cpan.org/search?query=Test.php
7 register_shutdown_function('_test_ends');
10 # How many tests are planned
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
17 # Are are we currently within todo_start()/todo_end() ?
21 function plan($plan, $why = '')
25 $__Test['planned'] = true;
30 $__Test['planned'] = false;
33 printf("1..0%s\n", $why ?
" # Skip $why" : '');
36 printf("1..%d\n", $plan);
41 function pass($desc = '')
43 return _proclaim(true, $desc);
46 function fail($desc = '')
48 return _proclaim(false, $desc);
51 function ok($cond, $desc = '') {
52 return _proclaim($cond, $desc);
55 function is($got, $expected, $desc = '') {
56 $pass = $got == $expected;
57 return _proclaim($pass, $desc, /* todo */ false, $got, $expected);
60 function isnt($got, $expected, $desc = '') {
61 $pass = $got != $expected;
62 return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true);
65 function like($got, $expected, $desc = '') {
66 $pass = preg_match($expected, $got);
67 return _proclaim($pass, $desc, /* todo */ false, $got, $expected);
70 function unlike($got, $expected, $desc = '') {
71 $pass = !preg_match($expected, $got);
72 return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true);
75 function cmp_ok($got, $op, $expected, $desc = '')
79 # See http://www.php.net/manual/en/language.operators.comparison.php
83 $pass = $got == $expected;
86 $pass = $got === $expected;
90 $pass = $got != $expected;
93 $pass = $got !== $expected;
96 $pass = $got < $expected;
99 $pass = $got > $expected;
102 $pass = $got <= $expected;
105 $pass = $got >= $expected;
108 if (function_exists($op)) {
109 $pass = $op($got, $expected);
111 die("No such operator or function $op\n");
115 return _proclaim($pass, $desc, /* todo */ false, $got, "$got $op $expected");
118 function diag($message)
120 if (is_array($message))
122 $message = implode("\n", $message);
125 foreach (explode("\n", $message) as $line)
131 function include_ok($file, $desc = '')
133 $pass = include $file;
134 return _proclaim($pass, $desc == '' ?
"include $file" : $desc);
137 function require_ok($file, $desc = '')
139 $pass = require $file;
140 return _proclaim($pass, $desc == '' ?
"require $file" : $desc);
143 function is_deeply($got, $expected, $desc = '')
145 $diff = _cmp_deeply($got, $expected);
146 $pass = is_null($diff);
149 $got = strlen($diff['gpath']) ?
($diff['gpath'] . ' = ' . $diff['got'])
151 $expected = strlen($diff['epath']) ?
($diff['epath'] . ' = ' . $diff['expected'])
155 _proclaim($pass, $desc, /* todo */ false, $got, $expected);
158 function isa_ok($obj, $expected, $desc = '')
160 $pass = is_a($obj, $expected);
161 _proclaim($pass, $desc, /* todo */ false, $name, $expected);
164 function todo_start($why = '')
168 $__Test['todo'][] = $why;
175 if (count($__Test['todo']) == 0) {
176 die("todo_end() called without a matching todo_start() call");
178 array_pop($__Test['todo']);
183 # The code below consists of private utility functions for the above functions
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'])) {
204 # Everything after the first # is special, so escape user-supplied messages
205 $desc = str_replace('#', '\\#', $desc);
206 $desc = str_replace("\n", '\\n', $desc);
208 $ok = $cond ?
"ok" : "not ok";
212 $todo_idx = count($__Test['todo']) - 1;
213 $directive .= ' # TODO ' . $__Test['todo'][$todo_idx];
216 printf("%s %d %s%s\n", $ok, $__Test['run'], $desc, $directive);
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'];
226 sprintf(" Failed%stest '%s'\n in %s at line %d\n got: %s\n expected: %s",
227 $todo ?
' TODO ' : ' ',
240 function _test_ends()
244 if (count($__Test['todo']) != 0) {
245 $todos = join("', '", $__Test['todo']);
246 die("Missing todo_end() for '$todos'");
249 if (!$__Test['planned']) {
250 printf("1..%d\n", $__Test['run']);
255 # All of the below is for is_deeply()
258 function _repl($obj, $deep = true) {
259 if (is_string($obj)) {
260 return "'" . $obj . "'";
261 } else if (is_numeric($obj)) {
263 } else if (is_null($obj)) {
265 } else if (is_bool($obj)) {
266 return $obj ?
'true' : 'false';
267 } else if (is_array($obj)) {
268 return _repl_array($obj, $deep);
270 return gettype($obj);
274 function _diff($gpath, $got, $epath, $expected) {
279 'expected' => $expected
283 function _idx($obj, $path = '') {
284 return $path . '[' . _repl($obj) . ']';
287 function _cmp_deeply($got, $exp, $path = '') {
288 if (is_array($exp)) {
290 if (!is_array($got)) {
291 return _diff($path, _repl($got), $path, _repl($exp));
294 $gk = array_keys($got);
295 $ek = array_keys($exp);
296 $mc = max(count($gk), count($ek));
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]]));
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]]));
315 $rc = _cmp_deeply($got[$gk[$el]], $exp[$ek[$el]], _idx($gk[$el], $path));
322 # Default to serialize hack
323 if (serialize($got) != serialize($exp)) {
324 return _diff($path, _repl($got), $path, _repl($exp));
331 function _plural($n, $singular, $plural = null) {
332 if (is_null($plural)) {
333 $plural = $singular . 's';
335 return $n == 1 ?
"$n $singular" : "$n $plural";
338 function _repl_array($obj, $deep) {
340 $slice = array_slice($obj, 0, 3); # Increase from 3 to show more
343 foreach ($slice as $idx => $el) {
344 $elrep = _repl($el, false);
345 if (is_numeric($idx) && $next == $idx) {
349 // Out of sequence or non-numeric
350 $elrep = _repl($idx, false) . ' => ' . $elrep;
354 $more = count($obj) - count($slice);
356 $repl[] = '... ' . _plural($more, 'more element') . ' ...';
358 return 'array(' . join(', ', $repl) . ')';
361 return 'array(' . count($obj) . ')';
369 Test.php - TAP test framework for PHP with a L<Test::More>-like interface
377 plan($num); # plan $num tests
379 plan('no_plan'); # We don't know how many
381 plan('skip_all'); # Skip all tests
383 plan('skip_all', $reason); # Skip all tests with a reason
385 diag('message in test output') # Trailing \n not required
387 # $test_name is always optional and should be a short description of
388 # the test, e.g. "some_function() returns an integer"
390 # Various ways to say "ok"
391 ok($got == $expected, $test_name);
393 # Compare with == and !=
394 is($got, $expected, $test_name);
395 isnt($got, $expected, $test_name);
397 # Run a preg regex match on some data
398 like($got, $regex, $test_name);
399 unlike($got, $regex, $test_name);
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);
406 # Recursively check datastructures for equalness
407 is_deeply($got, $expected, $test_name);
409 # Always pass or fail a test under an optional name
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");
418 # TODOs can be nested
419 todo_start("string comparison still working")
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).
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.
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):
445 ok 1 This dummy test passed
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
454 All tests successful.
455 Files=1, Tests=1, 0 wallclock secs ( 0.03 cusr + 0.02 csys = 0.05 CPU)
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
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.
474 pass('This dummy test passed');
479 L<TAP> - The TAP protocol
483 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org> and Andy Armstrong <andy@hexten.net>
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.