mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-22 14:52:40 +01:00
Add coverage reports to py.test runner
Summary: Of note this now forces the pytest-cov plugin to be installed unless they turn it off. Test Plan: ``` ../arcanist/bin/arc unit COVERAGE REPORT 97% changes/models/test.py 100% tests/changes/api/serializer/models/test_testcase.py 100% changes/api/serializer/models/testcase.py UNIT OKAY No unit test failures. Updated an existing Differential revision: Revision URI: https://tails.corp.dropbox.com/D43387 Included changes: M changes/api/serializer/models/testcase.py M changes/models/test.py M tests/changes/api/serializer/models/test_testcase.py ``` Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D8667
This commit is contained in:
parent
11e2c1688f
commit
b8e4261455
1 changed files with 118 additions and 6 deletions
|
@ -8,16 +8,128 @@
|
|||
final class PytestTestEngine extends ArcanistBaseUnitTestEngine {
|
||||
|
||||
public function run() {
|
||||
$working_copy = $this->getWorkingCopy();
|
||||
$this->project_root = $working_copy->getProjectRoot();
|
||||
|
||||
$junit_tmp = new TempFile();
|
||||
$cover_tmp = new TempFile();
|
||||
|
||||
$cmd_line = csprintf('py.test --junitxml=%s',
|
||||
$future = $this->buildTestFuture($junit_tmp, $cover_tmp);
|
||||
$future->resolvex();
|
||||
|
||||
$future = new ExecFuture('coverage xml -o %s', $cover_tmp);
|
||||
$future->setCWD($this->project_root);
|
||||
$future->resolvex();
|
||||
|
||||
return $this->parseTestResults($junit_tmp, $cover_tmp);
|
||||
}
|
||||
|
||||
public function buildTestFuture($junit_tmp, $cover_tmp) {
|
||||
$paths = $this->getPaths();
|
||||
|
||||
$cmd_line = csprintf('py.test --junitxml %s',
|
||||
$junit_tmp);
|
||||
$future = new ExecFuture('%C', $cmd_line);
|
||||
list($stdout, $stderr) = $future->resolvex();
|
||||
|
||||
if ($this->getEnableCoverage() !== false) {
|
||||
$cmd_line = csprintf('coverage run --source %s -m %C',
|
||||
$this->project_root, $cmd_line);
|
||||
}
|
||||
|
||||
return new ExecFuture('%C', $cmd_line);
|
||||
}
|
||||
|
||||
public function parseTestResults($junit_tmp, $cover_tmp) {
|
||||
$parser = new ArcanistXUnitTestResultParser();
|
||||
$results = $parser->parseTestResults(
|
||||
Filesystem::readFile($junit_tmp));
|
||||
|
||||
return $parser->parseTestResults(Filesystem::readFile($junit_tmp));
|
||||
if ($this->getEnableCoverage() !== false) {
|
||||
$coverage_report = $this->readCoverage($cover_tmp);
|
||||
foreach ($results as $result) {
|
||||
$result->setCoverage($coverage_report);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function readCoverage($path) {
|
||||
$coverage_data = Filesystem::readFile($path);
|
||||
if (empty($coverage_data)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$coverage_dom = new DOMDocument();
|
||||
$coverage_dom->loadXML($coverage_data);
|
||||
|
||||
$paths = $this->getPaths();
|
||||
$reports = array();
|
||||
$classes = $coverage_dom->getElementsByTagName("class");
|
||||
|
||||
foreach ($classes as $class) {
|
||||
// filename is actually python module path with ".py" at the end,
|
||||
// e.g.: tornado.web.py
|
||||
$relative_path = explode(".", $class->getAttribute("filename"));
|
||||
array_pop($relative_path);
|
||||
$relative_path = implode("/", $relative_path);
|
||||
|
||||
// first we check if the path is a directory (a Python package), if it is
|
||||
// set relative and absolute paths to have __init__.py at the end.
|
||||
$absolute_path = Filesystem::resolvePath($relative_path);
|
||||
if (is_dir($absolute_path)) {
|
||||
$relative_path .= "/__init__.py";
|
||||
$absolute_path .= "/__init__.py";
|
||||
}
|
||||
|
||||
// then we check if the path with ".py" at the end is file (a Python
|
||||
// submodule), if it is - set relative and absolute paths to have
|
||||
// ".py" at the end.
|
||||
if (is_file($absolute_path.".py")) {
|
||||
$relative_path .= ".py";
|
||||
$absolute_path .= ".py";
|
||||
}
|
||||
|
||||
if (!file_exists($absolute_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($relative_path, $paths)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get total line count in file
|
||||
$line_count = count(file($absolute_path));
|
||||
|
||||
$coverage = "";
|
||||
$start_line = 1;
|
||||
$lines = $class->getElementsByTagName("line");
|
||||
for ($ii = 0; $ii < $lines->length; $ii++) {
|
||||
$line = $lines->item($ii);
|
||||
|
||||
$next_line = intval($line->getAttribute("number"));
|
||||
for ($start_line; $start_line < $next_line; $start_line++) {
|
||||
$coverage .= "N";
|
||||
}
|
||||
|
||||
if (intval($line->getAttribute("hits")) == 0) {
|
||||
$coverage .= "U";
|
||||
}
|
||||
else if (intval($line->getAttribute("hits")) > 0) {
|
||||
$coverage .= "C";
|
||||
}
|
||||
|
||||
$start_line++;
|
||||
}
|
||||
|
||||
if ($start_line < $line_count) {
|
||||
foreach (range($start_line, $line_count) as $line_num) {
|
||||
$coverage .= "N";
|
||||
}
|
||||
}
|
||||
|
||||
$reports[$relative_path] = $coverage;
|
||||
}
|
||||
|
||||
return $reports;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue