diff --git a/src/unit/engine/PytestTestEngine.php b/src/unit/engine/PytestTestEngine.php index b104d8fc..8d3891b9 100644 --- a/src/unit/engine/PytestTestEngine.php +++ b/src/unit/engine/PytestTestEngine.php @@ -1,135 +1,144 @@ getWorkingCopy(); $this->project_root = $working_copy->getProjectRoot(); $junit_tmp = new TempFile(); $cover_tmp = new TempFile(); $future = $this->buildTestFuture($junit_tmp, $cover_tmp); - $future->resolvex(); + list($err, $stdout, $stderr) = $future->resolve(); + + if (!Filesystem::pathExists($junit_tmp)) { + throw new CommandException( + "Command failed with error #{$err}!", + $future->getCommand(), + $err, + $stdout, + $stderr); + } $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 --junit-xml=%s', $junit_tmp); 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)); 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; } }