Testing_DocTest
[ class tree: Testing_DocTest ] [ index: Testing_DocTest ] [ all elements ]

Source for file Default.php

Documentation is available at Default.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * This file is part of the PEAR Testing_DocTest package.
  7.  *
  8.  * PHP version 5
  9.  *
  10.  * LICENSE: This source file is subject to the MIT license that is available
  11.  * through the world-wide-web at the following URI:
  12.  * http://opensource.org/licenses/mit-license.php
  13.  *
  14.  * @category  Testing
  15.  * @package   Testing_DocTest
  16.  * @author    David JEAN LOUIS <izimobil@gmail.com>
  17.  * @copyright 2008 David JEAN LOUIS
  18.  * @license   http://opensource.org/licenses/mit-license.php MIT License
  19.  * @version   CVS: $Id$
  20.  * @link      http://pear.php.net/package/Testing_DocTest
  21.  * @since     File available since release 0.1.0
  22.  * @filesource
  23.  */
  24.  
  25. /**
  26.  * Resuired files
  27.  */
  28. require_once 'Testing/DocTest/RunnerInterface.php';
  29.  
  30. /**
  31.  * DocTest Runner default class.
  32.  *
  33.  * <code>
  34.  * require_once 'Testing/DocTest.php';
  35.  * require_once 'Testing/DocTest/TestCase.php';
  36.  *
  37.  * $test = new Testing_DocTest_TestCase();
  38.  * $test->code          = 'echo "Foobar!";';
  39.  * $test->expectedValue = '  foobar !';
  40.  *
  41.  * $r = new Testing_DocTest_Runner_Default();
  42.  * $r->run($test);
  43.  * var_dump($test->state === Testing_DocTest_TestCase::STATE_PASSED);
  44.  *
  45.  * $test->flags |= Testing_DocTest::FLAG_NORMALIZE_WHITESPACE;
  46.  * $r->run($test);
  47.  * var_dump($test->state === Testing_DocTest_TestCase::STATE_PASSED);
  48.  *
  49.  * $test->flags |= Testing_DocTest::FLAG_CASE_INSENSITIVE;
  50.  * $r->run($test);
  51.  * var_dump($test->state === Testing_DocTest_TestCase::STATE_PASSED);
  52.  *
  53.  * $test->expectedValue = '  f[...]bar !';
  54.  * $test->flags |= Testing_DocTest::FLAG_ELLIPSIS;
  55.  * $r->run($test);
  56.  * var_dump($test->state === Testing_DocTest_TestCase::STATE_PASSED);
  57.  *
  58.  * $test->flags |= Testing_DocTest::FLAG_SKIP;
  59.  * $r->run($test);
  60.  * var_dump($test->state === Testing_DocTest_TestCase::STATE_SKIPPED);
  61.  *
  62.  * // expects:
  63.  * // bool(false)
  64.  * // bool(false)
  65.  * // bool(true)
  66.  * // bool(true)
  67.  * // bool(true)
  68.  * </code>
  69.  *
  70.  * <code>
  71.  * // flags: ELLIPSIS
  72.  * require_once 'Testing/DocTest.php';
  73.  * require_once 'Testing/DocTest/TestCase.php';
  74.  *
  75.  * $test                = new Testing_DocTest_TestCase();
  76.  * $test->code          = 'echo nonExistantFunc();';
  77.  * $test->expectedValue = 'foo';
  78.  * $runner              = new Testing_DocTest_Runner_Default();
  79.  * $runner->run($test);
  80.  * var_dump($test->actualValue);
  81.  * // expects:
  82.  * // string([...]) "[...]Fatal error: Call to undefined function [...]"
  83.  *
  84.  * </code>
  85.  *
  86.  * @category  Testing
  87.  * @package   Testing_DocTest
  88.  * @author    David JEAN LOUIS <izimobil@gmail.com>
  89.  * @copyright 2008 David JEAN LOUIS
  90.  * @license   http://opensource.org/licenses/mit-license.php MIT License
  91.  * @version   Release: @package_version@
  92.  * @link      http://pear.php.net/package/Testing_DocTest
  93.  * @since     Class available since release 0.1.0
  94.  */
  95. class Testing_DocTest_Runner_Default implements Testing_DocTest_RunnerInterface
  96. {
  97.     // run() {{{
  98.  
  99.     /**
  100.      * Run the test provided by comparing the expected result with the actual
  101.      * result returned by the test code.
  102.      * Each test is run in its own php process.
  103.      *
  104.      * @param object $testCase Testing_DocTest_TestCase instance to run
  105.      *
  106.      * @access public
  107.      * @return void 
  108.      * @throws Testing_DocTest_Exception
  109.      */
  110.     public function run(Testing_DocTest_TestCase $testCase
  111.     {
  112.         $codetpl "<?php\n";
  113.         if ($testCase->file !== null{
  114.             $codetpl .= "require_once '{$testCase->file}';\n";
  115.         }
  116.         $codetpl .= "%s?>\n";
  117.         // skip condition
  118.         if (($skipCode $testCase->skipIfCode!== null{
  119.             $skipCode trim($skipCode);
  120.             $ret      $this->_exec(sprintf('<?php echo %s; ?>'$skipCode));
  121.             if ($ret['code'==0 || strlen($ret['output']> 1{
  122.                 throw new Testing_DocTest_Exception('skip-condition in test "'
  123.                     . $testCase->name . '" must be a boolean expression, '
  124.                     . 'got: ' $skipCode);
  125.             }
  126.             $skip $ret['code'=== 0 && trim($ret['output']=== '1';
  127.         else {
  128.             $skip = false;
  129.         }
  130.         if ($skip || $testCase->hasFlag(Testing_DocTest::FLAG_SKIP)) {
  131.             $testCase->state = Testing_DocTest_TestCase::STATE_SKIPPED;
  132.             return;
  133.         }
  134.         // handle ini settings
  135.         if (!empty($testCase->iniSettings)) {
  136.             $options $this->_formatIniSettings($testCase->iniSettings);
  137.         else {
  138.             $options = null;
  139.         }
  140.         $ret $this->_exec(sprintf($codetpl$testCase->code)$options);
  141.         if ($ret['code'!== 0{
  142.             $testCase->actualValue = trim($ret['output']);
  143.         else {
  144.             $testCase->actualValue = $ret['output'];
  145.         }
  146.         if ($this->_compare($testCase)) {
  147.             $testCase->state = Testing_DocTest_TestCase::STATE_PASSED;
  148.         else {
  149.             if ($ret['code'!== 0{
  150.                 $testCase->state = Testing_DocTest_TestCase::STATE_ERROR;
  151.             else {
  152.                 $testCase->state = Testing_DocTest_TestCase::STATE_FAILED;
  153.             }
  154.         }
  155.         // handle clean line
  156.         if (($cleanCode $testCase->cleanCode!== null{
  157.             $cleanCode trim($cleanCode);
  158.             $ret       $this->_exec(sprintf('<?php %s; ?>'$cleanCode));
  159.             if ($ret['code'==0{
  160.                 throw new Testing_DocTest_Exception('cleaning code failed in '
  161.                     . 'test "' $testCase->name . '": ' $cleanCode);
  162.             }
  163.         }
  164.         
  165.     }
  166.  
  167.     // }}}
  168.     // _formatIniSettings() {{{
  169.  
  170.     /**
  171.      * Given an array of directive=>value this method return the commandline
  172.      * string to pass to the php interpreter.
  173.      *
  174.      * @param array $iniSettings an array of ini settings
  175.      *
  176.      * @access private
  177.      * @return string 
  178.      */
  179.     private function _formatIniSettings(array $iniSettings)
  180.     {
  181.         $ret '';
  182.         foreach ($iniSettings as $k=>$v{
  183.             if (substr(PHP_OS03== 'WIN'{
  184.                 // XXX check why windows does not like escapeshellarg
  185.                 $ret .= ' -d' $k '=' $v;
  186.             else {
  187.                 $ret .= ' -d' escapeshellarg($k '=' addslashes($v));
  188.             }
  189.         }
  190.         return $ret;
  191.     }
  192.  
  193.     // }}}
  194.     // _compare() {{{
  195.  
  196.     /**
  197.      * Compare the expected result with the actual result.
  198.      *
  199.      * @param object $test a Testing_DocTest_TestCase instance
  200.      *
  201.      * @access private
  202.      * @return boolean 
  203.      */
  204.     private function _compare(Testing_DocTest_TestCase $test
  205.     {
  206.         $exp trim($test->expectedValue"\n");
  207.         $act trim($test->actualValue"\n");
  208.         if ($test->hasFlag(Testing_DocTest::FLAG_CASE_INSENSITIVE)) {
  209.             $exp strtolower($exp);
  210.             $act strtolower($act);
  211.         }
  212.         if ($test->hasFlag(Testing_DocTest::FLAG_NORMALIZE_WHITESPACE)) {
  213.             $exp preg_replace('/\s/'''$exp);
  214.             $act preg_replace('/\s/'''$act);
  215.         }
  216.         if ($test->hasFlag(Testing_DocTest::FLAG_ELLIPSIS)) {
  217.             $exp str_replace(array("\n""\r\n"'[...]'),
  218.                 array('''''__ellipsis__')$exp);
  219.             $act str_replace(array("\n""\r\n"'[...]')''$act);
  220.             $rx  preg_quote($exp'/');
  221.             $rx  str_replace('__ellipsis__''.*?'$rx);
  222.             return (bool)preg_match('/^'.$rx.'$/'$act);
  223.         }
  224.         return $exp == $act;
  225.     }
  226.  
  227.     // }}}
  228.     // _exec() {{{
  229.  
  230.     /**
  231.      * Run given php code in a subprocess and return an array as follows:
  232.      * 
  233.      * <code>
  234.      * array(
  235.      *     'code'   => true|false, // return code of the process
  236.      *     'output' => 'string'    // output of the process (stdout or stderr)
  237.      * )
  238.      * </code>
  239.      *
  240.      * @param string $code    the php code to execute
  241.      * @param string $options additionnal options to pass to php
  242.      *
  243.      * @access private
  244.      * @return array 
  245.      * @throws Testing_DocTest_Exception if the process cannot be opened
  246.      */
  247.     private function _exec($code$options=null
  248.     {
  249.         $descriptors = array(
  250.             0 => array('pipe''r')// stdin
  251.             1 => array('pipe''w')// stdout
  252.             2 => array('pipe''w')  // stderr
  253.         );
  254.         // path to php bin
  255.         $php substr('@php_bin@'01== '@' 'php' '@php_bin@';
  256.         if (substr(PHP_OS03== 'WIN'{
  257.             $php '"' $php '"';
  258.         }
  259.         if ($options !== null{
  260.             $php .= ' ' $options;
  261.         }
  262.         // try to open proc and raise an exception if it fails
  263.         $process proc_open($php$descriptors$pipes);
  264.         if (!is_resource($process)) {
  265.             throw new Testing_DocTest_Exception("Unable to open process: $php");
  266.         }
  267.         // write code to stdin
  268.         fwrite($pipes[0]$code);
  269.         fflush($pipes[0]);
  270.         fclose($pipes[0]);
  271.         // will contain script output
  272.         $output '';
  273.         while (true{
  274.             // hide errors from interrupted syscalls
  275.             $r $pipes;
  276.             $e = null;
  277.             $w = null;
  278.             $n @stream_select($r$w$e60);
  279.             if ($n <= 0{
  280.                 // timed out
  281.                 $output .= "\n ** ERROR: process timed out **\n";
  282.                 return array(proc_terminate($process)$output);
  283.             
  284.             if (false === ($data fgets($pipes[1]))) {
  285.                 // nothing on stdout, try stderr
  286.                 //if (false === ($data = fgets($pipes[2]))) {
  287.                     break;
  288.                 //}
  289.             }
  290.             $output .= $data;
  291.         }
  292.         // close stdout and stderr
  293.         fflush($pipes[1]);
  294.         fclose($pipes[1]);
  295.         fflush($pipes[2]);
  296.         fclose($pipes[2]);
  297.         // get return code by closing the process
  298.         return array('code' => proc_close($process)'output' => $output);
  299.     }
  300.  
  301.     // }}}
  302. }

Documentation generated on Mon, 11 Mar 2019 15:18:32 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.