366 lines
9.0 KiB
PHP
366 lines
9.0 KiB
PHP
<?php
|
|
|
|
require_once 'Sweety/Runner.php';
|
|
require_once 'Sweety/TestLocator.php';
|
|
require_once 'Sweety/Reporter.php';
|
|
|
|
/**
|
|
* Base functionality for the Sweety_Runner.
|
|
* @package Sweety
|
|
* @author Chris Corbyn
|
|
*/
|
|
abstract class Sweety_Runner_AbstractTestRunner implements Sweety_Runner
|
|
{
|
|
|
|
/**
|
|
* The reporter used for showing progress.
|
|
* @var Sweety_Reporter
|
|
* @access private
|
|
*/
|
|
private $_reporter;
|
|
|
|
/**
|
|
* TestLocator strategies.
|
|
* @var Sweety_TestLocator[]
|
|
* @access private
|
|
*/
|
|
private $_testLocators = array();
|
|
|
|
/**
|
|
* Regular expression for classes which should be ignored.
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
private $_ignoredClassRegex = '/^$/D';
|
|
|
|
/**
|
|
* Set the reporter used for showing results.
|
|
* @param Sweety_Reporter $reporter
|
|
*/
|
|
public function setReporter(Sweety_Reporter $reporter)
|
|
{
|
|
$this->_reporter = $reporter;
|
|
}
|
|
|
|
/**
|
|
* Get the reporter used for showing results.
|
|
* @return Sweety_Reporter
|
|
*/
|
|
public function getReporter()
|
|
{
|
|
return $this->_reporter;
|
|
}
|
|
|
|
/**
|
|
* Register a test locator instance.
|
|
* @param Sweety_TestLocator $locator
|
|
*/
|
|
public function registerTestLocator(Sweety_TestLocator $locator)
|
|
{
|
|
$this->_testLocators[] = $locator;
|
|
}
|
|
|
|
/**
|
|
* Set the regular expression used to filter out certain class names.
|
|
* @param string $ignoredClassRegex
|
|
*/
|
|
public function setIgnoredClassRegex($ignoredClassRegex)
|
|
{
|
|
$this->_ignoredClassRegex = $ignoredClassRegex;
|
|
}
|
|
|
|
/**
|
|
* Get the filtering regular expression for ignoring certain classes.
|
|
* @return string
|
|
*/
|
|
public function getIgnoredClassRegex()
|
|
{
|
|
return $this->_ignoredClassRegex;
|
|
}
|
|
|
|
/**
|
|
* Run a single test case with the given name, using the provided output format.
|
|
* @param string $testName
|
|
* @param string $format (xml, text or html)
|
|
* @return int
|
|
*/
|
|
public function runTestCase($testName, $format = Sweety_Runner::REPORT_TEXT)
|
|
{
|
|
foreach ($this->_testLocators as $locator)
|
|
{
|
|
if ($locator->includeTest($testName))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
$testClass = new ReflectionClass($testName);
|
|
if ($testClass->getConstructor())
|
|
{
|
|
//We don't want test output to be cached
|
|
if (!SimpleReporter::inCli())
|
|
{
|
|
header("Cache-Control: no-cache, must-revalidate");
|
|
header("Pragma: no-cache");
|
|
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
|
}
|
|
|
|
switch ($format)
|
|
{
|
|
case Sweety_Runner::REPORT_HTML:
|
|
$reporter = new HtmlReporter();
|
|
break;
|
|
case Sweety_Runner::REPORT_XML:
|
|
if (!SimpleReporter::inCli())
|
|
{
|
|
header("Content-Type: text/xml"); //Sigh! SimpleTest (skip() issues).
|
|
}
|
|
$reporter = new XmlReporter();
|
|
break;
|
|
case Sweety_Runner::REPORT_TEXT:
|
|
default:
|
|
$reporter = new TextReporter();
|
|
break;
|
|
}
|
|
$test = $testClass->newInstance();
|
|
return $test->run($reporter) ? 0 : 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Use strategies to find tests which are runnable.
|
|
* @param string[] $dirs
|
|
* @return string[]
|
|
*/
|
|
protected function findTests($dirs = array())
|
|
{
|
|
$tests = array();
|
|
foreach ($this->_testLocators as $locator)
|
|
{
|
|
$tests += $locator->getTests($dirs);
|
|
}
|
|
return $tests;
|
|
}
|
|
|
|
/**
|
|
* Parse an XML response from a test case an report to the reporter.
|
|
* @param string $xml
|
|
* @param string $testCase
|
|
*/
|
|
protected function parseXml($xml, $testCase)
|
|
{
|
|
$reporter = $this->_reporter->getReporterFor($testCase);
|
|
if (!$reporter->isStarted())
|
|
{
|
|
$reporter->start();
|
|
}
|
|
|
|
$xml = str_replace("\0", '?', trim($xml));
|
|
$xml = preg_replace('/[^\x01-\x7F]/e', 'sprintf("&#%d;", ord("$0"));', $xml); //Do something better?
|
|
if (!empty($xml))
|
|
{
|
|
$document = @simplexml_load_string($xml);
|
|
if ($document)
|
|
{
|
|
$this->_parseDocument($document, $testCase, $reporter);
|
|
$reporter->finish();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$reporter->reportException(
|
|
'Invalid XML response: ' .
|
|
trim(strip_tags(
|
|
preg_replace('/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/s', '', $xml)
|
|
)),
|
|
$testCase
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Parse formatted test output.
|
|
* @param SimpleXMLElement The node containing the output
|
|
* @param string $path to this test method
|
|
* @access private
|
|
*/
|
|
private function _parseFormatted(SimpleXMLElement $formatted, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$reporter->reportOutput((string)$formatted, $path);
|
|
}
|
|
|
|
/**
|
|
* Parse test output.
|
|
* @param SimpleXMLElement The node containing the output
|
|
* @param string $path to this test method
|
|
* @access private
|
|
*/
|
|
private function _parseMessage(SimpleXMLElement $message, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$reporter->reportOutput((string)$message, $path);
|
|
}
|
|
|
|
/**
|
|
* Parse a test failure.
|
|
* @param SimpleXMLElement The node containing the fail
|
|
* @param string $path to this test method
|
|
* @access private
|
|
*/
|
|
private function _parseFailure(SimpleXMLElement $failure, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$reporter->reportFail((string)$failure, $path);
|
|
}
|
|
|
|
/**
|
|
* Parse an exception.
|
|
* @param SimpleXMLElement The node containing the exception
|
|
* @param string $path to this test method
|
|
* @access private
|
|
*/
|
|
private function _parseException(SimpleXMLElement $exception, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$reporter->reportException((string)$exception, $path);
|
|
}
|
|
|
|
/**
|
|
* Parse a pass.
|
|
* @param SimpleXMLElement The node containing this pass.
|
|
* @param string $path to this test method
|
|
* @access private
|
|
*/
|
|
private function _parsePass(SimpleXMLElement $pass, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$reporter->reportPass((string)$pass, $path);
|
|
}
|
|
|
|
/**
|
|
* Parse a single test case.
|
|
* @param SimpleXMLElement The node containing the test case
|
|
* @param string $path to this test case
|
|
* @access private
|
|
*/
|
|
private function _parseTestCase(SimpleXMLElement $testCase, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
foreach ($testCase->xpath('./test') as $testMethod)
|
|
{
|
|
$testMethodName = (string) $this->_firstNodeValue($testMethod->xpath('./name'));
|
|
|
|
foreach ($testMethod->xpath('./formatted') as $formatted)
|
|
{
|
|
$this->_parseFormatted(
|
|
$formatted, $path . ' -> ' . $testMethodName, $reporter);
|
|
}
|
|
|
|
foreach ($testMethod->xpath('./message') as $message)
|
|
{
|
|
$this->_parseMessage(
|
|
$message, $path . ' -> ' . $testMethodName, $reporter);
|
|
}
|
|
|
|
foreach ($testMethod->xpath('./fail') as $failure)
|
|
{
|
|
$this->_parseFailure(
|
|
$failure, $path . ' -> ' . $testMethodName, $reporter);
|
|
}
|
|
|
|
foreach ($testMethod->xpath('./exception') as $exception)
|
|
{
|
|
$this->_parseException(
|
|
$exception, $path . ' -> ' . $testMethodName, $reporter);
|
|
}
|
|
|
|
foreach ($testMethod->xpath('./pass') as $pass)
|
|
{
|
|
$this->_parsePass($pass, $path . ' -> ' . $testMethodName, $reporter);
|
|
}
|
|
|
|
$stdout = trim((string) $testMethod);
|
|
if ($stdout)
|
|
{
|
|
$reporter->reportOutput($stdout, $path . ' -> ' . $testMethodName);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse the results of all tests.
|
|
* @param SimpleXMLElement The node containing the tests
|
|
* @param string $path to the tests
|
|
* @access private
|
|
*/
|
|
private function _parseResults(SimpleXMLElement $document, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
$groups = $document->xpath('./group');
|
|
if (!empty($groups))
|
|
{
|
|
foreach ($groups as $group)
|
|
{
|
|
$groupName = (string) $this->_firstNodeValue($group->xpath('./name'));
|
|
$this->_parseResults($group, $path . ' -> ' . $groupName, $reporter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach ($document->xpath('./case') as $testCase)
|
|
{
|
|
$this->_parseTestCase($testCase, $path, $reporter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse the entire SimpleTest XML document from a test case.
|
|
* @param SimpleXMLElement $document to parse
|
|
* @param string $path to the test
|
|
* @access private
|
|
*/
|
|
private function _parseDocument(SimpleXMLElement $document, $path = '',
|
|
Sweety_Reporter $reporter)
|
|
{
|
|
if ($everything = $this->_firstNodeValue($document->xpath('/run')))
|
|
{
|
|
$this->_parseResults($everything, $path, $reporter);
|
|
}
|
|
elseif ($skip = $this->_firstNodeValue($document->xpath('/skip')))
|
|
{
|
|
$reporter->reportSkip((string) $skip, $path);
|
|
}
|
|
}
|
|
|
|
protected function _sort($a, $b)
|
|
{
|
|
$apkg = preg_replace('/_[^_]+$/D', '', $a);
|
|
$bpkg = preg_replace('/_[^_]+$/D', '', $b);
|
|
if ($apkg == $bpkg)
|
|
{
|
|
if ($a == $b)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return ($a > $b) ? 1 : -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ($apkg > $bpkg) ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
private function _firstNodeValue($nodeSet)
|
|
{
|
|
$first = array_shift($nodeSet);
|
|
return $first;
|
|
}
|
|
|
|
}
|