summaryrefslogtreecommitdiffstats
path: root/devtools/client/bin/devtools-node-test-runner.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/bin/devtools-node-test-runner.js')
-rw-r--r--devtools/client/bin/devtools-node-test-runner.js193
1 files changed, 193 insertions, 0 deletions
diff --git a/devtools/client/bin/devtools-node-test-runner.js b/devtools/client/bin/devtools-node-test-runner.js
new file mode 100644
index 0000000000..475260613d
--- /dev/null
+++ b/devtools/client/bin/devtools-node-test-runner.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+/* global __dirname, process */
+
+"use strict";
+
+/**
+ * This is a test runner dedicated to run DevTools node tests continuous integration
+ * platforms. It will parse the logs to output errors compliant with treeherder tooling.
+ *
+ * See taskcluster/ci/source-test/node.yml for the definition of the task running those
+ * tests on try.
+ */
+
+const { execFileSync } = require("child_process");
+const { chdir } = require("process");
+const path = require("path");
+
+// Supported node test suites for DevTools
+const TEST_TYPES = {
+ JEST: "jest",
+ MOCHA: "mocha",
+ TYPESCRIPT: "typescript",
+};
+
+const SUITES = {
+ aboutdebugging: {
+ path: "../aboutdebugging/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ accessibility: {
+ path: "../accessibility/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ application: {
+ path: "../application/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ compatibility: {
+ path: "../inspector/compatibility/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ framework: {
+ path: "../framework/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ netmonitor: {
+ path: "../netmonitor/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ performance: {
+ path: "../performance-new",
+ type: TEST_TYPES.TYPESCRIPT,
+ },
+ shared_components: {
+ path: "../shared/components/test/node",
+ type: TEST_TYPES.JEST,
+ },
+ webconsole: {
+ path: "../webconsole/test/node",
+ type: TEST_TYPES.MOCHA,
+ },
+};
+
+function execOut(...args) {
+ let out;
+ let err;
+ try {
+ out = execFileSync(...args);
+ } catch (e) {
+ out = e.stdout;
+ err = e.stderr;
+ }
+ return { out: out.toString(), err: err && err.toString() };
+}
+
+function getErrors(suite, out, err) {
+ switch (SUITES[suite].type) {
+ case TEST_TYPES.JEST:
+ return getJestErrors(out, err);
+ case TEST_TYPES.MOCHA:
+ return getMochaErrors(out, err);
+ case TEST_TYPES.TYPESCRIPT:
+ return getTypescriptErrors(out, err);
+ default:
+ throw new Error("Unsupported suite type: " + SUITES[suite].type);
+ }
+}
+
+function getJestErrors(out, err) {
+ // The string out has extra content before the JSON object starts.
+ const jestJsonOut = out.substring(out.indexOf("{"), out.lastIndexOf("}") + 1);
+ const results = JSON.parse(jestJsonOut);
+
+ // The individual failing tests are jammed into the same message string :/
+ return results.testResults.reduce((p, testResult) => {
+ const failures = testResult.message
+ .split("\n")
+ .filter(l => l.includes("●"));
+ return p.concat(failures);
+ }, []);
+}
+
+function getMochaErrors(out, err) {
+ // With mocha tests, the command itself contains curly braces already so we need
+ // to find the first brace after the first line.
+ const firstRelevantBracket = out.indexOf("{", out.indexOf("--reporter json"));
+ const mochaJsonOut = out.substring(
+ firstRelevantBracket,
+ out.lastIndexOf("}") + 1
+ );
+ const results = JSON.parse(mochaJsonOut);
+ if (!results.failures) {
+ // No failures, return an empty array.
+ return [];
+ }
+ return results.failures.map(
+ failure => failure.fullTitle + " | " + failure.err.message
+ );
+}
+
+function getTypescriptErrors(out, err) {
+ // Typescript error lines look like:
+ // popup/panel.jsm.js(103,7): error TS2531: Object is possibly 'null'.
+ // Which means:
+ // {file_path}({line},{col}): error TS{error_code}: {message}
+ const tsErrorRegex = /error TS\d+\:/;
+ return out.split("\n").filter(l => tsErrorRegex.test(l));
+}
+
+function runTests() {
+ console.log("[devtools-node-test-runner] Extract suite argument");
+ const suiteArg = process.argv.find(arg => arg.includes("suite="));
+ const suite = suiteArg.split("=")[1];
+ if (!SUITES[suite]) {
+ throw new Error(
+ "Invalid suite argument to devtools-node-test-runner: " + suite
+ );
+ }
+
+ console.log("[devtools-node-test-runner] Found test suite: " + suite);
+ const testPath = path.join(__dirname, SUITES[suite].path);
+ chdir(testPath);
+
+ console.log("[devtools-node-test-runner] Check `yarn` is available");
+ try {
+ // This will throw if yarn is unavailable
+ execFileSync("yarn", ["--version"]);
+ } catch (e) {
+ console.log(
+ "[devtools-node-test-runner] ERROR: `yarn` is not installed. " +
+ "See https://yarnpkg.com/docs/install/ "
+ );
+ return false;
+ }
+
+ console.log("[devtools-node-test-runner] Run `yarn` in test folder");
+ execOut("yarn");
+
+ console.log(`TEST START | ${SUITES[suite].type} | ${suite}`);
+
+ console.log("[devtools-node-test-runner] Run `yarn test` in test folder");
+ const { out, err } = execOut("yarn", ["test-ci"]);
+
+ if (err) {
+ console.log("[devtools-node-test-runner] Error log");
+ console.log(err);
+ }
+
+ console.log("[devtools-node-test-runner] Parse errors from the test logs");
+ const errors = getErrors(suite, out, err) || [];
+ for (const error of errors) {
+ console.log(
+ `TEST-UNEXPECTED-FAIL | ${SUITES[suite].type} | ${suite} | ${error}`
+ );
+ }
+
+ const success = errors.length === 0;
+ if (success) {
+ console.log(`[devtools-node-test-runner] Test suite [${suite}] succeeded`);
+ } else {
+ console.log(`[devtools-node-test-runner] Test suite [${suite}] failed`);
+ console.log(
+ "[devtools-node-test-runner] You can find documentation about the " +
+ "devtools node tests at https://firefox-source-docs.mozilla.org/devtools/tests/node-tests.html"
+ );
+ }
+ return success;
+}
+
+process.exitCode = runTests() ? 0 : 1;