Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13225446
D13949.id33672.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D13949.id33672.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D13949: Add Mocha TestEngine (not worth review / ready for upstream yet)
Attached
Detach File
Event Timeline
Log In to Comment