Changeset View
Standalone View
src/unit/engine/MochaTestEngine.php
- This file was added.
<?php | |||||
joshuaspence: There should be a blank line between `<?php` and any code, see D13534. | |||||
/** | |||||
* Uses mocha (https://mochajs.org/) to test Javascript code. | |||||
joshuaspenceUnsubmitted Done Inline ActionsPrefer to write this as [[https://mochajs.org/ | Mocha]]. joshuaspence: Prefer to write this as `[[https://mochajs.org/ | Mocha]]`. | |||||
* Uses istanbul (https://gotwarlost.github.io/istanbul/) to cover Javascript | |||||
joshuaspenceUnsubmitted Done Inline ActionsPrefer to write this as [[https://gotwarlost.github.io/istanbul/ | Istanbul]] joshuaspence: Prefer to write this as `[[https://gotwarlost.github.io/istanbul/ | Istanbul]]` | |||||
* code. | |||||
* | |||||
* Assumes that when modifying a file with a path like `SomeDir/SomeFile.js`, | |||||
* that the unit test that verifies the functionality of `SomeFile` is | |||||
* located at `SomeDir/__tests__/SomeFile-test.js`. | |||||
* | |||||
* @concrete-extensible | |||||
joshuaspenceUnsubmitted Done Inline ActionsThis isn't quite correct. @concrete-extensible only makes sense for non-final, non-abstract classes. joshuaspence: This isn't quite correct. `@concrete-extensible` only makes sense for non-`final`, non… | |||||
*/ | |||||
final class MochaTestEngine extends ArcanistUnitTestEngine { | |||||
/** | |||||
* Return the name of the test engine. | |||||
* So it can be used in ArcanistConfigurationDrivenUnitTestEngine later. | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsSee my next comment. joshuaspence: See my next comment. | |||||
public function getEngineConfigurationName() { | |||||
return 'mocha'; | |||||
} | |||||
/** | |||||
* This test engine supports running all tests. | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsPersonally, I find this comment to be fairly useless and somewhat distracting. In this particular case, I don't think the comment provides any explanation that the code does not. joshuaspence: Personally, I find this comment to be fairly useless and somewhat distracting. In this… | |||||
protected function supportsRunAllTests() { | |||||
return true; | |||||
} | |||||
/** | |||||
* Main entry point for the test engine. Determines what files changed | |||||
* and test based on the files that have changed. | |||||
* @return array Array of test results. | |||||
* @throws ArcanistNoEffectException | |||||
* @throws CommandException | |||||
*/ | |||||
public function run() { | |||||
/* | |||||
* Determine whether it shall run all the test or more precisely the | |||||
* modified file | |||||
* | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsIMO, this doesn't provide any value. joshuaspence: IMO, this doesn't provide any value. | |||||
tycho.tatitscheffAuthorUnsubmitted Done Inline ActionsWill get rid of all this useless comments :) tycho.tatitscheff: Will get rid of all this useless comments :) | |||||
if (!$this->getRunAllTests()) { | |||||
$pattern_tests = $this->getAllTestsPattern(); | |||||
} else { | |||||
$pattern_tests = $this->getGranularTestsPattern(); | |||||
} | |||||
/* | |||||
* Throw an error if there is no test that can be run | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsAs above. joshuaspence: As above. | |||||
if (!$pattern_tests) { | |||||
throw new ArcanistNoEffectException(pht('No tests to run.')); | |||||
} | |||||
/* | |||||
* Retrieve working copy and project root path | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsAs aabove. joshuaspence: As aabove. | |||||
$working_copy = $this->getWorkingCopy(); | |||||
$project_root = $working_copy->getProjectRoot(); | |||||
/* | |||||
* Create new temp-file paths to receive junit test result | |||||
* and lcov coverage result only if coverage is activated. | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsAs above. joshuaspence: As above. | |||||
$junit_tmp = new TempFile(); | |||||
$cover_tmp = null; | |||||
if ($this->getEnableCoverage() !== false) { | |||||
joshuaspenceUnsubmitted Done Inline ActionsWhy not just if ($this->getEnableCoverage()) { ? joshuaspence: Why not just `if ($this->getEnableCoverage()) {` ? | |||||
tycho.tatitscheffAuthorUnsubmitted Done Inline ActionsIndeed ! tycho.tatitscheff: Indeed ! | |||||
$cover_path = './coverage/cobertura-coverage.xml'; | |||||
$cover_tmp = Filesystem::resolvePath($cover_path, $project_root); | |||||
} | |||||
$future = $this->buildTestFuture($pattern_tests, $junit_tmp); | |||||
list($err, $stdout, $stderr) = $future->resolve(); | |||||
/* | |||||
* Check that the future output desired files | |||||
*/ | |||||
joshuaspenceUnsubmitted Done Inline ActionsAs above. joshuaspence: As above. | |||||
if (!Filesystem::pathExists($junit_tmp)) { | |||||
throw new CommandException( | |||||
pht('Testing Command failed with error #%s!', $err), | |||||
$future->getCommand(), | |||||
$err, | |||||
$stdout, | |||||
$stderr); | |||||
} | |||||
if ($this->getEnableCoverage() !== false) { | |||||
joshuaspenceUnsubmitted Done Inline ActionsAs above. joshuaspence: As above. | |||||
if (!Filesystem::pathExists($cover_tmp)) { | |||||
throw new CommandException( | |||||
pht('Coverage Command failed with error #%s!', $err), | |||||
$future->getCommand(), | |||||
$err, | |||||
$stdout, | |||||
$stderr); | |||||
} | |||||
} | |||||
return $this->parseTestResults($junit_tmp, $cover_tmp); | |||||
} | |||||
/** | |||||
* Return pattern for retrieving test files | |||||
* If special pattern isn't set in configuration file, it is defaulted | |||||
*/ | |||||
private function getAllTestsPattern() { | |||||
$pattern = $this->getConfigurationManager() | |||||
->getConfigFromAnySource('unit.mocha.pattern'); | |||||
joshuaspenceUnsubmitted Done Inline ActionsThis should (later) come from arclint. joshuaspence: This should (later) come from `arclint`. | |||||
tycho.tatitscheffAuthorUnsubmitted Done Inline ActionsAnd get getConfigFromAnySource won't be froward compatible with .arclint and D13534 ? tycho.tatitscheff: And get getConfigFromAnySource won't be froward compatible with `.arclint` and D13534 ?
I will… | |||||
if (!$pattern) { | |||||
$pattern = 'tests/*.spec.js'; | |||||
} | |||||
return $pattern; | |||||
} | |||||
/** | |||||
* This test engine supports running all tests. | |||||
*/ | |||||
private function getGranularTestsPattern() { | |||||
// TODO : build something really granular. | |||||
Lint: TODO Comment This comment has a TODO. Lint: TODO Comment: This comment has a TODO. | |||||
return $this->getAllTestsPattern(); | |||||
} | |||||
private function buildTestFuture($pattern_tests, $junit_tmp) { | |||||
$cmd_line = csprintf( | |||||
'MOCHA_FILE=%s '. | |||||
'mocha test -u exports '. | |||||
'--reporter mocha-junit-reporter %s', | |||||
$junit_tmp, $pattern_tests); | |||||
if ($this->getEnableCoverage() !== false) { | |||||
$cmd_line = csprintf( | |||||
'MOCHA_FILE=%s '. | |||||
'istanbul cover '. | |||||
'node_modules/mocha/bin/_mocha '. | |||||
'-- -u exports '. | |||||
'--reporter mocha-junit-reporter %s'. | |||||
'&& istanbul report cobertura', | |||||
$junit_tmp, $pattern_tests); | |||||
joshuaspenceUnsubmitted Done Inline ActionsI'm not sure about &&... particularly I'm not sure if this will work on Windows. joshuaspence: I'm not sure about `&&`... particularly I'm not sure if this will work on Windows. | |||||
tycho.tatitscheffAuthorUnsubmitted Done Inline ActionsOn production I have an other syntax that works on windows too. I will switch for this. tycho.tatitscheff: On production I have an other syntax that works on windows too. I will switch for this. | |||||
} | |||||
return new ExecFuture('%C', $cmd_line); | |||||
joshuaspenceUnsubmitted Done Inline ActionsThis is equivalent to ExecFuture($cmd_line) joshuaspence: This is equivalent to `ExecFuture($cmd_line)` | |||||
} | |||||
private function parseTestResults($junit_tmp, $cover_tmp) { | |||||
$parser = new ArcanistXUnitTestResultParser(); | |||||
$results = $parser->parseTestResults(Filesystem::readFile($junit_tmp)); | |||||
if ($this->getEnableCoverage() !== false) { | |||||
$coverage = $this->readCoverage($cover_tmp); | |||||
foreach ($results as $result) { | |||||
$result->setCoverage($coverage); | |||||
} | |||||
} | |||||
return $results; | |||||
} | |||||
/** | |||||
* TODO: This is copied from @{class:NoseTestEngine}. | |||||
Lint: TODO Comment This comment has a TODO. Lint: TODO Comment: This comment has a TODO. | |||||
* I didn't ever try to understand what is going on. | |||||
*/ | |||||
public function readCoverage($cover_file) { | |||||
$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 | |||||
$lines_list = phutil_split_lines(Filesystem::readFile($path)); | |||||
$line_count = count($lines_list); | |||||
$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; | |||||
} | |||||
} |
There should be a blank line between <?php and any code, see D13534.