All messages files rebuilt and unknown messages removed
[lhc/web/wiklou.git] / Test.php
index d6384d8..d6a2cf9 100644 (file)
--- a/Test.php
+++ b/Test.php
 <?php
+# See the end of this file for documentation
 
-/*
-
-=head1 NAME
-
-Test.php - L<Test::More> 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<Test.php> is an implementation of Perl's L<Test::More> and Pugs's B<Test> for
-PHP. Like those two modules it produces TAP output (see L<TAP>) which
-can then be gathered, formatted and summarized by a program that
-understands TAP such as L<prove(1)>.
+    # 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<ok(0, "foo # TODO fix this")>
-C<ok(1, "foo", array( 'todo' => '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<Test::More>-like interface
 
-=item L<TAP> - The TAP protocol
+=head1 SYNOPSIS
 
-=item L<Test::More> 
+    #!/usr/bin/env php
+    <?php  
+    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"
+  
+    # 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 regex 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);
+
+    # TODO tests, these are expected to fail but won't fail the test run,
+    # unexpected success will be reported
+    todo_start("integer arithmetic still working");
+    ok(1 + 2 == 3);
+    {
+        # TODOs can be nested
+        todo_start("string comparison still working")
+        is("foo", "bar");
+        todo_end();
+    }
+    todo_end();
+    ?>
+  
+=head1 DESCRIPTION
 
-=item Pugs's Test.pm
+F<Test.php> is an implementation of Perl's L<Test::More> for PHP. Like
+Test::More it produces language agnostic TAP output (see L<TAP>) which
+can then be gathered, formatted and summarized by a program that
+understands TAP such as prove(1).
+
+=head1 HOWTO
+
+First place the F<Test.php> in the project root or somewhere else in
+the include path where C<require> and C<include> will find it.
+
+Then make a place to put your tests in, it's customary to place TAP
+tests in a directory named F<t> 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<t> directory recursively use C<prove -r
+t>. This can be put in a F<Makefile> under a I<test> 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
+    <?php
+    
+    require 'Test.php';
+    
+    plan(1);
+    pass('This dummy test passed');
+    ?>
+    
+=head1 SEE ALSO
 
-=back
+L<TAP> - The TAP protocol
 
 =head1 AUTHOR
 
-Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org> and Andy Armstrong <andy@hexten.net>
 
 =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
 
 */
 
-
+?>