4
* Very basic 'py.test' unit test engine wrapper.
6
final class PytestTestEngine extends ArcanistUnitTestEngine {
8
public function run() {
9
$working_copy = $this->getWorkingCopy();
10
$this->project_root = $working_copy->getProjectRoot();
12
$junit_tmp = new TempFile();
13
$cover_tmp = new TempFile();
15
$future = $this->buildTestFuture($junit_tmp, $cover_tmp);
18
$future = new ExecFuture('coverage xml -o %s', $cover_tmp);
19
$future->setCWD($this->project_root);
22
return $this->parseTestResults($junit_tmp, $cover_tmp);
25
public function buildTestFuture($junit_tmp, $cover_tmp) {
26
$paths = $this->getPaths();
28
$cmd_line = csprintf('py.test --junit-xml=%s', $junit_tmp);
30
if ($this->getEnableCoverage() !== false) {
32
'coverage run --source %s -m %C',
37
return new ExecFuture('%C', $cmd_line);
40
public function parseTestResults($junit_tmp, $cover_tmp) {
41
$parser = new ArcanistXUnitTestResultParser();
42
$results = $parser->parseTestResults(
43
Filesystem::readFile($junit_tmp));
45
if ($this->getEnableCoverage() !== false) {
46
$coverage_report = $this->readCoverage($cover_tmp);
47
foreach ($results as $result) {
48
$result->setCoverage($coverage_report);
55
public function readCoverage($path) {
56
$coverage_data = Filesystem::readFile($path);
57
if (empty($coverage_data)) {
61
$coverage_dom = new DOMDocument();
62
$coverage_dom->loadXML($coverage_data);
64
$paths = $this->getPaths();
66
$classes = $coverage_dom->getElementsByTagName('class');
68
foreach ($classes as $class) {
69
// filename is actually python module path with ".py" at the end,
70
// e.g.: tornado.web.py
71
$relative_path = explode('.', $class->getAttribute('filename'));
72
array_pop($relative_path);
73
$relative_path = implode('/', $relative_path);
75
// first we check if the path is a directory (a Python package), if it is
76
// set relative and absolute paths to have __init__.py at the end.
77
$absolute_path = Filesystem::resolvePath($relative_path);
78
if (is_dir($absolute_path)) {
79
$relative_path .= '/__init__.py';
80
$absolute_path .= '/__init__.py';
83
// then we check if the path with ".py" at the end is file (a Python
84
// submodule), if it is - set relative and absolute paths to have
86
if (is_file($absolute_path.'.py')) {
87
$relative_path .= '.py';
88
$absolute_path .= '.py';
91
if (!file_exists($absolute_path)) {
95
if (!in_array($relative_path, $paths)) {
99
// get total line count in file
100
$line_count = count(file($absolute_path));
104
$lines = $class->getElementsByTagName('line');
105
for ($ii = 0; $ii < $lines->length; $ii++) {
106
$line = $lines->item($ii);
108
$next_line = intval($line->getAttribute('number'));
109
for ($start_line; $start_line < $next_line; $start_line++) {
113
if (intval($line->getAttribute('hits')) == 0) {
116
else if (intval($line->getAttribute('hits')) > 0) {
123
if ($start_line < $line_count) {
124
foreach (range($start_line, $line_count) as $line_num) {
129
$reports[$relative_path] = $coverage;