1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-03-26 11:10:15 +01:00
phorge-arcanist/src/unit/parser/ArcanistPhpunitTestResultParser.php
Eric Stern 086f5399bf Filter out some Clover coverage to prevent false positives
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
2016-02-24 12:58:07 -08:00

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);
}
}