tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

try-runner.js (8975B)


      1 /* eslint-disable no-console */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      5 
      6 /*
      7 * A small test runner/reporter for node-based tests,
      8 * which are run via taskcluster node(debugger).
      9 *
     10 * Forked from
     11 * https://searchfox.org/mozilla-central/rev/c3453c7a0427eb27d467e1582f821f402aed9850/devtools/client/debugger/bin/try-runner.js
     12 */
     13 
     14 const { execFileSync } = require("child_process");
     15 const { readFileSync, writeFileSync } = require("fs");
     16 const path = require("path");
     17 const { pathToFileURL } = require("url");
     18 const chalk = require("chalk");
     19 
     20 function logErrors(tool, errors) {
     21  for (const error of errors) {
     22    console.log(`TEST-UNEXPECTED-FAIL | ${tool} | ${error}`);
     23  }
     24  return errors;
     25 }
     26 
     27 function execOut(...args) {
     28  let exitCode = 0;
     29  let out;
     30  let err;
     31 
     32  try {
     33    out = execFileSync(...args, {
     34      silent: false,
     35    });
     36  } catch (e) {
     37    // For debugging on (eg) try server...
     38    //
     39    // if (e) {
     40    //   logErrors("execOut", ["execFileSync returned exception: ", e]);
     41    // }
     42 
     43    out = e && e.stdout;
     44    err = e && e.stderr;
     45    exitCode = e && e.status;
     46  }
     47  return { exitCode, out: out && out.toString(), err: err && err.toString() };
     48 }
     49 
     50 function logStart(name) {
     51  console.log(`TEST-START | ${name}`);
     52 }
     53 
     54 function logSkip(name) {
     55  console.log(`TEST-SKIP | ${name}`);
     56 }
     57 
     58 const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
     59 
     60 const tests = {
     61  bundles() {
     62    logStart("bundles");
     63 
     64    const items = {
     65      "about:welcome bundle": {
     66        path: path.join(
     67          "../",
     68          "aboutwelcome",
     69          "content",
     70          "aboutwelcome.bundle.js"
     71        ),
     72      },
     73      "aboutwelcome.css": {
     74        path: path.join("../", "aboutwelcome", "content", "aboutwelcome.css"),
     75        extraCheck: content => {
     76          if (content.match(/^\s*@import/m)) {
     77            return "aboutwelcome.css contains an @import statement. We should not import styles through the stylesheet, because it is loaded in multiple environments, including the browser chrome for feature callouts. To add other stylesheets to about:welcome or spotlight, add them to aboutwelcome.html or spotlight.html instead.";
     78          }
     79          return null;
     80        },
     81      },
     82      "about:asrouter bundle": {
     83        path: path.join(
     84          "../",
     85          "asrouter",
     86          "content",
     87          "asrouter-admin.bundle.js"
     88        ),
     89      },
     90      "ASRouterAdmin.css": {
     91        path: path.join(
     92          "../",
     93          "asrouter",
     94          "content",
     95          "components",
     96          "ASRouterAdmin",
     97          "ASRouterAdmin.css"
     98        ),
     99      },
    100    };
    101    const errors = [];
    102 
    103    for (const name of Object.keys(items)) {
    104      const item = items[name];
    105      item.before = readFileSync(item.path, item.encoding || "utf8");
    106    }
    107 
    108    // Run about:welcome bundle script.
    109    let cwd = process.cwd();
    110    process.chdir("../aboutwelcome");
    111    let welcomeBundleExitCode = execOut(npmCommand, ["run", "bundle"]).exitCode;
    112    process.chdir(cwd);
    113 
    114    let asrouterBundleExitCode = execOut(npmCommand, [
    115      "run",
    116      "bundle",
    117    ]).exitCode;
    118 
    119    for (const name of Object.keys(items)) {
    120      const item = items[name];
    121      const after = readFileSync(item.path, item.encoding || "utf8");
    122 
    123      if (item.before !== after) {
    124        errors.push(`${name} out of date`);
    125      }
    126 
    127      if (item.extraCheck) {
    128        const extraError = item.extraCheck(after);
    129        if (extraError) {
    130          errors.push(extraError);
    131        }
    132      }
    133    }
    134 
    135    if (welcomeBundleExitCode !== 0) {
    136      errors.push("about:welcome npm:bundle did not run successfully");
    137    }
    138 
    139    if (asrouterBundleExitCode !== 0) {
    140      errors.push("about:asrouter npm:bundle did not run successfully");
    141    }
    142 
    143    logErrors("bundles", errors);
    144    return errors.length === 0;
    145  },
    146 
    147  karma() {
    148    logStart(`karma ${process.cwd()}`);
    149 
    150    const errors = [];
    151    const { exitCode, out } = execOut(npmCommand, [
    152      "run",
    153      "testmc:unit",
    154      // , "--", "--log-level", "--verbose",
    155      // to debug the karma integration, uncomment the above line
    156    ]);
    157 
    158    // karma spits everything to stdout, not stderr, so if nothing came back on
    159    // stdout, give up now.
    160    if (!out) {
    161      return false;
    162    }
    163 
    164    // Detect mocha failures
    165    let jsonContent;
    166    try {
    167      // Note that this will be overwritten at each run, but that shouldn't
    168      // matter.
    169      jsonContent = readFileSync(path.join("logs", "karma-run-results.json"));
    170    } catch (ex) {
    171      console.error("exception reading karma-run-results.json: ", ex);
    172      return false;
    173    }
    174    const results = JSON.parse(jsonContent);
    175    // eslint-disable-next-line guard-for-in
    176    for (let testArray in results.result) {
    177      let failedTests = Array.from(results.result[testArray]).filter(
    178        test => !test.success && !test.skipped
    179      );
    180 
    181      errors.push(
    182        ...failedTests.map(
    183          test => `${test.suite.join(":")} ${test.description}: ${test.log[0]}`
    184        )
    185      );
    186    }
    187 
    188    // Detect istanbul failures (coverage thresholds set in karma config)
    189    const coverage = out.match(/ERROR.+coverage-istanbul.+/g);
    190    if (coverage) {
    191      errors.push(...coverage.map(line => line.match(/Coverage.+/)[0]));
    192    }
    193 
    194    logErrors(`karma ${process.cwd()}`, errors);
    195 
    196    console.log("-----karma stdout below this line---");
    197    console.log(out);
    198    console.log("-----karma stdout above this line---");
    199 
    200    // Pass if there's no detected errors and nothing unexpected.
    201    return errors.length === 0 && !exitCode;
    202  },
    203 
    204  welcomekarma() {
    205    let cwd = process.cwd();
    206    process.chdir("../aboutwelcome");
    207    const result = this.karma();
    208    process.chdir(cwd);
    209    return result;
    210  },
    211 
    212  zipCodeCoverage() {
    213    logStart("zipCodeCoverage");
    214 
    215    const welcomeCoveragePath = "../aboutwelcome/logs/coverage/lcov.info";
    216    const asrouterCoveragePath = "logs/coverage/lcov.info";
    217 
    218    const welcomeCoverage = readFileSync(welcomeCoveragePath, "utf8");
    219    let asrouterCoverage = readFileSync(asrouterCoveragePath, "utf8");
    220 
    221    asrouterCoverage = `${welcomeCoverage}${asrouterCoverage}`;
    222    writeFileSync(asrouterCoveragePath, asrouterCoverage, "utf8");
    223 
    224    const { exitCode, out } = execOut("zip", [
    225      "-j",
    226      "logs/coverage/code-coverage-grcov",
    227      "logs/coverage/lcov.info",
    228    ]);
    229 
    230    console.log("zipCodeCoverage log output: ", out);
    231 
    232    if (!exitCode) {
    233      return true;
    234    }
    235 
    236    return false;
    237  },
    238 };
    239 
    240 async function main() {
    241  const { default: meow } = await import("meow");
    242  const fileUrl = pathToFileURL(__filename);
    243  const cli = meow(
    244    `
    245  Usage
    246    $ node bin/try-runner.js <tests> [options]
    247 
    248  Options
    249    -t NAME, --test NAME   Run only the specified test. If not specified,
    250                           all tests will be run. Argument can be passed 
    251                           multiple times to run multiple tests.
    252    --help                 Show this help message.
    253 
    254  Examples
    255    $ node bin/try-runner.js bundles karma
    256    $ node bin/try-runner.js -t karma -t zip
    257 `,
    258    {
    259      description: false,
    260      // `pkg` is a tiny optimization. It prevents meow from looking for a package
    261      // that doesn't technically exist. meow searches for a package and changes
    262      // the process name to the package name. It resolves to the newtab
    263      // package.json, which would give a confusing name and be wasteful.
    264      pkg: {
    265        name: "try-runner",
    266        version: "1.0.0",
    267      },
    268      // `importMeta` is required by meow 10+. It was added to support ESM, but
    269      // meow now requires it, and no longer supports CJS style imports. But it
    270      // only uses import.meta.url, which can be polyfilled like this:
    271      importMeta: { url: fileUrl },
    272      flags: {
    273        test: {
    274          type: "string",
    275          isMultiple: true,
    276          shortFlag: "t",
    277        },
    278      },
    279    }
    280  );
    281  const aliases = {
    282    bundle: "bundles",
    283    build: "bundles",
    284    coverage: "karma",
    285    cov: "karma",
    286    zip: "zipCodeCoverage",
    287    welcomecoverage: "welcomekarma",
    288    welcomecov: "welcomekarma",
    289  };
    290 
    291  const inputs = [...cli.input, ...cli.flags.test].map(input =>
    292    (aliases[input] || input).toLowerCase()
    293  );
    294 
    295  function shouldRunTest(name) {
    296    if (inputs.length) {
    297      return inputs.includes(name.toLowerCase());
    298    }
    299    return true;
    300  }
    301 
    302  const results = [];
    303  for (const name of Object.keys(tests)) {
    304    if (shouldRunTest(name)) {
    305      results.push([name, tests[name]()]);
    306    } else {
    307      logSkip(name);
    308    }
    309  }
    310 
    311  for (const [name, result] of results) {
    312    // colorize output based on result
    313    console.log(result ? chalk.green(`✓ ${name}`) : chalk.red(`✗ ${name}`));
    314  }
    315 
    316  const success = results.every(([, result]) => result);
    317  process.exitCode = success ? 0 : 1;
    318  console.log("CODE", process.exitCode);
    319 }
    320 
    321 main();