Improved XML loading for Codeception. Issue #182.
This commit is contained in:
parent
541c6ffc3f
commit
7bc9d1ff12
85
src/Helper/Xml.php
Normal file
85
src/Helper/Xml.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PHPCensor\Helper;
|
||||||
|
|
||||||
|
class XmlUtf8CleanFilter extends \php_user_filter
|
||||||
|
{
|
||||||
|
const PATTERN = '/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $in
|
||||||
|
* @param resource $out
|
||||||
|
* @param int $consumed
|
||||||
|
* @param bool $closing
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function filter($in, $out, &$consumed, $closing)
|
||||||
|
{
|
||||||
|
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||||
|
$bucket->data = preg_replace(self::PATTERN, '', $bucket->data);
|
||||||
|
$consumed += $bucket->datalen;
|
||||||
|
|
||||||
|
stream_bucket_append($out, $bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PSFS_PASS_ON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Xml
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param $filePath
|
||||||
|
*
|
||||||
|
* @return null|\SimpleXMLElement
|
||||||
|
*/
|
||||||
|
public static function loadFromFile($filePath)
|
||||||
|
{
|
||||||
|
stream_filter_register('xml_utf8_clean', 'PHPCensor\Helper\XmlUtf8CleanFilter');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$xml = simplexml_load_file('php://filter/read=xml_utf8_clean/resource=' . $filePath);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
$xml = null;
|
||||||
|
} catch (\Throwable $ex) { // since php7
|
||||||
|
$xml = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$xml) {
|
||||||
|
// from https://stackoverflow.com/questions/7766455/how-to-handle-invalid-unicode-with-simplexml/8092672#8092672
|
||||||
|
$oldUse = libxml_use_internal_errors(true);
|
||||||
|
|
||||||
|
libxml_clear_errors();
|
||||||
|
|
||||||
|
$dom = new \DOMDocument("1.0", "UTF-8");
|
||||||
|
|
||||||
|
$dom->strictErrorChecking = false;
|
||||||
|
$dom->validateOnParse = false;
|
||||||
|
$dom->recover = true;
|
||||||
|
|
||||||
|
$dom->loadXML(strtr(
|
||||||
|
file_get_contents($filePath),
|
||||||
|
['"' => "'"] // " in attribute names may mislead the parser
|
||||||
|
));
|
||||||
|
|
||||||
|
/** @var \LibXMLError $xmlError */
|
||||||
|
$xmlError = libxml_get_last_error();
|
||||||
|
if ($xmlError) {
|
||||||
|
$warning = sprintf('L%s C%s: %s', $xmlError->line, $xmlError->column, $xmlError->message);
|
||||||
|
print 'WARNING: ignored errors while reading phpunit result, '.$warning."\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$dom->hasChildNodes()) {
|
||||||
|
new \SimpleXMLElement('<empty/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$xml = simplexml_import_dom($dom);
|
||||||
|
|
||||||
|
libxml_clear_errors();
|
||||||
|
libxml_use_internal_errors($oldUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -145,8 +145,7 @@ class Codeception extends Plugin implements ZeroConfigPluginInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$xml = file_get_contents($outputPath . 'report.xml', false);
|
$parser = new Parser($this->builder, ($outputPath . 'report.xml'));
|
||||||
$parser = new Parser($this->builder, $xml);
|
|
||||||
$output = $parser->parse();
|
$output = $parser->parse();
|
||||||
|
|
||||||
$meta = [
|
$meta = [
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace PHPCensor\Plugin\Util;
|
namespace PHPCensor\Plugin\Util;
|
||||||
|
|
||||||
|
use PHPCensor\Helper\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class PhpUnitResultJunit parses the results for the PhpUnitV2 plugin
|
* Class PhpUnitResultJunit parses the results for the PhpUnitV2 plugin
|
||||||
*
|
*
|
||||||
|
@ -24,11 +26,11 @@ class PhpUnitResultJunit extends PhpUnitResult
|
||||||
|
|
||||||
$suites = $this->loadResultFile();
|
$suites = $this->loadResultFile();
|
||||||
|
|
||||||
foreach ($suites->xpath('//testcase') as $testCase) {
|
if ($suites) {
|
||||||
$this->parseTestcase($testCase);
|
foreach ($suites->xpath('//testcase') as $testCase) {
|
||||||
|
$this->parseTestcase($testCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$suites['failures'];
|
|
||||||
$suites['errors'];
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -139,44 +141,7 @@ class PhpUnitResultJunit extends PhpUnitResult
|
||||||
return new \SimpleXMLElement('<empty/>'); // new empty element
|
return new \SimpleXMLElement('<empty/>'); // new empty element
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return Xml::loadFromFile($this->outputFile);
|
||||||
$suites = simplexml_load_file($this->outputFile);
|
|
||||||
} catch (\Exception $ex) {
|
|
||||||
$suites = null;
|
|
||||||
} catch (\Throwable $ex) { // since php7
|
|
||||||
$suites = null;
|
|
||||||
}
|
|
||||||
if (!$suites) {
|
|
||||||
// from https://stackoverflow.com/questions/7766455/how-to-handle-invalid-unicode-with-simplexml/8092672#8092672
|
|
||||||
$oldUse = libxml_use_internal_errors(true);
|
|
||||||
libxml_clear_errors();
|
|
||||||
$dom = new \DOMDocument("1.0", "UTF-8");
|
|
||||||
$dom->strictErrorChecking = false;
|
|
||||||
$dom->validateOnParse = false;
|
|
||||||
$dom->recover = true;
|
|
||||||
$dom->loadXML(strtr(
|
|
||||||
file_get_contents($this->outputFile),
|
|
||||||
array('"' => "'") // " in attribute names may mislead the parser
|
|
||||||
));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \LibXMLError
|
|
||||||
*/
|
|
||||||
$xmlError = libxml_get_last_error();
|
|
||||||
if ($xmlError) {
|
|
||||||
$warning = sprintf('L%s C%s: %s', $xmlError->line, $xmlError->column, $xmlError->message);
|
|
||||||
print 'WARNING: ignored errors while reading phpunit result, '.$warning."\n";
|
|
||||||
}
|
|
||||||
if (!$dom->hasChildNodes()) {
|
|
||||||
$this->internalProblem('xml file with no content');
|
|
||||||
}
|
|
||||||
$suites = simplexml_import_dom($dom);
|
|
||||||
|
|
||||||
libxml_clear_errors();
|
|
||||||
libxml_use_internal_errors($oldUse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $suites;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -185,7 +150,5 @@ class PhpUnitResultJunit extends PhpUnitResult
|
||||||
private function internalProblem($description)
|
private function internalProblem($description)
|
||||||
{
|
{
|
||||||
throw new \RuntimeException($description);
|
throw new \RuntimeException($description);
|
||||||
|
|
||||||
// alternative to error throwing: append to $this->errors
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace PHPCensor\Plugin\Util\TestResultParsers;
|
namespace PHPCensor\Plugin\Util\TestResultParsers;
|
||||||
|
|
||||||
use PHPCensor\Builder;
|
use PHPCensor\Builder;
|
||||||
|
use PHPCensor\Helper\Xml;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Codeception
|
* Class Codeception
|
||||||
|
@ -11,23 +12,49 @@ use PHPCensor\Builder;
|
||||||
*/
|
*/
|
||||||
class Codeception implements ParserInterface
|
class Codeception implements ParserInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var Builder
|
||||||
|
*/
|
||||||
protected $builder;
|
protected $builder;
|
||||||
protected $resultsXml;
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $xmlPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
protected $results;
|
protected $results;
|
||||||
protected $totalTests;
|
|
||||||
protected $totalTimeTaken;
|
/**
|
||||||
protected $totalFailures;
|
* @var int
|
||||||
protected $totalErrors;
|
*/
|
||||||
|
protected $totalTests = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $totalTimeTaken = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $totalFailures = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $totalErrors = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Builder $builder
|
* @param Builder $builder
|
||||||
* @param $resultsXml
|
* @param string $xmlPath
|
||||||
*/
|
*/
|
||||||
public function __construct(Builder $builder, $resultsXml)
|
public function __construct(Builder $builder, $xmlPath)
|
||||||
{
|
{
|
||||||
$this->builder = $builder;
|
$this->builder = $builder;
|
||||||
$this->resultsXml = $resultsXml;
|
$this->xmlPath = $xmlPath;
|
||||||
$this->totalTests = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,42 +63,43 @@ class Codeception implements ParserInterface
|
||||||
public function parse()
|
public function parse()
|
||||||
{
|
{
|
||||||
$rtn = [];
|
$rtn = [];
|
||||||
$this->results = new \SimpleXMLElement($this->resultsXml);
|
$this->results = Xml::loadFromFile($this->xmlPath);
|
||||||
|
|
||||||
// calculate total results
|
if ($this->results) {
|
||||||
foreach ($this->results->testsuite as $testSuite) {
|
foreach ($this->results->testsuite as $testSuite) {
|
||||||
$this->totalTests += (int)$testSuite['tests'];
|
$this->totalTests += (int)$testSuite['tests'];
|
||||||
$this->totalTimeTaken += (float)$testSuite['time'];
|
$this->totalTimeTaken += (float)$testSuite['time'];
|
||||||
$this->totalFailures += (int)$testSuite['failures'];
|
$this->totalFailures += (int)$testSuite['failures'];
|
||||||
$this->totalErrors += (int)$testSuite['errors'];
|
$this->totalErrors += (int)$testSuite['errors'];
|
||||||
|
|
||||||
foreach ($testSuite->testcase as $testCase) {
|
foreach ($testSuite->testcase as $testCase) {
|
||||||
$testResult = [
|
$testResult = [
|
||||||
'suite' => (string)$testSuite['name'],
|
'suite' => (string)$testSuite['name'],
|
||||||
'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']),
|
'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']),
|
||||||
'name' => (string)$testCase['name'],
|
'name' => (string)$testCase['name'],
|
||||||
'feature' => (string)$testCase['feature'],
|
'feature' => (string)$testCase['feature'],
|
||||||
'assertions' => (int)$testCase['assertions'],
|
'assertions' => (int)$testCase['assertions'],
|
||||||
'time' => (float)$testCase['time']
|
'time' => (float)$testCase['time']
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($testCase['class'])) {
|
if (isset($testCase['class'])) {
|
||||||
$testResult['class'] = (string) $testCase['class'];
|
$testResult['class'] = (string) $testCase['class'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHPUnit testcases does not have feature field. Use class::method instead
|
||||||
|
if (!$testResult['feature']) {
|
||||||
|
$testResult['feature'] = sprintf('%s::%s', $testResult['class'], $testResult['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($testCase->failure) || isset($testCase->error)) {
|
||||||
|
$testResult['pass'] = false;
|
||||||
|
$testResult['message'] = isset($testCase->failure) ? (string)$testCase->failure : (string)$testCase->error;
|
||||||
|
} else {
|
||||||
|
$testResult['pass'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rtn[] = $testResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PHPUnit testcases does not have feature field. Use class::method instead
|
|
||||||
if (!$testResult['feature']) {
|
|
||||||
$testResult['feature'] = sprintf('%s::%s', $testResult['class'], $testResult['name']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($testCase->failure) || isset($testCase->error)) {
|
|
||||||
$testResult['pass'] = false;
|
|
||||||
$testResult['message'] = isset($testCase->failure) ? (string)$testCase->failure : (string)$testCase->error;
|
|
||||||
} else {
|
|
||||||
$testResult['pass'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rtn[] = $testResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue