Page MenuHomePhabricator

D13949.id33672.diff
No OneTemporary

D13949.id33672.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -267,6 +267,7 @@
'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php',
'ArcanistXUnitTestResultParser' => 'unit/parser/ArcanistXUnitTestResultParser.php',
'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php',
+ 'MochaTestEngine' => 'unit/engine/MochaTestEngine.php',
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php',
'PhpunitTestEngineTestCase' => 'unit/engine/__tests__/PhpunitTestEngineTestCase.php',
@@ -540,6 +541,7 @@
'ArcanistXMLLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistXUnitTestResultParser' => 'Phobject',
'CSharpToolsTestEngine' => 'XUnitTestEngine',
+ 'MochaTestEngine' => 'ArcanistUnitTestEngine',
'NoseTestEngine' => 'ArcanistUnitTestEngine',
'PhpunitTestEngine' => 'ArcanistUnitTestEngine',
'PhpunitTestEngineTestCase' => 'PhutilTestCase',
diff --git a/src/unit/engine/MochaTestEngine.php b/src/unit/engine/MochaTestEngine.php
new file mode 100644
--- /dev/null
+++ b/src/unit/engine/MochaTestEngine.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Uses mocha (https://mochajs.org/) to test Javascript code.
+ * Uses istanbul (https://gotwarlost.github.io/istanbul/) to cover Javascript
+ * 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
+ */
+final class MochaTestEngine extends ArcanistUnitTestEngine {
+ /**
+ * Return the name of the test engine.
+ * So it can be used in ArcanistConfigurationDrivenUnitTestEngine later.
+ */
+ public function getEngineConfigurationName() {
+ return 'mocha';
+ }
+
+ /**
+ * This test engine supports running all tests.
+ */
+ 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
+ *
+ */
+ if (!$this->getRunAllTests()) {
+ $pattern_tests = $this->getAllTestsPattern();
+ } else {
+ $pattern_tests = $this->getGranularTestsPattern();
+ }
+ /*
+ * Throw an error if there is no test that can be run
+ */
+ if (!$pattern_tests) {
+ throw new ArcanistNoEffectException(pht('No tests to run.'));
+ }
+ /*
+ * Retrieve working copy and project root path
+ */
+ $workingCopy = $this->getWorkingCopy();
+ $projectRoot = $workingCopy->getProjectRoot();
+ /*
+ * Create new temp-file paths to receive junit test result
+ * and lcov coverage result only if coverage is activated.
+ */
+ $junit_tmp = new TempFile();
+ $cover_tmp = null;
+ if ($this->getEnableCoverage() !== false) {
+ $cover_tmp = Filesystem::resolvePath('./coverage/cobertura-coverage.xml', $projectRoot);
+ }
+
+ $future = $this->buildTestFuture($pattern_tests, $junit_tmp);
+ list($err, $stdout, $stderr) = $future->resolve();
+
+ /*
+ * Check that the future output desired files
+ */
+ 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) {
+ 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');
+ if (!$pattern) {
+ $pattern = 'tests/*.spec.js';
+ }
+ return $pattern;
+ }
+
+ /**
+ * This test engine supports running all tests.
+ */
+ private function getGranularTestsPattern() {
+ // TODO : build something really granular.
+ 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);
+ }
+
+ return new ExecFuture('%C', $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}.
+ * 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;
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Mon, May 20, 3:11 PM (1 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6303837
Default Alt Text
D13949.id33672.diff (7 KB)

Event Timeline