diff --git a/src/Helper/Xml.php b/src/Helper/Xml.php new file mode 100644 index 00000000..e3b91876 --- /dev/null +++ b/src/Helper/Xml.php @@ -0,0 +1,85 @@ +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(''); + } + + $xml = simplexml_import_dom($dom); + + libxml_clear_errors(); + libxml_use_internal_errors($oldUse); + } + + return $xml; + } +} diff --git a/src/Plugin/Codeception.php b/src/Plugin/Codeception.php index d5e8e1d4..585aea4e 100644 --- a/src/Plugin/Codeception.php +++ b/src/Plugin/Codeception.php @@ -145,8 +145,7 @@ class Codeception extends Plugin implements ZeroConfigPluginInterface } } - $xml = file_get_contents($outputPath . 'report.xml', false); - $parser = new Parser($this->builder, $xml); + $parser = new Parser($this->builder, ($outputPath . 'report.xml')); $output = $parser->parse(); $meta = [ diff --git a/src/Plugin/Util/PhpUnitResultJunit.php b/src/Plugin/Util/PhpUnitResultJunit.php index 3e149105..14ec1c7f 100644 --- a/src/Plugin/Util/PhpUnitResultJunit.php +++ b/src/Plugin/Util/PhpUnitResultJunit.php @@ -2,6 +2,8 @@ namespace PHPCensor\Plugin\Util; +use PHPCensor\Helper\Xml; + /** * Class PhpUnitResultJunit parses the results for the PhpUnitV2 plugin * @@ -24,11 +26,11 @@ class PhpUnitResultJunit extends PhpUnitResult $suites = $this->loadResultFile(); - foreach ($suites->xpath('//testcase') as $testCase) { - $this->parseTestcase($testCase); + if ($suites) { + foreach ($suites->xpath('//testcase') as $testCase) { + $this->parseTestcase($testCase); + } } - $suites['failures']; - $suites['errors']; return $this; } @@ -139,44 +141,7 @@ class PhpUnitResultJunit extends PhpUnitResult return new \SimpleXMLElement(''); // new empty element } - try { - $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; + return Xml::loadFromFile($this->outputFile); } /** @@ -185,7 +150,5 @@ class PhpUnitResultJunit extends PhpUnitResult private function internalProblem($description) { throw new \RuntimeException($description); - - // alternative to error throwing: append to $this->errors } } diff --git a/src/Plugin/Util/TestResultParsers/Codeception.php b/src/Plugin/Util/TestResultParsers/Codeception.php index dfd97cae..90fcbb2e 100644 --- a/src/Plugin/Util/TestResultParsers/Codeception.php +++ b/src/Plugin/Util/TestResultParsers/Codeception.php @@ -3,6 +3,7 @@ namespace PHPCensor\Plugin\Util\TestResultParsers; use PHPCensor\Builder; +use PHPCensor\Helper\Xml; /** * Class Codeception @@ -11,23 +12,49 @@ use PHPCensor\Builder; */ class Codeception implements ParserInterface { + /** + * @var Builder + */ protected $builder; - protected $resultsXml; + + /** + * @var string + */ + protected $xmlPath; + + /** + * @var array + */ protected $results; - protected $totalTests; - protected $totalTimeTaken; - protected $totalFailures; - protected $totalErrors; + + /** + * @var int + */ + protected $totalTests = 0; + + /** + * @var int + */ + protected $totalTimeTaken = 0; + + /** + * @var int + */ + protected $totalFailures = 0; + + /** + * @var int + */ + protected $totalErrors = 0; /** * @param Builder $builder - * @param $resultsXml + * @param string $xmlPath */ - public function __construct(Builder $builder, $resultsXml) + public function __construct(Builder $builder, $xmlPath) { - $this->builder = $builder; - $this->resultsXml = $resultsXml; - $this->totalTests = 0; + $this->builder = $builder; + $this->xmlPath = $xmlPath; } /** @@ -36,42 +63,43 @@ class Codeception implements ParserInterface public function parse() { $rtn = []; - $this->results = new \SimpleXMLElement($this->resultsXml); + $this->results = Xml::loadFromFile($this->xmlPath); - // calculate total results - foreach ($this->results->testsuite as $testSuite) { - $this->totalTests += (int)$testSuite['tests']; - $this->totalTimeTaken += (float)$testSuite['time']; - $this->totalFailures += (int)$testSuite['failures']; - $this->totalErrors += (int)$testSuite['errors']; + if ($this->results) { + foreach ($this->results->testsuite as $testSuite) { + $this->totalTests += (int)$testSuite['tests']; + $this->totalTimeTaken += (float)$testSuite['time']; + $this->totalFailures += (int)$testSuite['failures']; + $this->totalErrors += (int)$testSuite['errors']; - foreach ($testSuite->testcase as $testCase) { - $testResult = [ - 'suite' => (string)$testSuite['name'], - 'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']), - 'name' => (string)$testCase['name'], - 'feature' => (string)$testCase['feature'], - 'assertions' => (int)$testCase['assertions'], - 'time' => (float)$testCase['time'] - ]; + foreach ($testSuite->testcase as $testCase) { + $testResult = [ + 'suite' => (string)$testSuite['name'], + 'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']), + 'name' => (string)$testCase['name'], + 'feature' => (string)$testCase['feature'], + 'assertions' => (int)$testCase['assertions'], + 'time' => (float)$testCase['time'] + ]; - if (isset($testCase['class'])) { - $testResult['class'] = (string) $testCase['class']; + if (isset($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; } }