From 9946e23f9ea7a545efa8285c2d2e5a1e3e51b7fc Mon Sep 17 00:00:00 2001 From: indiefan Date: Fri, 25 Jan 2013 17:06:20 -0800 Subject: [PATCH] Decoupling phpunit test parsing from running and gathering. Should enable third party test engines to reuse common business logic around phpunit without requiring common logic for the typically application-specific running and gathering. Test Plan: Ran PhpunitTestEngine unit test. Also used refactored PhpunitTestEngine to run phpunit tests. Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin, aurelijus Differential Revision: https://secure.phabricator.com/D4651 --- src/__phutil_library_map__.php | 1 + src/unit/engine/PhpunitResultParser.php | 200 ++++++++++++++++++++++++ src/unit/engine/PhpunitTestEngine.php | 155 +----------------- 3 files changed, 205 insertions(+), 151 deletions(-) create mode 100644 src/unit/engine/PhpunitResultParser.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f80ab6a8..7da6c249 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -145,6 +145,7 @@ phutil_register_library_map(array( 'ExampleLintEngine' => 'lint/engine/ExampleLintEngine.php', 'NoseTestEngine' => 'unit/engine/NoseTestEngine.php', 'PHPUnitTestEngineTestCase' => 'unit/engine/__tests__/PHPUnitTestEngineTestCase.php', + 'PhpunitResultParser' => 'unit/engine/PhpunitResultParser.php', 'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php', 'PhutilLintEngine' => 'lint/engine/PhutilLintEngine.php', 'PhutilUnitTestEngine' => 'unit/engine/PhutilUnitTestEngine.php', diff --git a/src/unit/engine/PhpunitResultParser.php b/src/unit/engine/PhpunitResultParser.php new file mode 100644 index 00000000..1915cb81 --- /dev/null +++ b/src/unit/engine/PhpunitResultParser.php @@ -0,0 +1,200 @@ +enableCoverage = $enable_coverage; + + return $this; + } + + public function setProjectRoot($project_root) { + $this->projectRoot = $project_root; + + return $this; + } + + /** + * Parse test results from phpunit json report + * + * @param string $path Path to test + * @param string $json_path Path to phpunit json report + * @param string $clover_tmp Path to phpunit clover report + * @param array $affected_tests Array of the tests affected by this run + * @param bool $enable_coverage Option to enable coverage in results + * + * @return array + */ + public function parseTestResults( + $path, + $json_tmp, + $clover_tmp, + $affected_tests) { + + $test_results = Filesystem::readFile($json_tmp); + + $report = $this->getJsonReport($json_tmp); + + // coverage is for all testcases in the executed $path + $coverage = array(); + if ($this->enableCoverage !== false) { + $coverage = $this->readCoverage($clover_tmp, $affected_tests); + } + + $results = array(); + foreach ($report as $event) { + if ('test' != $event->event) { + continue; + } + + $status = ArcanistUnitTestResult::RESULT_PASS; + $user_data = ''; + + if ('fail' == $event->status) { + $status = ArcanistUnitTestResult::RESULT_FAIL; + $user_data .= $event->message . "\n"; + foreach ($event->trace as $trace) { + $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); + } + } else if ('error' == $event->status) { + if ('Skipped Test' == $event->message) { + $status = ArcanistUnitTestResult::RESULT_SKIP; + $user_data .= $event->message; + } else if ('Incomplete Test' == $event->message) { + $status = ArcanistUnitTestResult::RESULT_SKIP; + $user_data .= $event->message; + } else { + $status = ArcanistUnitTestResult::RESULT_BROKEN; + $user_data .= $event->message; + foreach ($event->trace as $trace) { + $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); + } + } + } + + $name = preg_replace('/ \(.*\)/', '', $event->test); + + $result = new ArcanistUnitTestResult(); + $result->setName($name); + $result->setResult($status); + $result->setDuration($event->time); + $result->setCoverage($coverage); + $result->setUserData($user_data); + + $results[] = $result; + } + + return $results; + } + + /** + * Read the coverage from phpunit generated clover report + * + * @param string $path Path to report + * + * @return array + */ + private function readCoverage($path, $affected_tests) { + $test_results = Filesystem::readFile($path); + if (empty($test_results)) { + throw new Exception('Clover coverage XML report file is empty, ' + . 'it probably means that phpunit failed to run tests. ' + . 'Try running arc unit with --trace option and then run ' + . 'generated phpunit command yourself, you might get the ' + . 'answer.' + ); + } + + $coverage_dom = new DOMDocument(); + $coverage_dom->loadXML($test_results); + + $reports = array(); + $files = $coverage_dom->getElementsByTagName('file'); + + foreach ($files as $file) { + $class_path = $file->getAttribute('name'); + if (empty($affected_tests[$class_path])) { + continue; + } + $test_path = $affected_tests[$file->getAttribute('name')]; + // get total line count in file + $line_count = count(file($class_path)); + + $coverage = ''; + $start_line = 1; + $lines = $file->getElementsByTagName('line'); + for ($ii = 0; $ii < $lines->length; $ii++) { + $line = $lines->item($ii); + for (; $start_line < $line->getAttribute('num'); $start_line++) { + $coverage .= 'N'; + } + + if ($line->getAttribute('type') != 'stmt') { + $coverage .= 'N'; + } else { + if ((int) $line->getAttribute('count') == 0) { + $coverage .= 'U'; + } else if ((int) $line->getAttribute('count') > 0) { + $coverage .= 'C'; + } + } + + $start_line++; + } + + for (; $start_line <= $line_count; $start_line++) { + $coverage .= 'N'; + } + + $len = strlen($this->projectRoot . DIRECTORY_SEPARATOR); + $class_path = substr($class_path, $len); + $reports[$class_path] = $coverage; + } + + return $reports; + } + + /** + * We need this non-sense to make json generated by phpunit + * valid. + * + * @param string $json_tmp Path to JSON report + * + * @return array JSON decoded array + */ + private function getJsonReport($json_tmp) { + $json = Filesystem::readFile($json_tmp); + + if (empty($json)) { + throw new Exception('JSON report file is empty, ' + . 'it probably means that phpunit failed to run tests. ' + . 'Try running arc unit with --trace option and then run ' + . 'generated phpunit command yourself, you might get the ' + . 'answer.' + ); + } + + $json = preg_replace('/}{\s*"/', '},{"', $json); + $json = '[' . $json . ']'; + $json = json_decode($json); + if (!is_array($json)) { + throw new Exception('JSON could not be decoded'); + } + + return $json; + } +} diff --git a/src/unit/engine/PhpunitTestEngine.php b/src/unit/engine/PhpunitTestEngine.php index d2f02a8b..a7bb43d5 100644 --- a/src/unit/engine/PhpunitTestEngine.php +++ b/src/unit/engine/PhpunitTestEngine.php @@ -90,36 +90,6 @@ final class PhpunitTestEngine extends ArcanistBaseUnitTestEngine { return array_mergev($results); } - /** - * We need this non-sense to make json generated by phpunit - * valid. - * - * @param string $json_tmp Path to JSON report - * - * @return array JSON decoded array - */ - private function getJsonReport($json_tmp) { - $json = Filesystem::readFile($json_tmp); - - if (empty($json)) { - throw new Exception('JSON report file is empty, ' - . 'it probably means that phpunit failed to run tests. ' - . 'Try running arc unit with --trace option and then run ' - . 'generated phpunit command yourself, you might get the ' - . 'answer.' - ); - } - - $json = preg_replace('/}{\s*"/', '},{"', $json); - $json = '[' . $json . ']'; - $json = json_decode($json); - if (!is_array($json)) { - throw new Exception('JSON could not be decoded'); - } - - return $json; - } - /** * Parse test results from phpunit json report * @@ -130,127 +100,10 @@ final class PhpunitTestEngine extends ArcanistBaseUnitTestEngine { * @return array */ private function parseTestResults($path, $json_tmp, $clover_tmp) { - $test_results = Filesystem::readFile($json_tmp); - - $report = $this->getJsonReport($json_tmp); - - // coverage is for all testcases in the executed $path - $coverage = array(); - if ($this->getEnableCoverage() !== false) { - $coverage = $this->readCoverage($clover_tmp); - } - - $results = array(); - foreach ($report as $event) { - if ('test' != $event->event) { - continue; - } - - $status = ArcanistUnitTestResult::RESULT_PASS; - $user_data = ''; - - if ('fail' == $event->status) { - $status = ArcanistUnitTestResult::RESULT_FAIL; - $user_data .= $event->message . "\n"; - foreach ($event->trace as $trace) { - $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); - } - } else if ('error' == $event->status) { - if ('Skipped Test' == $event->message) { - $status = ArcanistUnitTestResult::RESULT_SKIP; - $user_data .= $event->message; - } else if ('Incomplete Test' == $event->message) { - $status = ArcanistUnitTestResult::RESULT_SKIP; - $user_data .= $event->message; - } else { - $status = ArcanistUnitTestResult::RESULT_BROKEN; - $user_data .= $event->message; - foreach ($event->trace as $trace) { - $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); - } - } - } - - $name = preg_replace('/ \(.*\)/', '', $event->test); - - $result = new ArcanistUnitTestResult(); - $result->setName($name); - $result->setResult($status); - $result->setDuration($event->time); - $result->setCoverage($coverage); - $result->setUserData($user_data); - - $results[] = $result; - } - - return $results; - } - - /** - * Red the coverage from phpunit generated clover report - * - * @param string $path Path to report - * - * @return array - */ - private function readCoverage($path) { - $test_results = Filesystem::readFile($path); - if (empty($test_results)) { - throw new Exception('Clover coverage XML report file is empty, ' - . 'it probably means that phpunit failed to run tests. ' - . 'Try running arc unit with --trace option and then run ' - . 'generated phpunit command yourself, you might get the ' - . 'answer.' - ); - } - - $coverage_dom = new DOMDocument(); - $coverage_dom->loadXML($test_results); - - $reports = array(); - $files = $coverage_dom->getElementsByTagName('file'); - - foreach ($files as $file) { - $class_path = $file->getAttribute('name'); - if (empty($this->affectedTests[$class_path])) { - continue; - } - $test_path = $this->affectedTests[$file->getAttribute('name')]; - // get total line count in file - $line_count = count(file($class_path)); - - $coverage = ''; - $start_line = 1; - $lines = $file->getElementsByTagName('line'); - for ($ii = 0; $ii < $lines->length; $ii++) { - $line = $lines->item($ii); - for (; $start_line < $line->getAttribute('num'); $start_line++) { - $coverage .= 'N'; - } - - if ($line->getAttribute('type') != 'stmt') { - $coverage .= 'N'; - } else { - if ((int) $line->getAttribute('count') == 0) { - $coverage .= 'U'; - } else if ((int) $line->getAttribute('count') > 0) { - $coverage .= 'C'; - } - } - - $start_line++; - } - - for (; $start_line <= $line_count; $start_line++) { - $coverage .= 'N'; - } - - $len = strlen($this->projectRoot . DIRECTORY_SEPARATOR); - $class_path = substr($class_path, $len); - $reports[$class_path] = $coverage; - } - - return $reports; + return id(new PhpunitResultParser()) + ->setEnableCoverage($this->getEnableCoverage()) + ->setProjectRoot($this->projectRoot) + ->parseTestResults($path, $json_tmp, $clover_tmp, $this->affectedTests); }