mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-03-26 11:10:15 +01:00
Summary: Clover reports generated from PHPUnit sometimes give false positives of lines that were not covered by a test that should have actually been not coverable, apparently due to inaccurate static analysis of files that weren't actually executed. This filters coverage reports of files that show no coverage which avoids these false positives. Fixes T10420. Test Plan: Looked at coverage reports of files before and after the change Before: {F1124115} After: {F1124113} Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, aurelijus Maniphest Tasks: T10420 Differential Revision: https://secure.phabricator.com/D15343
188 lines
5.4 KiB
PHP
188 lines
5.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* PHPUnit Result Parsing utility
|
|
*
|
|
* For an example on how to integrate with your test engine, see
|
|
* @{class:PhpunitTestEngine}.
|
|
*/
|
|
final class ArcanistPhpunitTestResultParser extends ArcanistTestResultParser {
|
|
|
|
/**
|
|
* Parse test results from phpunit json report
|
|
*
|
|
* @param string $path Path to test
|
|
* @param string $test_results String containing phpunit json report
|
|
*
|
|
* @return array
|
|
*/
|
|
public function parseTestResults($path, $test_results) {
|
|
if (!$test_results) {
|
|
$result = id(new ArcanistUnitTestResult())
|
|
->setName($path)
|
|
->setUserData($this->stderr)
|
|
->setResult(ArcanistUnitTestResult::RESULT_BROKEN);
|
|
return array($result);
|
|
}
|
|
|
|
$report = $this->getJsonReport($test_results);
|
|
|
|
// coverage is for all testcases in the executed $path
|
|
$coverage = array();
|
|
if ($this->enableCoverage !== false) {
|
|
$coverage = $this->readCoverage();
|
|
}
|
|
|
|
$last_test_finished = true;
|
|
|
|
$results = array();
|
|
foreach ($report as $event) {
|
|
switch (idx($event, 'event')) {
|
|
case 'test':
|
|
break;
|
|
case 'testStart':
|
|
$last_test_finished = false;
|
|
// fall through
|
|
default:
|
|
continue 2; // switch + loop
|
|
}
|
|
|
|
$status = ArcanistUnitTestResult::RESULT_PASS;
|
|
$user_data = '';
|
|
|
|
if ('fail' == idx($event, 'status')) {
|
|
$status = ArcanistUnitTestResult::RESULT_FAIL;
|
|
$user_data .= idx($event, 'message')."\n";
|
|
foreach (idx($event, 'trace') as $trace) {
|
|
$user_data .= sprintf(
|
|
"\n%s:%s",
|
|
idx($trace, 'file'),
|
|
idx($trace, 'line'));
|
|
}
|
|
} else if ('error' == idx($event, 'status')) {
|
|
if (strpos(idx($event, 'message'), 'Skipped Test') !== false) {
|
|
$status = ArcanistUnitTestResult::RESULT_SKIP;
|
|
$user_data .= idx($event, 'message');
|
|
} else if (strpos(
|
|
idx($event, 'message'),
|
|
'Incomplete Test') !== false) {
|
|
$status = ArcanistUnitTestResult::RESULT_SKIP;
|
|
$user_data .= idx($event, 'message');
|
|
} else {
|
|
$status = ArcanistUnitTestResult::RESULT_BROKEN;
|
|
$user_data .= idx($event, 'message');
|
|
foreach (idx($event, 'trace') as $trace) {
|
|
$user_data .= sprintf(
|
|
"\n%s:%s",
|
|
idx($trace, 'file'),
|
|
idx($trace, 'line'));
|
|
}
|
|
}
|
|
}
|
|
|
|
$name = preg_replace('/ \(.*\)/s', '', idx($event, 'test'));
|
|
|
|
$result = new ArcanistUnitTestResult();
|
|
$result->setName($name);
|
|
$result->setResult($status);
|
|
$result->setDuration(idx($event, 'time'));
|
|
$result->setCoverage($coverage);
|
|
$result->setUserData($user_data);
|
|
|
|
$results[] = $result;
|
|
$last_test_finished = true;
|
|
}
|
|
|
|
if (!$last_test_finished) {
|
|
$results[] = id(new ArcanistUnitTestResult())
|
|
->setName(idx($event, 'test')) // use last event
|
|
->setUserData($this->stderr)
|
|
->setResult(ArcanistUnitTestResult::RESULT_BROKEN);
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Read the coverage from phpunit generated clover report
|
|
*
|
|
* @return array
|
|
*/
|
|
private function readCoverage() {
|
|
$test_results = Filesystem::readFile($this->coverageFile);
|
|
if (empty($test_results)) {
|
|
return array();
|
|
}
|
|
|
|
$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 = '';
|
|
$any_line_covered = false;
|
|
$start_line = 1;
|
|
$lines = $file->getElementsByTagName('line');
|
|
|
|
$coverage = str_repeat('N', $line_count);
|
|
foreach ($lines as $line) {
|
|
if ($line->getAttribute('type') != 'stmt') {
|
|
continue;
|
|
}
|
|
if ((int)$line->getAttribute('count') > 0) {
|
|
$is_covered = 'C';
|
|
$any_line_covered = true;
|
|
} else {
|
|
$is_covered = 'U';
|
|
}
|
|
$line_no = (int)$line->getAttribute('num');
|
|
$coverage[$line_no - 1] = $is_covered;
|
|
}
|
|
|
|
// Sometimes the Clover coverage gives false positives on uncovered lines
|
|
// when the file wasn't actually part of the test. This filters out files
|
|
// with no coverage which helps give more accurate overall results.
|
|
if ($any_line_covered) {
|
|
$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 String containing JSON report
|
|
* @return array JSON decoded array
|
|
*/
|
|
private function getJsonReport($json) {
|
|
|
|
if (empty($json)) {
|
|
throw new Exception(
|
|
pht(
|
|
'JSON report file is empty, it probably means that phpunit '.
|
|
'failed to run tests. Try running %s with %s option and then run '.
|
|
'generated phpunit command yourself, you might get the answer.',
|
|
'arc unit',
|
|
'--trace'));
|
|
}
|
|
|
|
$json = preg_replace('/}{\s*"/', '},{"', $json);
|
|
$json = '['.$json.']';
|
|
return phutil_json_decode($json);
|
|
}
|
|
|
|
}
|