Version: current

      1 <?php
      2 require_once 'PHPUnit/Framework/TestResult.php';
      3 require_once 'PHPUnit/Framework/TestListener.php';
      4
      5 /**#@+
      6  * @var int
      7  */
      8 /**
      9  * Failure test status constant
     10  */
     11 define('TEST_FAILURE', -1);
     12 /**
     13  * Error test status constant
     14  */
     15 define('TEST_ERROR', 0);
     16 /**
     17  * Success test status constant
     18  */
     19 define('TEST_SUCCESS', 1);
     20 /**
     21  * Incomplete test status constant
     22  */
     23 define('TEST_INCOMPLETE', 2);
     24 /**#@-*/
     25
     26 /**
     27  * Gathers details about PHPUnit2 test suites as they
     28  * are been executed. This does not actually format any output
     29  * but simply gathers extended information about the overall
     30  * results of all suites & their tests for use elsewhere.
     31  *
     32  * Changelog:
     33  *  0.6 First created [David Spurr]
     34  *  0.7 Added fix to getTestException provided [Glen Ogilvie]
     35  *
     36  * @package sapphire
     37  * @subpackage testing
     38  *
     39  * @version 0.7 2006-03-12
     40  * @author David Spurr
     41  */
     42 class SapphireTestReporter implements PHPUnit_Framework_TestListener {
     43     /**
     44      * Holds array of suites and total number of tests run
     45      * @var array
     46      */
     47     protected $suiteResults;
     48     /**
     49      * Holds data of current suite that is been run
     50      * @var array
     51      */
     52     protected $currentSuite;
     53     /**
     54      * Holds data of current test that is been run
     55      * @var array
     56      */
     57     protected $currentTest;
     58     /**
     59      * Whether PEAR Benchmark_Timer is available for timing
     60      * @var boolean
     61      */
     62     protected $hasTimer;
     63     /**
     64      * Holds the PEAR Benchmark_Timer object
     65      * @var obj Benchmark_Timer
     66      */
     67     protected $timer;
     68
     69     protected $startTestTime;
     70
     71     /**
     72      * An array of all the test speeds
     73      */
     74     protected $testSpeeds = array();
     75
     76     /**
     77      * Constructor, checks to see availability of PEAR Benchmark_Timer and
     78      * sets up basic properties
     79      *
     80      * @access public
     81      * @return void
     82      */
     83     public function __construct() {
     84             @include_once 'Benchmark/Timer.php';
     85         if(class_exists('Benchmark_Timer')) {
     86             $this->timer = new Benchmark_Timer();
     87             $this->hasTimer = true;
     88         } else {
     89             $this->hasTimer = false;
     90         }
     91
     92         $this->suiteResults = array(
     93             'suites'      => array(),         // array of suites run
     94             'hasTimer'    => $this->hasTimer, // availability of PEAR Benchmark_Timer
     95             'totalTests'  => 0                // total number of tests run
     96         );
     97     }
     98
     99     /**
    100      * Returns the suite results
    101      *
    102      * @access public
    103      * @return array Suite results
    104      */
    105     public function getSuiteResults() {
    106         return $this->suiteResults;
    107     }
    108
    109     /**
    110      * Sets up the container for result details of the current test suite when
    111      * each suite is first run
    112      *
    113      * @access public
    114      * @param obj PHPUnit2_Framework_TestSuite, the suite that is been run
    115      * @return void
    116      */
    117     public function startTestSuite( PHPUnit_Framework_TestSuite $suite) {
    118         if(strlen($suite->getName())) {
    119             $this->currentSuite = array(
    120                 'suite'      => $suite,  // the test suite
    121                 'tests'      => array(), // the tests in the suite
    122                 'errors'     => 0,       // number of tests with errors
    123                 'failures'   => 0,       // number of tests which failed
    124                 'incomplete' => 0);      // number of tests that were not completed correctly
    125         }
    126     }
    127
    128     /**
    129      * Sets up the container for result details of the current test when each
    130      * test is first run
    131      *
    132      * @access public
    133      * @param obj PHPUnit_Framework_Test, the test that is being run
    134      * @return void
    135      */
    136     public function startTest(PHPUnit_Framework_Test $test) {
    137         $this->startTestTime = microtime(true);
    138
    139         if($test instanceof PHPUnit_Framework_TestCase) {
    140             $this->currentTest = array(
    141                 'name'        => preg_replace('(\(.*\))', '', $test->toString()), // the name of the test (without the suite name)
    142                 'timeElapsed' => 0,                // execution time of the test
    143                 'status'      => TEST_SUCCESS,     // status of the test execution
    144                 'message'     => '',               // user message of test result
    145                 'exception'   => NULL,             // original caught exception thrown by test upon failure/error
    146                 'uid'         => md5(microtime())  // a unique ID for this test (used for identification purposes in results)
    147             );
    148             if($this->hasTimer) $this->timer->start();
    149         }
    150     }
    151
    152     /**
    153      * Adds the failure detail to the current test and increases the failure
    154      * count for the current suite
    155      *
    156      * @access public
    157      * @param obj PHPUnit_Framework_Test, current test that is being run
    158      * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
    159      * @return void
    160      */
    161     public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {
    162         $this->currentSuite['failures']++;
    163         $this->currentTest['status'] = TEST_FAILURE;
    164         $this->currentTest['message'] = $e->toString();
    165         $this->currentTest['exception'] = $this->getTestException($test, $e);
    166         $this->currentTest['trace'] = $e->getTrace();
    167     }
    168
    169     /**
    170      * Adds the error detail to the current test and increases the error
    171      * count for the current suite
    172      *
    173      * @access public
    174      * @param obj PHPUnit_Framework_Test, current test that is being run
    175      * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
    176      * @return void
    177      */
    178     public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {
    179         $this->currentSuite['errors']++;
    180         $this->currentTest['status'] = TEST_ERROR;
    181         $this->currentTest['message'] = $e->getMessage();
    182         $this->currentTest['exception'] = $this->getTestException($test, $e);
    183         $this->currentTest['trace'] = $e->getTrace();
    184     }
    185
    186     /**
    187      * Adds the test incomplete detail to the current test and increases the incomplete
    188      * count for the current suite
    189      *
    190      * @access public
    191      * @param obj PHPUnit_Framework_Test, current test that is being run
    192      * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
    193      * @return void
    194      */
    195     public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
    196         $this->currentSuite['incomplete']++;
    197         $this->currentTest['status'] = TEST_INCOMPLETE;
    198         $this->currentTest['message'] = $e->toString();
    199         $this->currentTest['exception'] = $this->getTestException($test, $e);
    200         $this->currentTest['trace'] = $e->getTrace();
    201     }
    202
    203     /**
    204      * Not used
    205      *
    206      * @param PHPUnit_Framework_Test $test
    207      * @param unknown_type $time
    208      */
    209     public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
    210         // not implemented
    211     }
    212
    213     /**
    214      * Upon completion of a test, records the execution time (if available) and adds the test to
    215      * the tests performed in the current suite.
    216      *
    217      * @access public
    218      * @param obj PHPUnit_Framework_Test, current test that is being run
    219      * @return void
    220      */
    221     public function endTest( PHPUnit_Framework_Test $test, $time) {
    222         $testDuration = microtime(true) - $this->startTestTime;
    223         $this->testSpeeds[$this->currentSuite['suite']->getName() . '.' . $this->currentTest['name']] = $testDuration;
    224
    225         if($this->hasTimer) {
    226             $this->timer->stop();
    227             $this->currentTest['timeElapsed'] = $this->timer->timeElapsed();
    228         }
    229         array_push($this->currentSuite['tests'], $this->currentTest);
    230     }
    231
    232     /**
    233      * Upon completion of a test suite adds the suite to the suties performed
    234      *
    235      * @access public
    236      * @param obj PHPUnit_Framework_TestSuite, current suite that is being run
    237      * @return void
    238      */
    239     public function endTestSuite( PHPUnit_Framework_TestSuite $suite) {
    240         if(strlen($suite->getName())) {
    241             array_push($this->suiteResults['suites'], $this->currentSuite);
    242         }
    243     }
    244
    245     /**
    246      * Trys to get the original exception thrown by the test on failure/error
    247      * to enable us to give a bit more detail about the failure/error
    248      *
    249      * @access private
    250      * @param obj PHPUnit_Framework_Test, current test that is being run
    251      * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
    252      * @return array
    253      */
    254     private function getTestException(PHPUnit_Framework_Test $test, Exception $e) {
    255         // get the name of the testFile from the test
    256         $testName = ereg_replace('(.*)\((.*[^)])\)', '\\2', $test->toString());
    257         $trace = $e->getTrace();
    258         // loop through the exception trace to find the original exception
    259         for($i = 0; $i < count($trace); $i++) {
    260
    261             if(array_key_exists('file', $trace[$i])) {
    262                 if(stristr($trace[$i]['file'], $testName.'.php') != false) return $trace[$i];
    263             }
    264             if(array_key_exists('file:protected', $trace[$i])) {
    265                 if(stristr($trace[$i]['file:protected'], $testName.'.php') != false) return $trace[$i];
    266             }
    267         }
    268     }
    269
    270     /**
    271      * Display error bar if it exists
    272      */
    273     public function writeResults() {
    274         $passCount = 0;
    275         $failCount = 0;
    276         $testCount = 0;
    277         $errorCount = 0;
    278
    279         foreach($this->suiteResults['suites'] as $suite) {
    280             foreach($suite['tests'] as $test) {
    281                 $testCount++;
    282                 ($test['status'] == 1) ? $passCount++ : $failCount++;
    283                 if ($test['status'] != 1) {
    284                     echo "<div class=\"failure\"><span>&otimes; ". $this->testNameToPhrase($test['name']) ."</span><br>";
    285                     echo "<pre>".htmlentities($test['message'])."</pre><br>";
    286                     echo SS_Backtrace::get_rendered_backtrace($test['trace']);
    287                     echo "</div>";
    288                 }
    289             }
    290         }
    291         $result = ($failCount > 0) ? 'fail' : 'pass';
    292         echo "<div class=\"$result\">";
    293         echo "<h2><span>$testCount</span> tests run: <span>$passCount</span> passes, <span>$failCount</span> fails, and <span>0</span> exceptions</h2>";
    294         echo "</div>";
    295
    296     }
    297
    298     protected function testNameToPhrase($name) {
    299         return ucfirst(preg_replace("/([a-z])([A-Z])/", "$1 $2", $name));
    300     }
    301
    302 }
    303
    304 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -