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.         if ($testCase->parsingError{
  113.             $testCase->state = Testing_DocTest_TestCase::STATE_ERROR;
  114.             return;
  115.         }
  116.         $codetpl "<?php\n%s\n";
  117.         if ($testCase->file !== null{
  118.             $codetpl .= "require_once '{$testCase->file}';\n";
  119.         }
  120.         $codetpl .= "%s\n?>\n";
  121.  
  122.         $tmplfile="";
  123.         if ($testCase->tmplCode{
  124.             $tmplfile=$testCase->tmplCode;
  125.         elseif (isset($testCase->_shellOptions['template_code']
  126.         && $testCase->_shellOptions['template_code']
  127.         {
  128.             $tmplfile=$testCase->_shellOptions['template_code'];
  129.         }
  130.  
  131.         if ($tmplfile{
  132.             $codetpl=file_get_contents($tmplfile);
  133.             if (empty($codetpl)) {
  134.                 throw new Testing_DocTest_Exception("Invalid template: $tmplfile");
  135.             }
  136.         }
  137.  
  138.         // skip condition
  139.         if (($skipCode $testCase->skipIfCode!== null{
  140.             $skipCode trim($skipCode);
  141.             $ret      $this->_exec(sprintf('<?php echo %s; ?>'$skipCode));
  142.             if ($ret['code'==0 || strlen($ret['output']> 1{
  143.                 throw new Testing_DocTest_Exception(
  144.                     'skip-condition in test "'$testCase->name . 
  145.                     '" must be a boolean expression, ''got: ' $skipCode
  146.                 );
  147.             }
  148.             $skip $ret['code'=== 0 && trim($ret['output']=== '1';
  149.         else {
  150.             $skip = false;
  151.         }
  152.         if ($skip || $testCase->hasFlag(Testing_DocTest::FLAG_SKIP)) {
  153.             $testCase->state = Testing_DocTest_TestCase::STATE_SKIPPED;
  154.             return;
  155.         }
  156.         // handle ini settings
  157.         if (!empty($testCase->iniSettings)) {
  158.             $options $this->_formatIniSettings($testCase->iniSettings);
  159.         else {
  160.             $options = null;
  161.         }
  162.         
  163.         $ret $this->_exec(
  164.             sprintf($codetpl$testCase->setupCode$testCase->code),
  165.             $options$testCase
  166.         );
  167.         
  168.         if ($ret['code'!== 0{
  169.             $testCase->actualValue = trim($ret['output']);
  170.         else {
  171.             $testCase->actualValue = $ret['output'];
  172.         }
  173.         if ($this->_compare($testCase)) {
  174.             $testCase->state = Testing_DocTest_TestCase::STATE_PASSED;
  175.         else {
  176.             if ($ret['code'!== 0{
  177.                 $testCase->state = Testing_DocTest_TestCase::STATE_ERROR;
  178.             else {
  179.                 $testCase->state = Testing_DocTest_TestCase::STATE_FAILED;
  180.             }
  181.         }
  182.         // handle clean line
  183.         if (($cleanCode $testCase->cleanCode!== null{
  184.             $cleanCode trim($cleanCode);
  185.             $ret       $this->_exec(sprintf('<?php %s; ?>'$cleanCode));
  186.             if ($ret['code'==0{
  187.                 throw new Testing_DocTest_Exception(
  188.                     'cleaning code failed in ' 'test "' .
  189.                     $testCase->name . '": ' $cleanCode
  190.                 );
  191.             }
  192.         }
  193.  
  194.     }
  195.  
  196.     // }}}
  197.     // _formatIniSettings() {{{
  198.  
  199.     /**
  200.      * Given an array of directive=>value this method return the commandline
  201.      * string to pass to the php interpreter.
  202.      *
  203.      * @param array $iniSettings an array of ini settings
  204.      *
  205.      * @access private
  206.      * @return string 
  207.      */
  208.     private function _formatIniSettings(array $iniSettings)
  209.     {
  210.         $ret '';
  211.         foreach ($iniSettings as $k=>$v{
  212.             if (!$v{
  213.                 continue;
  214.             }
  215.             
  216.             if (substr(PHP_OS03== 'WIN'{
  217.                 // XXX check why windows does not like escapeshellarg
  218.                 $ret .= ' -d' $k '=' $v;
  219.             else {
  220.                 $ret .= ' -d' escapeshellarg($k '=' addslashes($v));
  221.             }
  222.         }
  223.         return $ret;
  224.     }
  225.  
  226.     // }}}
  227.     // _compare() {{{
  228.  
  229.     /**
  230.      * Compare the expected result with the actual result.
  231.      *
  232.      * @param object $test a Testing_DocTest_TestCase instance
  233.      *
  234.      * @access private
  235.      * @return boolean 
  236.      */
  237.     private function _compare(Testing_DocTest_TestCase $test)
  238.     {
  239.         $exp trim($test->expectedValue"\n");
  240.         $act trim($test->actualValue"\n");
  241.         if ($test->hasFlag(Testing_DocTest::FLAG_CASE_INSENSITIVE)) {
  242.             $exp strtolower($exp);
  243.             $act strtolower($act);
  244.         }
  245.         if ($test->hasFlag(Testing_DocTest::FLAG_NORMALIZE_WHITESPACE)) {
  246.             $exp preg_replace('/\s/'''$exp);
  247.             $act preg_replace('/\s/'''$act);
  248.         }
  249.         if ($test->hasFlag(Testing_DocTest::FLAG_ELLIPSIS)) {
  250.             $exp str_replace(
  251.                 array("\n""\r\n"'[...]'),
  252.                 array('''''__ellipsis__')$exp
  253.             );
  254.             $act str_replace(array("\n""\r\n"'[...]')''$act);
  255.             $rx  preg_quote($exp'/');
  256.             $rx  str_replace('__ellipsis__''.*?'$rx);
  257.             return (bool)preg_match('/^'.$rx.'$/'$act);
  258.         }
  259.         return $exp == $act;
  260.     }
  261.  
  262.     // }}}
  263.     // _exec() {{{
  264.  
  265.     /**
  266.      * Run given php code in a subprocess and return an array as follows:
  267.      *
  268.      * <code>
  269.      * array(
  270.      *     'code'   => true|false, // return code of the process
  271.      *     'output' => 'string'    // output of the process (stdout or stderr)
  272.      * )
  273.      * </code>
  274.      *
  275.      * @param string                   $code     the php code to execute
  276.      * @param string                   $options  additionnal options to pass to php
  277.      * @param Testing_DocTest_TestCase $testCase aa
  278.      *
  279.      * @access private
  280.      * @return array 
  281.      * @throws Testing_DocTest_Exception if the process cannot be opened
  282.      */
  283.     private function _exec(
  284.         $code$options=null,
  285.         Testing_DocTest_TestCase $testCase=null
  286.     {
  287.         if (isset($testCase$testCase->_shellOptions['php_wrapper']
  288.             && $testCase->_shellOptions['php_wrapper']
  289.         {
  290.             //Needed for general framework setup
  291.             putenv('DOCTEST_SCRIPT='.$testCase->file);
  292.             $php $testCase->_shellOptions['php_wrapper'];
  293.         else {
  294.             $php substr('@php_bin@'01== '@' 'php ' 'php';
  295.             if (substr(PHP_OS03== 'WIN'{
  296.                 $php '"' $php '"';
  297.             }
  298.         }
  299.  
  300.         if ($options !== null{
  301.             $php .= ' ' $options;
  302.         }
  303.  
  304.         $descriptors = array(
  305.                 0 => array('pipe''r')// stdin
  306.                 1 => array('pipe''w')// stdout
  307.                 2 => array('pipe''w')  // stderr
  308.         );
  309.         // try to open proc and raise an exception if it fails
  310.         $process proc_open($php$descriptors$pipes);
  311.  
  312.         if (!is_resource($process)) {
  313.             throw new Testing_DocTest_Exception("Unable to open process: $php");
  314.         }
  315.  
  316.         // write code to stdin
  317.         fwrite($pipes[0]$code);
  318.         fflush($pipes[0]);
  319.         fclose($pipes[0]);
  320.         // will contain script output
  321.         $output '';
  322.  
  323.         while (true{
  324.             // hide errors from interrupted syscalls
  325.             $r $pipes;
  326.             $e = null;
  327.             $w = null;
  328.             $n @stream_select($r$w$e60);
  329.             if ($n <= 0{
  330.                 // timed out
  331.                 $output .= "\n ** ERROR: process timed out **\n";
  332.                 return array(proc_terminate($process)$output);
  333.             }
  334.             if (false === ($data fgets($pipes[1]))) {
  335.                 // nothing on stdout, try stderr
  336.                 //if (false === ($data = fgets($pipes[2]))) {
  337.                     break;
  338.                     //}
  339.             }
  340.             $output .= $data;
  341.         }
  342.  
  343.         // close stdout and stderr
  344.         fflush($pipes[1]);
  345.         fclose($pipes[1]);
  346.         fflush($pipes[2]);
  347.         fclose($pipes[2]);
  348.         // get return code by closing the process
  349.         return array('code' => proc_close($process)'output' => $output);
  350.     }
  351.  
  352.         // }}}
  353. }

Documentation generated on Thu, 17 Jan 2013 10:30:03 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.