Guest User

Mike Lively

a guest
Apr 6th, 2010
276
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 17.64 KB | None | 0 0
  1. <?php
  2. /**
  3.  * PHP TAP Test Harness 1_0_0_BETA
  4.  * Aggragates any stand alone testing library capable of outputting TAP
  5.  * compatible results.
  6.  *
  7.  * To use, place in the same directory as the tests you would like to
  8.  * run (any subdirectories will also be run.)
  9.  *
  10.  * There are two constants that can be used to control the testing
  11.  * environment:
  12.  *
  13.  * TAP_PHP_CLI - The path to your php-cli executable
  14.  * TAP_PHP_CLI_ARGS - Any command line arguments to pass to php-cli.
  15.  *     (-dinclude_path will be of particular use.)
  16.  *
  17.  * If there are files in the directory structure that you would like to
  18.  * ignore, you can add them to the $_EXCLUDE_FILES array.
  19.  *
  20.  * This software is licensed under a variant of the BSD license.
  21.  * For more information see: http://www.digitalsandwich.com/license.bsd
  22.  *
  23.  * Copyright (c) 2006, Mike Lively <http://digitalsandwich.com>
  24.  * All Rights Reserved
  25.  *//**
  26.  * Configuration Options
  27.  */
  28. define('TAP_PHP_CLI', '/usr/bin/php');
  29. define('TAP_PHP_CLI_ARGS', '-dinclude_path=.');
  30.  
  31. $_EXCLUDE_FILES = array('test-more.php', 'test-harness.php');
  32.  
  33. /// END OF CONFIGURATION \\\
  34.  
  35. define('TAP_TEST_DIRECTIVE_SKIP', 1);
  36. define('TAP_TEST_DIRECTIVE_TODO', 2);
  37.  
  38. define('TAP_TEST_STATUS_OK', 1);
  39. define('TAP_TEST_STATUS_NOTOK', 2);
  40.  
  41. define('TAP_TEST_FILE_STATUS_PASS', 1);
  42. define('TAP_TEST_FILE_STATUS_FAIL', 2);
  43. define('TAP_TEST_FILE_STATUS_SKIP', 3);
  44.  
  45. //verbosity bitmasks
  46. define('TAP_VERBOSITY_SILENT', 0);
  47. define('TAP_VERBOSITY_DEFAULT', 0x01);
  48. define('TAP_VERBOSITY_DETAIL', 0x02);
  49. define('TAP_VERBOSITY_DEBUG', 0x04);
  50.  
  51. $_BASE_DIR = dirname(__FILE__);
  52.  
  53. parse_command_arguments();
  54.  
  55. $session = init_test_session_stats();
  56.  
  57. run_test_dir($_BASE_DIR, $session);
  58.  
  59. //Print out Skip summary.
  60. if (count($session['skip_test_files'])) {
  61.     print_in_mode("Skipped Tests                  Total Skip Skipped List of Skipped\n");
  62.     print_in_mode("----------------------------------------------------------------------\n");
  63.     foreach ($session['skip_test_files'] as $test_file) {
  64.         $file = relativize_filename($test_file['file']);
  65.         $total = $test_file['plan'];
  66.         $skip = $test_file['skip'];
  67.         $skipped = $total?round($skipped / $total * 100, 2):0;
  68.         print_in_mode(str_pad($file, 30, ' ') . ' ');
  69.         print_in_mode(str_pad($total, 5, ' ', STR_PAD_LEFT) . ' ');
  70.         print_in_mode(str_pad($skip, 4, ' ', STR_PAD_LEFT) . ' ');
  71.         print_in_mode(str_pad('%' . $skipped, 7, ' ', STR_PAD_LEFT));
  72.        
  73.         if ($test_file['status'] == TAP_TEST_FILE_STATUS_SKIP) {
  74.             print_in_mode(' ' . $test_file['reason'] . "\n");
  75.         } else {
  76.             $first = true;
  77.             foreach ($test_file['skip_tests'] as $test) {
  78.                 if ($first) {
  79.                     $first = false;
  80.                     print_in_mode(' ' . "{$test['name']}: {$test['reason']}\n");
  81.                 } else {
  82.                     print_in_mode(str_repeat(' ', 49));
  83.                     print_in_mode(' ' . "{$test['name']}: {$test['reason']}\n");
  84.                 }
  85.             }
  86.         }
  87.         print_in_mode("\n");
  88.     }
  89. }
  90.  
  91. //Print out Bonus summary.
  92. if (count($session['bonus_test_files'])) {
  93.     print_in_mode("Bonus Tests                    Total Bonus Bonuses List of Bonuses\n");
  94.     print_in_mode("----------------------------------------------------------------------\n");
  95.     foreach ($session['bonus_test_files'] as $test_file) {
  96.         $file = relativize_filename($test_file['file']);
  97.         $total = $test_file['plan'];
  98.         $bonus = $test_file['bonus'];
  99.         $bonuses = $total?round($bonus / $total * 100, 2):0;
  100.         print_in_mode(str_pad($file, 30, ' '));
  101.         print_in_mode(str_pad($total, 5, ' ', STR_PAD_LEFT));
  102.         print_in_mode(str_pad($bonus, 5, ' ', STR_PAD_LEFT));
  103.         print_in_mode(str_pad('%' . $bonuses, 7, ' ', STR_PAD_LEFT));
  104.        
  105.         $first = true;
  106.         foreach ($test_file['bonus_tests'] as $test) {
  107.             if ($first) {
  108.                 $first = false;
  109.                 print_in_mode(' ' . "{$test['name']}: {$test['reason']}\n");
  110.             } else {
  111.                 print_in_mode(str_repeat(' ', 50));
  112.                 print_in_mode(' ' . "{$test['name']}: {$test['reason']}\n");
  113.             }
  114.         }
  115.            
  116.         print_in_mode("\n");
  117.     }
  118. }
  119.  
  120. //Print out Fail summary.
  121. if (count($session['fail_test_files'])) {
  122.     print_in_mode("Failed Tests                   Total Fail Failed List of Failed\n");
  123.     print_in_mode("----------------------------------------------------------------------\n");
  124.     foreach ($session['fail_test_files'] as $test_file) {
  125.         $file = relativize_filename($test_file['file']);
  126.         $total = $test_file['plan'];
  127.         $fail = $test_file['plan'] - $test_file['ok'];
  128.         $failed = $total?round($fail / $total * 100, 2):0;
  129.         print_in_mode(str_pad($file, 30, ' '));
  130.         print_in_mode(str_pad($total, 5, ' ', STR_PAD_LEFT));
  131.         print_in_mode(str_pad($fail, 4, ' ', STR_PAD_LEFT));
  132.         print_in_mode(str_pad('%' . $failed, 6, ' ', STR_PAD_LEFT));
  133.        
  134.         if ($test_file['status'] == TAP_TEST_FILE_STATUS_FAIL && $test_file['reason'] != '') {
  135.             print_in_mode(' ' . $test_file['reason'] . "\n");
  136.         } else {
  137.             $first = true;
  138.             foreach ($test_file['fail_tests'] as $test) {
  139.                 if ($first) {
  140.                     $first = false;
  141.                     print_in_mode(' ' . "{$test['name']}\n");
  142.                 } else {
  143.                     print_in_mode(str_repeat(' ', 48));
  144.                     print_in_mode(' ' . "{$test['name']}\n");
  145.                 }
  146.             }
  147.         }
  148.         print_in_mode("\n");
  149.     }
  150. }
  151.  
  152. //Print out total Summary
  153. $all_files = $session['t_count'];
  154. $failed_files = $session['t_fail'];
  155. $skipped_files = $session['t_skip'];
  156. $passed_files = $session['t_pass'];
  157.  
  158. $all_tests = $session['count'];
  159. $passed_tests = $session['ok'];
  160. $failed_tests = $session['count'] - $session['ok'];
  161. $skipped_tests = $session['skip'];
  162. $bonus_tests = $session['bonus'];
  163.  
  164. if ($bonus_tests) {
  165.     print_in_mode("$bonus_tests TODO tests passed. You may want to consider removing their TODO status.\n");
  166. }
  167.  
  168. if ($skipped_files || $skipped_tests) {
  169.     if ($skipped_files) print_in_mode("$skipped_files test scripts skipped.  ");
  170.     if ($skipped_tests) print_in_mode("$skipped_tests tests skipped.  ");
  171.     print_in_mode("\n");
  172. }
  173.  
  174. if ($failed_files) {
  175.     print_in_mode("Failed $failed_files/$all_files test scripts, %" . round(($all_files?$passed_files/$all_files:0) * 100, 2) . " okay. ");
  176. } else {
  177.     print_in_mode("All test scripts passed!  ");
  178. }
  179.  
  180. if ($failed_tests) {
  181.     print_in_mode("Failed $failed_tests/$all_tests subtests, %" . round(($all_files?$passed_tests/$all_tests:0) * 100, 2) . " okay. ");
  182. } else {
  183.     print_in_mode("All subtests passed!  ");
  184. }
  185.  
  186. print_in_mode("\n");
  187. if (!$failed_files && !$failed_tests) {
  188.     return 1;
  189. } else {
  190.     return 0;
  191. }
  192.  
  193. /// LIBRARY FUNCTIONS \\\
  194.  
  195. function parse_command_arguments() {
  196.     $argv = $_SERVER['argv'];
  197.     $GLOBALS['current_verbosity'] = TAP_VERBOSITY_DEFAULT;
  198.    
  199.     while (count($argv)) {
  200.         $argument = array_shift($argv);
  201.        
  202.         switch ($argument) {
  203.             case '--silent':
  204.             case '-s':
  205.                 $GLOBALS['current_verbosity'] = TAP_VERBOSITY_SILENT;
  206.             break;
  207.             case '--detail':
  208.             case '-v':
  209.                 $GLOBALS['current_verbosity'] |= TAP_VERBOSITY_DETAIL;
  210.             break;
  211.             case '--debug':
  212.                 $GLOBALS['current_verbosity'] |= TAP_VERBOSITY_DEBUG;
  213.             break;
  214.         }
  215.     }
  216. }
  217.  
  218. function print_in_mode($line, $mode = TAP_VERBOSITY_DEFAULT) {
  219.     if ($GLOBALS['current_verbosity'] & $mode) {
  220.         echo "$line";
  221.     }
  222. }
  223.  
  224. function run_test_dir($dirname, &$session) {
  225.     $dirname = rtrim($dirname, '/') . '/';
  226.     if (!is_dir($dirname)) {
  227.         print_in_mode("$dirname: Is not a valid directory\n", TAP_VERBOSITY_DEFAULT);
  228.         return;
  229.     }
  230.    
  231.     if (!$dh = opendir($dirname)) {
  232.         print_in_mode("$dirname: Could not open directory\n", TAP_VERBOSITY_DEFAULT);
  233.         return;
  234.     }
  235.    
  236.     $fileArray = array();
  237.     $dirArray = array();
  238.     while (($file = readdir($dh)) !== false) {
  239.         if (!in_array($file, array_merge($GLOBALS['_EXCLUDE_FILES'], array('.', '..')))) {
  240.             if (is_file($dirname . $file)) {
  241.                 $fileArray[] = $file;
  242.             } elseif (is_dir($file)) {
  243.                 $dirArray[] = $file;
  244.             }
  245.         }
  246.     }
  247.     closedir($dh);
  248.  
  249.     sort($dirArray);
  250.     sort($fileArray);
  251.    
  252.     foreach ($dirArray as $dir) {
  253.         run_test_dir($dirname . $dir, $session);
  254.     }
  255.  
  256.     foreach ($fileArray as $file) {
  257.         ob_start();
  258.         $test_file = run_test_file($dirname . $file);
  259.         $test_buffer = ob_get_clean();
  260.         switch ($test_file['status']) {
  261.             case TAP_TEST_FILE_STATUS_PASS:
  262.                 print_in_mode(str_pad(relativize_filename($test_file['file']), 50, '.') . "ok\n");
  263.             break;
  264.             case TAP_TEST_FILE_STATUS_FAIL:
  265.                 print_in_mode(str_pad(relativize_filename($test_file['file']), 50, '.') . "not ok\n");
  266.             break;
  267.             case TAP_TEST_FILE_STATUS_SKIP:
  268.                 print_in_mode(str_pad(relativize_filename($test_file['file']), 50, '.') . "skipped\n");
  269.             break;
  270.         }
  271.         echo $test_buffer;
  272.         add_test_file_2_session($test_file, $session);
  273.     }
  274. }
  275.  
  276. function run_test_file($filename) {
  277.     $test_file = init_test_file_stats();
  278.     $test_file['file'] = $filename;
  279.    
  280.     $time_start = microtime(true);
  281.     // Set up proc
  282.     if (!is_executable(TAP_PHP_CLI)) {
  283.         die("Couldn't find PHP exectuble. Please set TAP_PHP_CLI\n");
  284.     }
  285.     $command = TAP_PHP_CLI . ' ' . TAP_PHP_CLI_ARGS . ' ' . escapeshellarg($test_file['file']) . ' 2>&1';
  286.     $descriptors = array(
  287.         1 => array('pipe', 'w'),
  288.     );
  289.     $pipes = array();
  290.     $cwd = dirname($test_file['file']);
  291.    
  292.     $process = proc_open($command, $descriptors, $pipes, $cwd);
  293.    
  294.     if (is_resource($process)) {
  295.         process_test_results($pipes[1], $test_file);
  296.         fclose($pipes[1]);
  297.         proc_close($process);
  298.     } else {
  299.         $test_file['status'] = TAP_TEST_FILE_STATUS_FAIL;
  300.         $test_file['reason'] = "Unable to execute the test file.";
  301.     }
  302.     $test_file['time'] = microtime(true) - $time_start;
  303.    
  304.     return $test_file;
  305. }
  306.  
  307. function process_test_results($pipes, &$test_file) {
  308.     //set up regexs
  309.     $regex_plan = '/^1\.\.([0-9]+)(\s+#\s*(.*?)\s*)?$/';
  310.     $regex_testline = '/^(ok|not ok)(\s+([0-9]+))?(\s+([^#]+))?(\s+#\s*(SKIP|TODO)\s*(.*?))?\s*$/i';
  311.     $regex_diagnostics = '/^#\s*(.*?)\s*$/';
  312.     $regex_bail = '/^bail out!\s*(.*?)\s*$/i';
  313.    
  314.     $is_first_line = true;
  315.     $is_plan_in_middle = false;
  316.     $is_plan_set = false;
  317.     $current_test = 0;
  318.    
  319.     //expect pass
  320.     $test_file['status'] = TAP_TEST_FILE_STATUS_PASS;
  321.     while (!feof($pipes)) {
  322.         $line = trim(fgets($pipes));
  323.        
  324.         print_in_mode("$line\n", TAP_VERBOSITY_DETAIL);
  325.         if ($line == '') {
  326.             //do nothing on empty lines
  327.             continue;
  328.         }
  329.        
  330.         if ($is_plan_in_middle) {
  331.             print_in_mode("# Error: Plan found in middle of file. Please move to the begining or end.\n", TAP_VERBOSITY_DEFAULT);
  332.             $is_plan_in_middle = false;
  333.         }
  334.        
  335.         $matches = array();
  336.         if (preg_match($regex_plan, $line, $matches)) {
  337.             $plan = $matches[1];
  338.             $skip = $matches[3];
  339.            
  340.             print_in_mode("Plan Line ($plan|$skip)\n", TAP_VERBOSITY_DEBUG);
  341.            
  342.             if ($is_plan_set) {
  343.                 print_in_mode("# Error: Multiple Plans Specified Using First Plan\n", TAP_VERBOSITY_DEFAULT);
  344.             } else {
  345.                 $is_plan_set = true;
  346.                 $test_file['plan'] = $plan;
  347.                 if (!$is_first_line) {
  348.                     $is_plan_in_middle = true;
  349.                 }
  350.                
  351.                 if ($skip) {
  352.                     $test_file['plan'] = 0;
  353.                     $test_file['status'] = TAP_TEST_FILE_STATUS_SKIP;
  354.                     $test_file['reason'] = $skip;
  355.                     break;
  356.                 }
  357.             }
  358.            
  359.         } elseif (preg_match($regex_testline, $line, $matches)) {
  360.             $status = $matches[1];
  361.             $number = $matches[3];
  362.             $name = $matches[5];
  363.             $directive = $matches[7];
  364.             $reason = $matches[8];
  365.            
  366.             print_in_mode("Test Line ($status|$number|$name|$directive|$reason)\n", TAP_VERBOSITY_DEBUG);
  367.            
  368.             $current_test++;
  369.             $test = init_test_stats();
  370.            
  371.             switch ($status) {
  372.                 case 'ok':
  373.                     $test['status'] = TAP_TEST_STATUS_OK;
  374.                 break;
  375.                 case 'not ok':
  376.                     $test['status'] = TAP_TEST_STATUS_NOTOK;
  377.                 break;
  378.             }
  379.            
  380.             switch($directive) {
  381.                 case 'TODO':
  382.                     $test['directive'] = TAP_TEST_DIRECTIVE_TODO;
  383.                 break;
  384.                 case 'SKIP':
  385.                     $test['directive'] = TAP_TEST_DIRECTIVE_SKIP;
  386.                 break;
  387.             }
  388.            
  389.             $test['number'] = $number?$number:$current_test;
  390.             $test['name'] = $name;
  391.             $test['reason'] = $reason;
  392.            
  393.             add_test_2_test_file($test, $test_file);
  394.         } elseif (preg_match($regex_diagnostics, $line, $matches)) {
  395.             print_in_mode("Diagnostic Line\n", TAP_VERBOSITY_DEBUG);
  396.             //do nothing
  397.         } elseif (preg_match($regex_bail, $line, $matches)) {
  398.             $reason = $matches[1];
  399.             print_in_mode("Bail Out Line ($reason)\n", TAP_VERBOSITY_DEBUG);
  400.            
  401.             $test_file['status'] = TAP_TEST_FILE_STATUS_FAIL;
  402.             $test_file['reason'] = $reason;
  403.             break;
  404.         }
  405.         else
  406.         {
  407.             print_in_mode("# Error: Unrecognized output ($line)\n", TAP_VERBOSITY_DEFAULT);
  408.             $test_file['status'] = TAP_TEST_FILE_STATUS_FAIL;
  409.         }
  410.        
  411.     }
  412.    
  413.     //set plan if one does not exist
  414.     if ($test_file['plan'] == 0) {
  415.         $test_file['plan'] = $test_file['count'];
  416.     } elseif ($test_file['count'] > $test_file['plan']) { //TODO: not sure if this is right.
  417.         $test_file['plan'] = $test_file['count'];
  418.     }
  419. }
  420.  
  421. function init_test_stats() {
  422.     return array(
  423.         'status' => 0,
  424.         'number' => 0,
  425.         'name' => '',
  426.         'directive' => 0,
  427.         'reason' => '',
  428.     );
  429. }
  430.  
  431. function init_test_file_stats() {
  432.     return array(
  433.         'file' => '',
  434.         'count' => 0,
  435.         'ok' => 0,
  436.         'skip' => 0,
  437.         'bonus' => 0,
  438.         'plan' => 0,
  439.         'status' => 0,
  440.         'reason' => '',
  441.         'time' => 0,
  442.         'all_tests' => array(),
  443.         'fail_tests' => array(),
  444.         'skip_tests' => array(),
  445.         'bonus_tests' => array(),
  446.     );
  447. }
  448.  
  449. function init_test_session_stats() {
  450.     return array(
  451.         'count' => 0,
  452.         'ok' => 0,
  453.         'skip' => 0,
  454.         'bonus' => 0,
  455.         't_pass' => 0,
  456.         't_skip' => 0,
  457.         't_fail' => 0,
  458.         't_count' => 0,
  459.         'time' => 0,
  460.         'all_test_files' => array(),
  461.         'fail_test_files' => array(),
  462.         'skip_test_files' => array(),
  463.         'bonus_test_files' => array(),
  464.     );
  465. }
  466.  
  467. function add_test_2_test_file($test, &$test_file) {
  468.     $test_file['count']++;
  469.     $test_file['all_tests'][] = $test;
  470.    
  471.     if ($test['status'] == TAP_TEST_STATUS_OK ||
  472.       $test['directive'] == TAP_TEST_DIRECTIVE_SKIP ||
  473.       $test['directive'] == TAP_TEST_DIRECTIVE_TODO)    {
  474.           $test_file['ok']++;
  475.     } else {
  476.         $test_file['fail_tests'][] = $test;
  477.         $test_file['status'] = TAP_TEST_FILE_STATUS_FAIL;
  478.     }
  479.    
  480.     if ($test['directive'] == TAP_TEST_DIRECTIVE_SKIP) {
  481.         $test_file['skip']++;
  482.         $test_file['skip_tests'][] = $test;
  483.     }
  484.    
  485.     if ($test['directive'] == TAP_TEST_DIRECTIVE_TODO &&
  486.       $test['status'] == TAP_TEST_STATUS_OK) {
  487.           $test_file['bonus']++;
  488.         $test_file['bonus_tests'][] = $test;
  489.     }
  490. }
  491.  
  492. function add_test_file_2_session($test_file, &$session) {
  493.     $session['count'] += $test_file['plan'];
  494.     $session['ok'] += $test_file['ok'];
  495.     $session['skip'] += $test_file['skip'];
  496.     $session['bonus'] += $test_file['bonus'];
  497.     $session['time'] += $test_file['time'];
  498.     $session['all_test_files'][] = $test_file;
  499.     $session['t_count']++;
  500.    
  501.     switch ($test_file['status']) {
  502.         case TAP_TEST_FILE_STATUS_PASS:
  503.             $session['t_pass']++;
  504.         break;
  505.         case TAP_TEST_FILE_STATUS_FAIL:
  506.             $session['t_fail']++;
  507.         break;
  508.         case TAP_TEST_FILE_STATUS_SKIP:
  509.             $session['t_pass']++;
  510.             $session['t_skip']++;
  511.         break;
  512.     }
  513.    
  514.     if (($test_file['plan'] > $test_file['ok']) || ($test_file['status'] == TAP_TEST_FILE_STATUS_FAIL)) {
  515.         $session['fail_test_files'][] = $test_file;
  516.     }
  517.    
  518.     if (($test_file['skip'] > 0) || ($test_file['status'] == TAP_TEST_FILE_STATUS_SKIP)) {
  519.         $session['skip_test_files'][] = $test_file;
  520.     }
  521.    
  522.     if ($test_file['bonus'] > 0) {
  523.         $session['bonus_test_files'][] = $test_file;
  524.     }
  525. }
  526.  
  527. function relativize_filename($filename) {
  528.     if (strpos($filename, $GLOBALS['_BASE_DIR']) === 0) {
  529.         return trim(substr($filename, strlen($GLOBALS['_BASE_DIR'])), '/');
  530.     }
  531.     return $filename;
  532. }
  533. ?>
Add Comment
Please, Sign In to add comment