Changeset View
Changeset View
Standalone View
Standalone View
src/unit/engine/NoseTestEngine.php
- This file was copied to src/unit/engine/ArcanistNoseTestEngine.php.
| <?php | <?php | ||||
| /** | /** | ||||
| * Very basic 'nose' unit test engine wrapper. | * @deprecated | ||||
| * | |||||
| * Requires nose 1.1.3 for code coverage. | |||||
| */ | */ | ||||
| final class NoseTestEngine extends ArcanistUnitTestEngine { | final class NoseTestEngine extends ArcanistNoseTestEngine { | ||||
| public function run() { | public function run() { | ||||
| $paths = $this->getPaths(); | phutil_deprecated( | ||||
| __CLASS__, | |||||
| $affected_tests = array(); | pht( | ||||
| foreach ($paths as $path) { | 'You should use `%s` instead.', | ||||
| $absolute_path = Filesystem::resolvePath($path); | 'ArcanistNoseTestEngine')); | ||||
| parent::run(); | |||||
| 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(); | |||||
| $futures = id(new FutureIterator($futures)) | |||||
| ->limit(4); | |||||
| foreach ($futures 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; | |||||
| } | } | ||||
| } | } | ||||