2008-08-09 08:18:32 +02:00
|
|
|
<?php
|
2016-01-25 22:27:52 +01:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Dev;
|
|
|
|
|
|
|
|
use PHPUnit_Framework_TestListener;
|
|
|
|
use Benchmark_Timer;
|
|
|
|
use PHPUnit_Framework_TestSuite;
|
|
|
|
use PHPUnit_Framework_Test;
|
|
|
|
use PHPUnit_Framework_AssertionFailedError;
|
|
|
|
use Exception;
|
|
|
|
use PHPUnit_Framework_TestCase;
|
|
|
|
|
2008-08-09 08:18:32 +02:00
|
|
|
/**#@+
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Failure test status constant
|
|
|
|
*/
|
|
|
|
define('TEST_FAILURE', -1);
|
|
|
|
/**
|
|
|
|
* Error test status constant
|
|
|
|
*/
|
|
|
|
define('TEST_ERROR', 0);
|
|
|
|
/**
|
|
|
|
* Success test status constant
|
|
|
|
*/
|
|
|
|
define('TEST_SUCCESS', 1);
|
|
|
|
/**
|
|
|
|
* Incomplete test status constant
|
|
|
|
*/
|
|
|
|
define('TEST_INCOMPLETE', 2);
|
|
|
|
/**#@-*/
|
|
|
|
|
2010-04-23 02:11:41 +02:00
|
|
|
/**
|
2014-08-15 08:53:05 +02:00
|
|
|
* Gathers details about PHPUnit2 test suites as they
|
2010-04-23 02:11:41 +02:00
|
|
|
* are been executed. This does not actually format any output
|
2014-08-15 08:53:05 +02:00
|
|
|
* but simply gathers extended information about the overall
|
2010-04-23 02:11:41 +02:00
|
|
|
* results of all suites & their tests for use elsewhere.
|
|
|
|
*
|
|
|
|
* Changelog:
|
|
|
|
* 0.6 First created [David Spurr]
|
|
|
|
* 0.7 Added fix to getTestException provided [Glen Ogilvie]
|
2014-08-15 08:53:05 +02:00
|
|
|
*
|
2010-04-23 02:11:41 +02:00
|
|
|
* @version 0.7 2006-03-12
|
|
|
|
* @author David Spurr
|
|
|
|
*/
|
2016-11-29 00:31:16 +01:00
|
|
|
class SapphireTestReporter implements PHPUnit_Framework_TestListener
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Holds array of suites and total number of tests run
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $suiteResults;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds data of current suite that is been run
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $currentSuite;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds data of current test that is been run
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $currentTest;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether PEAR Benchmark_Timer is available for timing
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
protected $hasTimer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds the PEAR Benchmark_Timer object
|
|
|
|
*
|
|
|
|
* @var Benchmark_Timer
|
|
|
|
*/
|
|
|
|
protected $timer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected $startTestTime;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An array of all the test speeds
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $testSpeeds = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Errors not belonging to a test or suite
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $currentSession = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor, checks to see availability of PEAR Benchmark_Timer and
|
|
|
|
* sets up basic properties
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
@include_once 'Benchmark/Timer.php';
|
|
|
|
if (class_exists('Benchmark_Timer')) {
|
|
|
|
$this->timer = new Benchmark_Timer();
|
|
|
|
$this->hasTimer = true;
|
|
|
|
} else {
|
|
|
|
$this->hasTimer = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->suiteResults = array(
|
|
|
|
'suites' => array(), // array of suites run
|
|
|
|
'hasTimer' => $this->hasTimer, // availability of PEAR Benchmark_Timer
|
|
|
|
'totalTests' => 0 // total number of tests run
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->currentSession = array(
|
|
|
|
'errors' => 0, // number of tests with errors (including setup errors)
|
|
|
|
'failures' => 0, // number of tests which failed
|
|
|
|
'incomplete' => 0, // number of tests that were not completed correctly
|
|
|
|
'error' => array(), // Any error encountered outside of suites
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the suite results
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return array Suite results
|
|
|
|
*/
|
|
|
|
public function getSuiteResults()
|
|
|
|
{
|
|
|
|
return $this->suiteResults;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets up the container for result details of the current test suite when
|
|
|
|
* each suite is first run
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_TestSuite $suite the suite that is been run
|
|
|
|
*/
|
|
|
|
public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
|
|
|
|
{
|
|
|
|
$this->endCurrentTestSuite();
|
|
|
|
$this->currentSuite = array(
|
|
|
|
'suite' => $suite, // the test suite
|
|
|
|
'tests' => array(), // the tests in the suite
|
|
|
|
'errors' => 0, // number of tests with errors (including setup errors)
|
|
|
|
'failures' => 0, // number of tests which failed
|
|
|
|
'incomplete' => 0, // number of tests that were not completed correctly
|
|
|
|
'error' => null, // Any error encountered during setup of the test suite
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets up the container for result details of the current test when each
|
|
|
|
* test is first run
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test the test that is being run
|
|
|
|
*/
|
|
|
|
public function startTest(PHPUnit_Framework_Test $test)
|
|
|
|
{
|
|
|
|
$this->endCurrentTest();
|
|
|
|
|
|
|
|
$this->startTestTime = microtime(true);
|
|
|
|
$this->currentTest = array(
|
|
|
|
// the name of the test (without the suite name)
|
|
|
|
'name' => $this->descriptiveTestName($test),
|
|
|
|
// execution time of the test
|
|
|
|
'timeElapsed' => 0,
|
|
|
|
// status of the test execution
|
|
|
|
'status' => TEST_SUCCESS,
|
|
|
|
// user message of test result
|
|
|
|
'message' => '',
|
|
|
|
// original caught exception thrown by test upon failure/error
|
|
|
|
'exception' => null,
|
|
|
|
// Stacktrace used for exception handling
|
|
|
|
'trace' => null,
|
|
|
|
// a unique ID for this test (used for identification purposes in results)
|
|
|
|
'uid' => md5(microtime())
|
|
|
|
);
|
|
|
|
if ($this->hasTimer) {
|
|
|
|
$this->timer->start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Logs the specified status to the current test, or if no test is currently
|
|
|
|
* run, to the test suite.
|
|
|
|
* @param integer $status Status code
|
|
|
|
* @param string $message Message to log
|
|
|
|
* @param string $exception Exception body related to this message
|
|
|
|
* @param array $trace Stacktrace
|
|
|
|
*/
|
|
|
|
protected function addStatus($status, $message, $exception, $trace)
|
|
|
|
{
|
|
|
|
// Build status body to be saved
|
|
|
|
$statusResult = array(
|
|
|
|
'status' => $status,
|
|
|
|
'message' => $message,
|
|
|
|
'exception' => $exception,
|
|
|
|
'trace' => $trace
|
|
|
|
);
|
|
|
|
|
|
|
|
// Log either to current test or suite record
|
|
|
|
if ($this->currentTest) {
|
|
|
|
$this->currentTest = array_merge($this->currentTest, $statusResult);
|
|
|
|
} elseif ($this->currentSuite) {
|
|
|
|
$this->currentSuite['error'] = $statusResult;
|
|
|
|
} else {
|
|
|
|
$this->currentSession['error'][] = $statusResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the failure detail to the current test and increases the failure
|
|
|
|
* count for the current suite
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test current test that is being run
|
|
|
|
* @param PHPUnit_Framework_AssertionFailedError $e PHPUnit error
|
|
|
|
* @param int $time
|
|
|
|
*/
|
|
|
|
public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
|
|
|
|
{
|
|
|
|
if ($this->currentSuite) {
|
|
|
|
$this->currentSuite['failures']++;
|
|
|
|
} else {
|
|
|
|
$this->currentSession['failures']++;
|
|
|
|
}
|
|
|
|
$this->addStatus(TEST_FAILURE, $e->toString(), $this->getTestException($test, $e), $e->getTrace());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the error detail to the current test and increases the error
|
|
|
|
* count for the current suite
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test current test that is being run
|
|
|
|
* @param Exception $e PHPUnit error
|
|
|
|
* @param int $time
|
|
|
|
*/
|
|
|
|
public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
|
|
|
|
{
|
|
|
|
if ($this->currentSuite) {
|
|
|
|
$this->currentSuite['errors']++;
|
|
|
|
} else {
|
|
|
|
$this->currentSession['errors']++;
|
|
|
|
}
|
|
|
|
$this->addStatus(TEST_ERROR, $e->getMessage(), $this->getTestException($test, $e), $e->getTrace());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the test incomplete detail to the current test and increases the incomplete
|
|
|
|
* count for the current suite
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test current test that is being run
|
|
|
|
* @param Exception $e PHPUnit error
|
|
|
|
*/
|
|
|
|
public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
|
|
|
|
{
|
|
|
|
if ($this->currentSuite) {
|
|
|
|
$this->currentSuite['incomplete']++;
|
|
|
|
} else {
|
|
|
|
$this->currentSession['incomplete']++;
|
|
|
|
}
|
|
|
|
$this->addStatus(TEST_INCOMPLETE, $e->getMessage(), $this->getTestException($test, $e), $e->getTrace());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Not used
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test
|
|
|
|
* @param Exception $e
|
|
|
|
* @param int $time
|
|
|
|
*/
|
|
|
|
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
|
|
|
|
{
|
|
|
|
// not implemented
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanly end the current test
|
|
|
|
*/
|
|
|
|
protected function endCurrentTest()
|
|
|
|
{
|
|
|
|
if (!$this->currentTest || !$this->currentSuite) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Time the current test
|
|
|
|
$testDuration = microtime(true) - $this->startTestTime;
|
|
|
|
$this->testSpeeds[$this->currentSuite['suite']->getName() . '.' . $this->currentTest['name']] = $testDuration;
|
|
|
|
if ($this->hasTimer) {
|
|
|
|
$this->timer->stop();
|
|
|
|
$this->currentTest['timeElapsed'] = $this->timer->timeElapsed();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save and reset current state
|
|
|
|
array_push($this->currentSuite['tests'], $this->currentTest);
|
|
|
|
$this->currentTest = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Upon completion of a test, records the execution time (if available) and adds the test to
|
|
|
|
* the tests performed in the current suite.
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test Current test that is being run
|
|
|
|
* @param int $time
|
|
|
|
*/
|
|
|
|
public function endTest(PHPUnit_Framework_Test $test, $time)
|
|
|
|
{
|
|
|
|
$this->endCurrentTest();
|
|
|
|
if (method_exists($test, 'getActualOutput')) {
|
|
|
|
$output = $test->getActualOutput();
|
|
|
|
if ($output) {
|
|
|
|
echo "\nOutput:\n$output";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanly end the current test suite
|
|
|
|
*/
|
|
|
|
protected function endCurrentTestSuite()
|
|
|
|
{
|
|
|
|
if (!$this->currentSuite) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure any current test is ended along with the current suite
|
|
|
|
$this->endCurrentTest();
|
|
|
|
|
|
|
|
// Save and reset current state
|
|
|
|
array_push($this->suiteResults['suites'], $this->currentSuite);
|
|
|
|
$this->currentSuite = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Upon completion of a test suite adds the suite to the suties performed
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_TestSuite $suite current suite that is being run
|
|
|
|
*/
|
|
|
|
public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
|
|
|
|
{
|
|
|
|
if (strlen($suite->getName())) {
|
|
|
|
$this->endCurrentTestSuite();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Risky test.
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test
|
|
|
|
* @param Exception $e
|
|
|
|
* @param float $time
|
|
|
|
* @since Method available since Release 3.8.0
|
|
|
|
*/
|
|
|
|
public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
|
|
|
|
{
|
|
|
|
// Stub out to support PHPUnit 3.8
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to get the original exception thrown by the test on failure/error
|
|
|
|
* to enable us to give a bit more detail about the failure/error
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test current test that is being run
|
|
|
|
* @param Exception $e PHPUnit error
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getTestException(PHPUnit_Framework_Test $test, Exception $e)
|
|
|
|
{
|
|
|
|
// get the name of the testFile from the test
|
|
|
|
$testName = $this->descriptiveTestName($test);
|
|
|
|
$trace = $e->getTrace();
|
|
|
|
// loop through the exception trace to find the original exception
|
|
|
|
for ($i = 0; $i < count($trace); $i++) {
|
|
|
|
if (array_key_exists('file', $trace[$i])) {
|
|
|
|
if (stristr($trace[$i]['file'], $testName.'.php') != false) {
|
|
|
|
return $trace[$i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (array_key_exists('file:protected', $trace[$i])) {
|
|
|
|
if (stristr($trace[$i]['file:protected'], $testName.'.php') != false) {
|
|
|
|
return $trace[$i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes a status message to the output stream in a user readable HTML format
|
|
|
|
* @param string $name Name of the object that generated the error
|
|
|
|
* @param string $message Message of the error
|
|
|
|
* @param array $trace Stacktrace
|
|
|
|
*/
|
|
|
|
protected function writeResultError($name, $message, $trace)
|
|
|
|
{
|
|
|
|
echo "<div class=\"failure\"><h2 class=\"test-case\">⊗ ". $this->testNameToPhrase($name) ."</h2>";
|
|
|
|
echo "<pre>".htmlentities($message, ENT_COMPAT, 'UTF-8')."</pre>";
|
|
|
|
echo Backtrace::get_rendered_backtrace($trace);
|
|
|
|
echo "</div>";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display error bar if it exists
|
|
|
|
*/
|
|
|
|
public function writeResults()
|
|
|
|
{
|
|
|
|
$passCount = 0;
|
|
|
|
$failCount = 0;
|
|
|
|
$testCount = 0;
|
|
|
|
$incompleteCount = 0;
|
|
|
|
$errorCount = 0; // Includes both suite and test level errors
|
|
|
|
|
|
|
|
// Ensure that the current suite is cleanly ended.
|
|
|
|
// A suite may not end correctly if there was an error during setUp
|
|
|
|
$this->endCurrentTestSuite();
|
|
|
|
|
|
|
|
// Write session errors
|
|
|
|
if ($this->currentSession['error']) {
|
|
|
|
$errorCount += $this->currentSession['errors'];
|
|
|
|
$failCount += $this->currentSession['failures'];
|
|
|
|
$incompleteCount += $this->currentSession['incomplete'];
|
|
|
|
foreach ($this->currentSession['error'] as $error) {
|
|
|
|
$this->writeResultError(
|
|
|
|
'SilverStripe\\Control\\Session',
|
|
|
|
$error['message'],
|
|
|
|
$error['trace']
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write suite errors
|
|
|
|
foreach ($this->suiteResults['suites'] as $suite) {
|
|
|
|
// Report suite error. In the case of fatal non-success messages
|
|
|
|
// These should be reported as errors. Failure/Success relate only
|
|
|
|
// to individual tests directly
|
|
|
|
if ($suite['error']) {
|
|
|
|
$errorCount++;
|
|
|
|
$this->writeResultError(
|
|
|
|
$suite['suite']->getName(),
|
|
|
|
$suite['error']['message'],
|
|
|
|
$suite['error']['trace']
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run through all tests in this suite
|
|
|
|
foreach ($suite['tests'] as $test) {
|
|
|
|
$testCount++;
|
|
|
|
switch ($test['status']) {
|
2016-11-29 00:33:28 +01:00
|
|
|
case TEST_ERROR:
|
|
|
|
$errorCount++;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-29 00:33:28 +01:00
|
|
|
break;
|
|
|
|
case TEST_INCOMPLETE:
|
|
|
|
$incompleteCount++;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-29 00:33:28 +01:00
|
|
|
break;
|
|
|
|
case TEST_SUCCESS:
|
|
|
|
$passCount++;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-29 00:33:28 +01:00
|
|
|
break;
|
|
|
|
case TEST_FAILURE:
|
|
|
|
$failCount++;
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2016-11-29 00:33:28 +01:00
|
|
|
break;
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Report test error
|
|
|
|
if ($test['status'] != TEST_SUCCESS) {
|
|
|
|
$this->writeResultError(
|
|
|
|
$test['name'],
|
|
|
|
$test['message'],
|
|
|
|
$test['trace']
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$result = ($failCount || $errorCount) ? 'fail' : 'pass';
|
|
|
|
echo "<div class=\"status $result\">";
|
|
|
|
echo "<h2><span>$testCount</span> tests run: <span>$passCount</span> passes, <span>$failCount</span> failures,"
|
|
|
|
. " and <span>$incompleteCount</span> incomplete with <span>$errorCount</span> errors</h2>";
|
|
|
|
echo "</div>";
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function testNameToPhrase($name)
|
|
|
|
{
|
|
|
|
return ucfirst(preg_replace("/([a-z])([A-Z])/", "$1 $2", $name));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get name for this test
|
|
|
|
*
|
|
|
|
* @param PHPUnit_Framework_Test $test
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function descriptiveTestName(PHPUnit_Framework_Test $test)
|
|
|
|
{
|
|
|
|
if ($test instanceof PHPUnit_Framework_TestCase) {
|
|
|
|
$name = $test->toString();
|
|
|
|
} elseif (method_exists($test, 'getName')) {
|
|
|
|
$name = $test->getName();
|
|
|
|
} else {
|
|
|
|
$name = get_class($test);
|
|
|
|
}
|
|
|
|
// the name of the test (without the suite name)
|
|
|
|
return preg_replace('(\(.*\))', '', $name);
|
|
|
|
}
|
2008-08-09 08:18:32 +02:00
|
|
|
}
|