2012-04-27 21:11:41 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Very basic 'nose' unit test engine wrapper.
|
|
|
|
*
|
|
|
|
* Requires nose 1.1.3 for code coverage.
|
|
|
|
*/
|
|
|
|
final class NoseTestEngine extends ArcanistBaseUnitTestEngine {
|
|
|
|
|
|
|
|
public function run() {
|
|
|
|
$paths = $this->getPaths();
|
|
|
|
|
|
|
|
$affected_tests = array();
|
|
|
|
foreach ($paths as $path) {
|
|
|
|
$absolute_path = Filesystem::resolvePath($path);
|
|
|
|
|
|
|
|
if (is_dir($absolute_path)) {
|
2014-05-23 22:53:05 +02:00
|
|
|
$absolute_test_path = Filesystem::resolvePath('tests/'.$path);
|
2012-04-27 21:11:41 +02:00
|
|
|
if (is_readable($absolute_test_path)) {
|
|
|
|
$affected_tests[] = $absolute_test_path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_readable($absolute_path)) {
|
|
|
|
$filename = basename($path);
|
|
|
|
$directory = dirname($path);
|
|
|
|
|
|
|
|
// assumes directory layout: tests/<package>/test_<module>.py
|
2014-05-23 22:53:05 +02:00
|
|
|
$relative_test_path = 'tests/'.$directory.'/test_'.$filename;
|
2012-04-27 21:11:41 +02:00
|
|
|
$absolute_test_path = Filesystem::resolvePath($relative_test_path);
|
|
|
|
|
|
|
|
if (is_readable($absolute_test_path)) {
|
|
|
|
$affected_tests[] = $absolute_test_path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-14 15:38:22 +02:00
|
|
|
return $this->runTests($affected_tests, './');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function runTests($test_paths, $source_path) {
|
|
|
|
if (empty($test_paths)) {
|
2012-04-27 21:11:41 +02:00
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$futures = array();
|
|
|
|
$tmpfiles = array();
|
2013-05-14 15:38:22 +02:00
|
|
|
foreach ($test_paths as $test_path) {
|
2012-04-27 21:11:41 +02:00
|
|
|
$xunit_tmp = new TempFile();
|
|
|
|
$cover_tmp = new TempFile();
|
|
|
|
|
2014-07-09 01:12:13 +02:00
|
|
|
$future = $this->buildTestFuture($test_path, $xunit_tmp, $cover_tmp);
|
2012-04-27 21:11:41 +02:00
|
|
|
|
|
|
|
$futures[$test_path] = $future;
|
|
|
|
$tmpfiles[$test_path] = array(
|
|
|
|
'xunit' => $xunit_tmp,
|
|
|
|
'cover' => $cover_tmp,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
foreach (Futures($futures)->limit(4) as $test_path => $future) {
|
|
|
|
try {
|
|
|
|
list($stdout, $stderr) = $future->resolvex();
|
|
|
|
} catch(CommandException $exc) {
|
|
|
|
if ($exc->getError() > 1) {
|
|
|
|
// 'nose' returns 1 when tests are failing/broken.
|
|
|
|
throw $exc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$xunit_tmp = $tmpfiles[$test_path]['xunit'];
|
|
|
|
$cover_tmp = $tmpfiles[$test_path]['cover'];
|
|
|
|
|
2013-09-30 19:43:08 +02:00
|
|
|
$this->parser = new ArcanistXUnitTestResultParser();
|
2013-05-14 15:38:22 +02:00
|
|
|
$results[] = $this->parseTestResults($source_path,
|
2012-04-27 21:11:41 +02:00
|
|
|
$xunit_tmp,
|
|
|
|
$cover_tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_mergev($results);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function buildTestFuture($path, $xunit_tmp, $cover_tmp) {
|
2014-05-23 22:53:05 +02:00
|
|
|
$cmd_line = csprintf('nosetests --with-xunit --xunit-file=%s',
|
2012-04-27 21:11:41 +02:00
|
|
|
$xunit_tmp);
|
|
|
|
|
|
|
|
if ($this->getEnableCoverage() !== false) {
|
2014-07-09 01:12:13 +02:00
|
|
|
$cmd_line .= csprintf(
|
|
|
|
' --with-coverage --cover-xml --cover-xml-file=%s',
|
|
|
|
$cover_tmp);
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
|
2014-05-23 22:53:05 +02:00
|
|
|
return new ExecFuture('%C %s', $cmd_line, $path);
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
|
2013-05-14 15:38:22 +02:00
|
|
|
public function parseTestResults($source_path, $xunit_tmp, $cover_tmp) {
|
2013-09-30 19:43:08 +02:00
|
|
|
$results = $this->parser->parseTestResults(
|
|
|
|
Filesystem::readFile($xunit_tmp));
|
2012-04-27 21:11:41 +02:00
|
|
|
|
|
|
|
// coverage is for all testcases in the executed $path
|
|
|
|
if ($this->getEnableCoverage() !== false) {
|
2013-05-14 15:38:22 +02:00
|
|
|
$coverage = $this->readCoverage($cover_tmp, $source_path);
|
2013-09-30 19:43:08 +02:00
|
|
|
foreach ($results as $result) {
|
|
|
|
$result->setCoverage($coverage);
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
2013-05-14 15:38:22 +02:00
|
|
|
public function readCoverage($cover_file, $source_path) {
|
2012-04-27 21:11:41 +02:00
|
|
|
$coverage_dom = new DOMDocument();
|
2013-05-14 15:38:22 +02:00
|
|
|
$coverage_dom->loadXML(Filesystem::readFile($cover_file));
|
2012-04-27 21:11:41 +02:00
|
|
|
|
|
|
|
$reports = array();
|
2014-05-23 22:53:05 +02:00
|
|
|
$classes = $coverage_dom->getElementsByTagName('class');
|
2012-04-27 21:11:41 +02:00
|
|
|
|
|
|
|
foreach ($classes as $class) {
|
2014-05-23 22:53:05 +02:00
|
|
|
$path = $class->getAttribute('filename');
|
Fix unit test coverage for `NoseTestEngine`.
Summary:
I noticed that code coverage wasn't showing in Differential for
some repositories that we are using with Phabricator.
`arc unit` would should unit test coverage, but the paths were messy
(for example, `.//foo/bar.py` instead of `foo/bar.py`). As a result,
the code coverage info wasn't recognised as being for the correct
module.
I'm not sure why this logic is the way that it is... perhaps this is to
do with an older version of `nose` (I am using v1.3.0).
Test Plan:
I created a diff for an internal repository that we have, and
observed that code coverage was displayed in Differential.
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, seporaitis, avive, dctrwatson, zeeg
Differential Revision: https://secure.phabricator.com/D8433
2014-03-07 19:16:27 +01:00
|
|
|
$root = $this->getWorkingCopy()->getProjectRoot();
|
2012-04-27 21:11:41 +02:00
|
|
|
|
Fix unit test coverage for `NoseTestEngine`.
Summary:
I noticed that code coverage wasn't showing in Differential for
some repositories that we are using with Phabricator.
`arc unit` would should unit test coverage, but the paths were messy
(for example, `.//foo/bar.py` instead of `foo/bar.py`). As a result,
the code coverage info wasn't recognised as being for the correct
module.
I'm not sure why this logic is the way that it is... perhaps this is to
do with an older version of `nose` (I am using v1.3.0).
Test Plan:
I created a diff for an internal repository that we have, and
observed that code coverage was displayed in Differential.
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, seporaitis, avive, dctrwatson, zeeg
Differential Revision: https://secure.phabricator.com/D8433
2014-03-07 19:16:27 +01:00
|
|
|
if (!Filesystem::isDescendant($path, $root)) {
|
2012-04-27 21:11:41 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get total line count in file
|
Fix unit test coverage for `NoseTestEngine`.
Summary:
I noticed that code coverage wasn't showing in Differential for
some repositories that we are using with Phabricator.
`arc unit` would should unit test coverage, but the paths were messy
(for example, `.//foo/bar.py` instead of `foo/bar.py`). As a result,
the code coverage info wasn't recognised as being for the correct
module.
I'm not sure why this logic is the way that it is... perhaps this is to
do with an older version of `nose` (I am using v1.3.0).
Test Plan:
I created a diff for an internal repository that we have, and
observed that code coverage was displayed in Differential.
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, seporaitis, avive, dctrwatson, zeeg
Differential Revision: https://secure.phabricator.com/D8433
2014-03-07 19:16:27 +01:00
|
|
|
$line_count = count(phutil_split_lines(Filesystem::readFile($path)));
|
2012-04-27 21:11:41 +02:00
|
|
|
|
2014-05-23 22:53:05 +02:00
|
|
|
$coverage = '';
|
2012-04-27 21:11:41 +02:00
|
|
|
$start_line = 1;
|
2014-05-23 22:53:05 +02:00
|
|
|
$lines = $class->getElementsByTagName('line');
|
2012-04-27 21:11:41 +02:00
|
|
|
for ($ii = 0; $ii < $lines->length; $ii++) {
|
|
|
|
$line = $lines->item($ii);
|
|
|
|
|
2014-05-23 22:53:05 +02:00
|
|
|
$next_line = intval($line->getAttribute('number'));
|
2012-04-27 21:11:41 +02:00
|
|
|
for ($start_line; $start_line < $next_line; $start_line++) {
|
2014-05-23 22:53:05 +02:00
|
|
|
$coverage .= 'N';
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
|
2014-05-23 22:53:05 +02:00
|
|
|
if (intval($line->getAttribute('hits')) == 0) {
|
|
|
|
$coverage .= 'U';
|
|
|
|
} else if (intval($line->getAttribute('hits')) > 0) {
|
|
|
|
$coverage .= 'C';
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$start_line++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($start_line < $line_count) {
|
|
|
|
foreach (range($start_line, $line_count) as $line_num) {
|
2014-05-23 22:53:05 +02:00
|
|
|
$coverage .= 'N';
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix unit test coverage for `NoseTestEngine`.
Summary:
I noticed that code coverage wasn't showing in Differential for
some repositories that we are using with Phabricator.
`arc unit` would should unit test coverage, but the paths were messy
(for example, `.//foo/bar.py` instead of `foo/bar.py`). As a result,
the code coverage info wasn't recognised as being for the correct
module.
I'm not sure why this logic is the way that it is... perhaps this is to
do with an older version of `nose` (I am using v1.3.0).
Test Plan:
I created a diff for an internal repository that we have, and
observed that code coverage was displayed in Differential.
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley
CC: Korvin, epriestley, aran, seporaitis, avive, dctrwatson, zeeg
Differential Revision: https://secure.phabricator.com/D8433
2014-03-07 19:16:27 +01:00
|
|
|
$reports[$path] = $coverage;
|
2012-04-27 21:11:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $reports;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|