~ubuntu-branches/ubuntu/wily/phabricator/wily

« back to all changes in this revision

Viewing changes to src/unit/engine/NoseTestEngine.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-11-01 23:20:06 UTC
  • mto: This revision was merged to the branch mainline in revision 4.
  • Revision ID: package-import@ubuntu.com-20141101232006-mvlnp0cil67tsboe
Tags: upstream-0~git20141101/arcanist
Import upstream version 0~git20141101, component arcanist

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * Very basic 'nose' unit test engine wrapper.
 
5
 *
 
6
 * Requires nose 1.1.3 for code coverage.
 
7
 */
 
8
final class NoseTestEngine extends ArcanistUnitTestEngine {
 
9
 
 
10
  public function run() {
 
11
    $paths = $this->getPaths();
 
12
 
 
13
    $affected_tests = array();
 
14
    foreach ($paths as $path) {
 
15
      $absolute_path = Filesystem::resolvePath($path);
 
16
 
 
17
      if (is_dir($absolute_path)) {
 
18
        $absolute_test_path = Filesystem::resolvePath('tests/'.$path);
 
19
        if (is_readable($absolute_test_path)) {
 
20
          $affected_tests[] = $absolute_test_path;
 
21
        }
 
22
      }
 
23
 
 
24
      if (is_readable($absolute_path)) {
 
25
        $filename = basename($path);
 
26
        $directory = dirname($path);
 
27
 
 
28
        // assumes directory layout: tests/<package>/test_<module>.py
 
29
        $relative_test_path = 'tests/'.$directory.'/test_'.$filename;
 
30
        $absolute_test_path = Filesystem::resolvePath($relative_test_path);
 
31
 
 
32
        if (is_readable($absolute_test_path)) {
 
33
          $affected_tests[] = $absolute_test_path;
 
34
        }
 
35
      }
 
36
    }
 
37
 
 
38
    return $this->runTests($affected_tests, './');
 
39
  }
 
40
 
 
41
  public function runTests($test_paths, $source_path) {
 
42
    if (empty($test_paths)) {
 
43
      return array();
 
44
    }
 
45
 
 
46
    $futures = array();
 
47
    $tmpfiles = array();
 
48
    foreach ($test_paths as $test_path) {
 
49
      $xunit_tmp = new TempFile();
 
50
      $cover_tmp = new TempFile();
 
51
 
 
52
      $future = $this->buildTestFuture($test_path, $xunit_tmp, $cover_tmp);
 
53
 
 
54
      $futures[$test_path] = $future;
 
55
      $tmpfiles[$test_path] = array(
 
56
        'xunit' => $xunit_tmp,
 
57
        'cover' => $cover_tmp,
 
58
      );
 
59
    }
 
60
 
 
61
    $results = array();
 
62
    foreach (Futures($futures)->limit(4) as $test_path => $future) {
 
63
      try {
 
64
        list($stdout, $stderr) = $future->resolvex();
 
65
      } catch(CommandException $exc) {
 
66
        if ($exc->getError() > 1) {
 
67
          // 'nose' returns 1 when tests are failing/broken.
 
68
          throw $exc;
 
69
        }
 
70
      }
 
71
 
 
72
      $xunit_tmp = $tmpfiles[$test_path]['xunit'];
 
73
      $cover_tmp = $tmpfiles[$test_path]['cover'];
 
74
 
 
75
      $this->parser = new ArcanistXUnitTestResultParser();
 
76
      $results[] = $this->parseTestResults($source_path,
 
77
                                           $xunit_tmp,
 
78
                                           $cover_tmp);
 
79
    }
 
80
 
 
81
    return array_mergev($results);
 
82
  }
 
83
 
 
84
  public function buildTestFuture($path, $xunit_tmp, $cover_tmp) {
 
85
    $cmd_line = csprintf('nosetests --with-xunit --xunit-file=%s',
 
86
                         $xunit_tmp);
 
87
 
 
88
    if ($this->getEnableCoverage() !== false) {
 
89
      $cmd_line .= csprintf(
 
90
        ' --with-coverage --cover-xml --cover-xml-file=%s',
 
91
        $cover_tmp);
 
92
    }
 
93
 
 
94
    return new ExecFuture('%C %s', $cmd_line, $path);
 
95
  }
 
96
 
 
97
  public function parseTestResults($source_path, $xunit_tmp, $cover_tmp) {
 
98
    $results = $this->parser->parseTestResults(
 
99
      Filesystem::readFile($xunit_tmp));
 
100
 
 
101
    // coverage is for all testcases in the executed $path
 
102
    if ($this->getEnableCoverage() !== false) {
 
103
      $coverage = $this->readCoverage($cover_tmp, $source_path);
 
104
      foreach ($results as $result) {
 
105
        $result->setCoverage($coverage);
 
106
      }
 
107
    }
 
108
 
 
109
    return $results;
 
110
  }
 
111
 
 
112
  public function readCoverage($cover_file, $source_path) {
 
113
    $coverage_dom = new DOMDocument();
 
114
    $coverage_dom->loadXML(Filesystem::readFile($cover_file));
 
115
 
 
116
    $reports = array();
 
117
    $classes = $coverage_dom->getElementsByTagName('class');
 
118
 
 
119
    foreach ($classes as $class) {
 
120
      $path = $class->getAttribute('filename');
 
121
      $root = $this->getWorkingCopy()->getProjectRoot();
 
122
 
 
123
      if (!Filesystem::isDescendant($path, $root)) {
 
124
        continue;
 
125
      }
 
126
 
 
127
      // get total line count in file
 
128
      $line_count = count(phutil_split_lines(Filesystem::readFile($path)));
 
129
 
 
130
      $coverage = '';
 
131
      $start_line = 1;
 
132
      $lines = $class->getElementsByTagName('line');
 
133
      for ($ii = 0; $ii < $lines->length; $ii++) {
 
134
        $line = $lines->item($ii);
 
135
 
 
136
        $next_line = intval($line->getAttribute('number'));
 
137
        for ($start_line; $start_line < $next_line; $start_line++) {
 
138
          $coverage .= 'N';
 
139
        }
 
140
 
 
141
        if (intval($line->getAttribute('hits')) == 0) {
 
142
          $coverage .= 'U';
 
143
        } else if (intval($line->getAttribute('hits')) > 0) {
 
144
          $coverage .= 'C';
 
145
        }
 
146
 
 
147
        $start_line++;
 
148
      }
 
149
 
 
150
      if ($start_line < $line_count) {
 
151
        foreach (range($start_line, $line_count) as $line_num) {
 
152
          $coverage .= 'N';
 
153
        }
 
154
      }
 
155
 
 
156
      $reports[$path] = $coverage;
 
157
    }
 
158
 
 
159
    return $reports;
 
160
  }
 
161
 
 
162
}