From: Ævar Arnfjörð Bjarmason Date: Tue, 17 Jul 2007 03:32:43 +0000 (+0000) Subject: Importing Test.php 0.12 from CPAN X-Git-Tag: 1.31.0-rc.0~52058 X-Git-Url: https://git.cyclocoop.org/%28%28?a=commitdiff_plain;h=de00803e22564e6726a2e22976da537033322d12;p=lhc%2Fweb%2Fwiklou.git Importing Test.php 0.12 from CPAN --- diff --git a/Test.php b/Test.php index d6384d8571..d6a2cf9100 100644 --- a/Test.php +++ b/Test.php @@ -1,307 +1,498 @@ for PHP - -=head1 SYNOPSIS - - require 'Test.php'; - - plan( $num ); # plan $num tests - # or - plan( 'no_plan' ); # We don't know how many - # or - plan( 'skip_all' ); # Skip all tests - # or - plan( 'skip_all', $reason ); # Skip all tests with a reason - - diag( 'message in test output' ) # Trailing \n not required - - # $test_name is always optional and should be a short description of - # the test, e.g. "some_function() returns an integer" +# The latest release of this test framework can always be found on CPAN: +# http://search.cpan.org/search?query=Test.php - # Various ways to say "ok" - ok( $got == $expected, $test_name ); - - # Compare with == and != - is( $got, $expected, $test_name ); - isnt( $got, $expected, $test_name ); - - # Run a preg match on some data - like( $got, $regex, $test_name ); - unlike( $got, $regex, $test_name ); - - # Compare something with a given comparison operator - cmp_ok( $got, '==', $expected, $test_name ); - # Compare something with a comparison function (should return bool) - cmp_ok( $got, $func, $expected, $test_name ); - - # Recursively check datastructures for equalness - is_deeply( $got, $expected, $test_name ); - - # Always pass or fail a test under an optional name - pass( $test_name ); - fail( $test_name ); +register_shutdown_function('_test_ends'); -=head1 DESCRIPTION +$__Test = array( + # How many tests are planned + 'planned' => null, -F is an implementation of Perl's L and Pugs's B for -PHP. Like those two modules it produces TAP output (see L) which -can then be gathered, formatted and summarized by a program that -understands TAP such as L. + # How many tests we've run, if 'planned' is still null by the time we're + # done we report the total count at the end + 'run' => 0, -=cut - -*/ - -register_shutdown_function('test_ends'); - -$Test = array( - 'run' => 0, - 'failed' => 0, - 'badpass' => 0, - 'planned' => null + # Are are we currently within todo_start()/todo_end() ? + 'todo' => array(), ); -function plan( $plan, $why = '' ) +function plan($plan, $why = '') { - global $Test; - - $Test['planned'] = true; - - switch ( $plan ) - { - case 'no_plan': - $Test['planned'] = false; - break; - case 'skip_all'; - printf( "1..0%s\n", $why ? " # Skip $why" : '' ); - exit; - default: - printf( "1..%d\n", $plan ); - break; - } + global $__Test; + + $__Test['planned'] = true; + + switch ($plan) + { + case 'no_plan': + $__Test['planned'] = false; + break; + case 'skip_all'; + printf("1..0%s\n", $why ? " # Skip $why" : ''); + exit; + default: + printf("1..%d\n", $plan); + break; + } } -function pass( $desc = '' ) +function pass($desc = '') { - return proclaim(true, $desc); + return _proclaim(true, $desc); } -function fail( $desc = '' ) +function fail($desc = '') { - return proclaim( false, $desc ); + return _proclaim(false, $desc); } -function ok( $cond, $desc = '' ) { - return proclaim( $cond, $desc ); +function ok($cond, $desc = '') { + return _proclaim($cond, $desc); } -function is( $got, $expected, $desc = '' ) { - $pass = $got == $expected; - return proclaim( $pass, $desc, /* todo */ false, $got, $expected ); +function is($got, $expected, $desc = '') { + $pass = $got == $expected; + return _proclaim($pass, $desc, /* todo */ false, $got, $expected); } -function isnt( $got, $expected, $desc = '' ) { - $pass = $got != $expected; - return proclaim( $pass, $desc, /* todo */ false, $got, $expected, /* negated */ true ); +function isnt($got, $expected, $desc = '') { + $pass = $got != $expected; + return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true); } -function like( $got, $expected, $desc = '' ) { - $pass = preg_match( $expected, $got ); - return proclaim( $pass, $desc, /* todo */ false, $got, $expected ); +function like($got, $expected, $desc = '') { + $pass = preg_match($expected, $got); + return _proclaim($pass, $desc, /* todo */ false, $got, $expected); } -function unlike( $got, $expected, $desc = '' ) { - $pass = ! preg_match( $expected, $got ); - return proclaim( $pass, $desc, /* todo */ false, $got, $expected, /* negated */ true ); +function unlike($got, $expected, $desc = '') { + $pass = !preg_match($expected, $got); + return _proclaim($pass, $desc, /* todo */ false, $got, $expected, /* negated */ true); } function cmp_ok($got, $op, $expected, $desc = '') { - $pass = null; - - /* See http://www.php.net/manual/en/language.operators.comparison.php */ - switch ($op) - { - case '==': - $pass = $got == $expected; - break; - case '===': - $pass = $got === $expected; - break; - case '!=': - case '<>': - $pass = $got != $expected; - break; - case '!==': - $pass = $got !== $expected; - break; - case '<': - $pass = $got < $expected; - break; - case '>': - $pass = $got > $expected; - break; - case '<=': - $pass = $got <= $expected; - break; - case '>=': - $pass = $got >= $expected; - break; - default: - if ( function_exists( $op ) ) { - $pass = $op( $got, $expected ); - } else { - die("No such operator or function $op\n"); - } - } - - return proclaim( $pass, $desc, /* todo */ false, $got, "$op $expected" ); + $pass = null; + + # See http://www.php.net/manual/en/language.operators.comparison.php + switch ($op) + { + case '==': + $pass = $got == $expected; + break; + case '===': + $pass = $got === $expected; + break; + case '!=': + case '<>': + $pass = $got != $expected; + break; + case '!==': + $pass = $got !== $expected; + break; + case '<': + $pass = $got < $expected; + break; + case '>': + $pass = $got > $expected; + break; + case '<=': + $pass = $got <= $expected; + break; + case '>=': + $pass = $got >= $expected; + break; + default: + if (function_exists($op)) { + $pass = $op($got, $expected); + } else { + die("No such operator or function $op\n"); + } + } + + return _proclaim($pass, $desc, /* todo */ false, $got, "$got $op $expected"); } function diag($message) { if (is_array($message)) - { - $message = implode("\n", $message); - } - - $messages = explode("\n", $message); + { + $message = implode("\n", $message); + } - foreach ($messages as $msg) - { - echo "# $msg\n"; + foreach (explode("\n", $message) as $line) + { + echo "# $line\n"; } } -function include_ok( $file, $desc = '' ) +function include_ok($file, $desc = '') { $pass = include $file; - return proclaim( $pass, $desc == '' ? "include $file" : $desc ); + return _proclaim($pass, $desc == '' ? "include $file" : $desc); } -function require_ok( $file, $desc = '' ) +function require_ok($file, $desc = '') { $pass = require $file; - return proclaim( $pass, $desc == '' ? "require $file" : $desc ); + return _proclaim($pass, $desc == '' ? "require $file" : $desc); } -function is_deeply( $got, $expected, $desc = '' ) +function is_deeply($got, $expected, $desc = '') { - // hack - $s_got = serialize( $got ); - $s_exp = serialize( $expected ); + $diff = _cmp_deeply($got, $expected); + $pass = is_null($diff); + + if (!$pass) { + $got = strlen($diff['gpath']) ? ($diff['gpath'] . ' = ' . $diff['got']) + : _repl($got); + $expected = strlen($diff['epath']) ? ($diff['epath'] . ' = ' . $diff['expected']) + : _repl($expected); + } - $pass = $s_got == $s_exp; + _proclaim($pass, $desc, /* todo */ false, $got, $expected); +} - proclaim( $pass, $desc, /* todo */ false, $got, $expected ); +function isa_ok($obj, $expected, $desc = '') +{ + $pass = is_a($obj, $expected); + _proclaim($pass, $desc, /* todo */ false, $name, $expected); } -function isa_ok( $obj, $expected, $desc = '' ) { - $name = get_class( $obj ); - $pass = $name == $expected; - proclaim( $pass, $desc, /* todo */ false, $name, $expected ); -} +function todo_start($why = '') +{ + global $__Test; -function proclaim( - $cond, // bool - $desc = '', - $todo = false, - $got = null, - $expected = null, - $negate = false ) { + $__Test['todo'][] = $why; +} - global $Test; +function todo_end() +{ + global $__Test; - $Test['run'] += 1; + if (count($__Test['todo']) == 0) { + die("todo_end() called without a matching todo_start() call"); + } else { + array_pop($__Test['todo']); + } +} + +# +# The code below consists of private utility functions for the above functions +# - # TODO: force_todo +function _proclaim( + $cond, # bool + $desc = '', + $todo = false, + $got = null, + $expected = null, + $negate = false) { - # Everything after the first # is special, so escape user-supplied messages - $desc = str_replace( '#', '\\#', $desc ); - $desc = str_replace( "\n", '\\n', $desc ); + global $__Test; - $ok = $cond ? "ok" : "not ok"; - $directive = $todo === false ? '' : '# TODO aoeu'; + $__Test['run'] += 1; - printf( "%s %d %s%s\n", $ok, $Test['run'], $desc, $directive ); + # We're in a TODO block via todo_start()/todo_end(). TODO via specific + # functions is currently unimplemented and will probably stay that way + if (count($__Test['todo'])) { + $todo = true; + } - if ( ! $cond ) { - report_failure( $desc, $got, $expected, $negate, $todo ); - } + # Everything after the first # is special, so escape user-supplied messages + $desc = str_replace('#', '\\#', $desc); + $desc = str_replace("\n", '\\n', $desc); - return $cond; -} + $ok = $cond ? "ok" : "not ok"; + $directive = ''; -function report_failure( $desc, $got, $expected, $negate, $todo ) { - # Every public function in this file proclaim which then calls - # this function, so our culprit is the third item in the stack - $caller = debug_backtrace(); - $call = $caller['2']; - - diag( - sprintf( " Failed%stest '%s'\n in %s at line %d\n got: %s\n expected: %s", - $todo ? ' TODO ' : ' ', - $desc, - $call['file'], - $call['line'], - $got, - $expected - ) - ); + if ($todo) { + $todo_idx = count($__Test['todo']) - 1; + $directive .= ' # TODO ' . $__Test['todo'][$todo_idx]; + } + + printf("%s %d %s%s\n", $ok, $__Test['run'], $desc, $directive); + + # report a failure + if (!$cond) { + # Every public function in this file calls _proclaim so our culprit is + # the second item in the stack + $caller = debug_backtrace(); + $call = $caller['1']; + + diag( + sprintf(" Failed%stest '%s'\n in %s at line %d\n got: %s\n expected: %s", + $todo ? ' TODO ' : ' ', + $desc, + $call['file'], + $call['line'], + $got, + $expected + ) + ); + } + + return $cond; } -function test_ends () +function _test_ends() { - global $Test; + global $__Test; + + if (count($__Test['todo']) != 0) { + $todos = join("', '", $__Test['todo']); + die("Missing todo_end() for '$todos'"); + } - if ( $Test['planned'] === false ) { - printf( "1..%d\n", $Test['run'] ); - } + if (!$__Test['planned']) { + printf("1..%d\n", $__Test['run']); + } } -/* +# +# All of the below is for is_deeply() +# + +function _repl($obj, $deep = true) { + if (is_string($obj)) { + return "'" . $obj . "'"; + } else if (is_numeric($obj)) { + return $obj; + } else if (is_null($obj)) { + return 'null'; + } else if (is_bool($obj)) { + return $obj ? 'true' : 'false'; + } else if (is_array($obj)) { + return _repl_array($obj, $deep); + }else { + return gettype($obj); + } +} -=head1 TODO +function _diff($gpath, $got, $epath, $expected) { + return array( + 'gpath' => $gpath, + 'got' => $got, + 'epath' => $epath, + 'expected' => $expected + ); +} -=over +function _idx($obj, $path = '') { + return $path . '[' . _repl($obj) . ']'; +} -=item * Fully document this file +function _cmp_deeply($got, $exp, $path = '') { + if (is_array($exp)) { + + if (!is_array($got)) { + return _diff($path, _repl($got), $path, _repl($exp)); + } + + $gk = array_keys($got); + $ek = array_keys($exp); + $mc = max(count($gk), count($ek)); + + for ($el = 0; $el < $mc; $el++) { + # One array shorter than the other? + if ($el >= count($ek)) { + return _diff(_idx($gk[$el], $path), _repl($got[$gk[$el]]), + 'missing', 'nothing'); + } else if ($el >= count($gk)) { + return _diff('missing', 'nothing', + _idx($ek[$el], $path), _repl($exp[$ek[$el]])); + } + + # Keys differ? + if ($gk[$el] != $ek[$el]) { + return _diff(_idx($gk[$el], $path), _repl($got[$gk[$el]]), + _idx($ek[$el], $path), _repl($exp[$ek[$el]])); + } + + # Recurse + $rc = _cmp_deeply($got[$gk[$el]], $exp[$ek[$el]], _idx($gk[$el], $path)); + if (!is_null($rc)) { + return $rc; + } + } + } + else { + # Default to serialize hack + if (serialize($got) != serialize($exp)) { + return _diff($path, _repl($got), $path, _repl($exp)); + } + } + + return null; +} -=item * +function _plural($n, $singular, $plural = null) { + if (is_null($plural)) { + $plural = $singular . 's'; + } + return $n == 1 ? "$n $singular" : "$n $plural"; +} -Add TODO support, maybe via C -C 'fix this'))>. +function _repl_array($obj, $deep) { + if ($deep) { + $slice = array_slice($obj, 0, 3); # Increase from 3 to show more + $repl = array(); + $next = 0; + foreach ($slice as $idx => $el) { + $elrep = _repl($el, false); + if (is_numeric($idx) && $next == $idx) { + // Numeric index + $next++; + } else { + // Out of sequence or non-numeric + $elrep = _repl($idx, false) . ' => ' . $elrep; + } + $repl[] = $elrep; + } + $more = count($obj) - count($slice); + if ($more > 0) { + $repl[] = '... ' . _plural($more, 'more element') . ' ...'; + } + return 'array(' . join(', ', $repl) . ')'; + } + else { + return 'array(' . count($obj) . ')'; + } +} -=back +/* -=head1 SEE ALSO +=head1 NAME -=over +Test.php - TAP test framework for PHP with a L-like interface -=item L - The TAP protocol +=head1 SYNOPSIS -=item L + #!/usr/bin/env php + + +=head1 DESCRIPTION -=item Pugs's Test.pm +F is an implementation of Perl's L for PHP. Like +Test::More it produces language agnostic TAP output (see L) which +can then be gathered, formatted and summarized by a program that +understands TAP such as prove(1). + +=head1 HOWTO + +First place the F in the project root or somewhere else in +the include path where C and C will find it. + +Then make a place to put your tests in, it's customary to place TAP +tests in a directory named F under the root but they can be +anywhere you like. Make a test in this directory or one of its subdirs +and try running it with php(1): + + $ php t/pass.t + 1..1 + ok 1 This dummy test passed + +The TAP output consists of very simple output, of course reading +larger output is going to be harder which is where prove(1) comes +in. prove is a harness program that reads test output and produces +reports based on it: + + $ prove t/pass.t + t/pass....ok + All tests successful. + Files=1, Tests=1, 0 wallclock secs ( 0.03 cusr + 0.02 csys = 0.05 CPU) + +To run all the tests in the F directory recursively use C. This can be put in a F under a I target, for +example: + + test: Test.php + prove -r t + +For reference the example test file above looks like this, the shebang +on the first line is needed so that prove(1) and other test harness +programs know they're dealing with a PHP file. + + #!/usr/bin/env php + + +=head1 SEE ALSO -=back +L - The TAP protocol =head1 AUTHOR -Ævar Arnfjörð Bjarmason +Evar ArnfjErE Bjarmason and Andy Armstrong =head1 LICENSING -This program is free software; you can redistribute it and/or modify it -under the same terms as Perl itself. +The author or authors of this code dedicate any and all copyright +interest in this code to the public domain. We make this dedication +for the benefit of the public at large and to the detriment of our +heirs and successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights this +code under copyright law. =cut */ - +?>