mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-14 19:02:40 +01:00
ef18ae08eb
Summary: Ref T5655. It is superfluous to include "base" in the name of an abstract base class. Furthermore, it is not done consistently within the code base. In order to retain compatibility with external code, I have kept the `ArcanistBaseWorkflow` class (which trivially extends from `ArcanistWorkflow`), but it is now deprecated and should output a warning message. Similarly for `ArcanistBaseUnitTestEngine`. Test Plan: Created a workflow which extends from `ArcanistBaseWorkflow`. Executed the workflow and saw a deprecation warning. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, Korvin, aurelijus Maniphest Tasks: T5655 Differential Revision: https://secure.phabricator.com/D9983
162 lines
4.4 KiB
PHP
162 lines
4.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Very basic 'nose' unit test engine wrapper.
|
|
*
|
|
* Requires nose 1.1.3 for code coverage.
|
|
*/
|
|
final class NoseTestEngine extends ArcanistUnitTestEngine {
|
|
|
|
public function run() {
|
|
$paths = $this->getPaths();
|
|
|
|
$affected_tests = array();
|
|
foreach ($paths as $path) {
|
|
$absolute_path = Filesystem::resolvePath($path);
|
|
|
|
if (is_dir($absolute_path)) {
|
|
$absolute_test_path = Filesystem::resolvePath('tests/'.$path);
|
|
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
|
|
$relative_test_path = 'tests/'.$directory.'/test_'.$filename;
|
|
$absolute_test_path = Filesystem::resolvePath($relative_test_path);
|
|
|
|
if (is_readable($absolute_test_path)) {
|
|
$affected_tests[] = $absolute_test_path;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->runTests($affected_tests, './');
|
|
}
|
|
|
|
public function runTests($test_paths, $source_path) {
|
|
if (empty($test_paths)) {
|
|
return array();
|
|
}
|
|
|
|
$futures = array();
|
|
$tmpfiles = array();
|
|
foreach ($test_paths as $test_path) {
|
|
$xunit_tmp = new TempFile();
|
|
$cover_tmp = new TempFile();
|
|
|
|
$future = $this->buildTestFuture($test_path, $xunit_tmp, $cover_tmp);
|
|
|
|
$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'];
|
|
|
|
$this->parser = new ArcanistXUnitTestResultParser();
|
|
$results[] = $this->parseTestResults($source_path,
|
|
$xunit_tmp,
|
|
$cover_tmp);
|
|
}
|
|
|
|
return array_mergev($results);
|
|
}
|
|
|
|
public function buildTestFuture($path, $xunit_tmp, $cover_tmp) {
|
|
$cmd_line = csprintf('nosetests --with-xunit --xunit-file=%s',
|
|
$xunit_tmp);
|
|
|
|
if ($this->getEnableCoverage() !== false) {
|
|
$cmd_line .= csprintf(
|
|
' --with-coverage --cover-xml --cover-xml-file=%s',
|
|
$cover_tmp);
|
|
}
|
|
|
|
return new ExecFuture('%C %s', $cmd_line, $path);
|
|
}
|
|
|
|
public function parseTestResults($source_path, $xunit_tmp, $cover_tmp) {
|
|
$results = $this->parser->parseTestResults(
|
|
Filesystem::readFile($xunit_tmp));
|
|
|
|
// coverage is for all testcases in the executed $path
|
|
if ($this->getEnableCoverage() !== false) {
|
|
$coverage = $this->readCoverage($cover_tmp, $source_path);
|
|
foreach ($results as $result) {
|
|
$result->setCoverage($coverage);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
public function readCoverage($cover_file, $source_path) {
|
|
$coverage_dom = new DOMDocument();
|
|
$coverage_dom->loadXML(Filesystem::readFile($cover_file));
|
|
|
|
$reports = array();
|
|
$classes = $coverage_dom->getElementsByTagName('class');
|
|
|
|
foreach ($classes as $class) {
|
|
$path = $class->getAttribute('filename');
|
|
$root = $this->getWorkingCopy()->getProjectRoot();
|
|
|
|
if (!Filesystem::isDescendant($path, $root)) {
|
|
continue;
|
|
}
|
|
|
|
// get total line count in file
|
|
$line_count = count(phutil_split_lines(Filesystem::readFile($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[$path] = $coverage;
|
|
}
|
|
|
|
return $reports;
|
|
}
|
|
|
|
}
|