Simplistic watch mode for runtests (#51461)

* Simplistic watch mode for runtests

* Use esbuild WatchMode object for testRunner updates

* switch AbortController to CancelToken
This commit is contained in:
Ron Buckton
2022-11-09 15:07:08 -05:00
committed by GitHub
parent 6e0a62e8dd
commit e67b06e909
5 changed files with 243 additions and 11 deletions

View File

@@ -2,9 +2,11 @@ import del from "del";
import fs from "fs";
import os from "os";
import path from "path";
import chalk from "chalk";
import cmdLineOptions from "./options.mjs";
import { exec } from "./utils.mjs";
import { findUpFile, findUpRoot } from "./findUpDir.mjs";
import { CancelError } from "@esfx/canceltoken";
const mochaJs = path.resolve(findUpRoot(), "node_modules", "mocha", "bin", "_mocha");
export const localBaseline = "tests/baselines/local/";
@@ -17,8 +19,11 @@ export const localTest262Baseline = "internal/baselines/test262/local";
* @param {string} runJs
* @param {string} defaultReporter
* @param {boolean} runInParallel
* @param {object} options
* @param {import("@esfx/canceltoken").CancelToken} [options.token]
* @param {boolean} [options.watching]
*/
export async function runConsoleTests(runJs, defaultReporter, runInParallel) {
export async function runConsoleTests(runJs, defaultReporter, runInParallel, options = {}) {
let testTimeout = cmdLineOptions.timeout;
const tests = cmdLineOptions.tests;
const inspect = cmdLineOptions.break || cmdLineOptions.inspect;
@@ -31,7 +36,14 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel) {
const shards = +cmdLineOptions.shards || undefined;
const shardId = +cmdLineOptions.shardId || undefined;
if (!cmdLineOptions.dirty) {
if (options.watching) {
console.log(chalk.yellowBright(`[watch] cleaning test directories...`));
}
await cleanTestDirs();
if (options.token?.signaled) {
return;
}
}
if (fs.existsSync(testConfigFile)) {
@@ -56,6 +68,10 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel) {
testTimeout = 400000;
}
if (options.watching) {
console.log(chalk.yellowBright(`[watch] running tests...`));
}
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || shards || shardId) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed, shards, shardId);
}
@@ -114,7 +130,8 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel) {
try {
setNodeEnvToDevelopment();
const { exitCode } = await exec(process.execPath, args);
const { exitCode } = await exec(process.execPath, args, { token: options.token });
if (exitCode !== 0) {
errorStatus = exitCode;
error = new Error(`Process exited with status code ${errorStatus}.`);
@@ -132,8 +149,17 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel) {
await deleteTemporaryProjectOutput();
if (error !== undefined) {
process.exitCode = typeof errorStatus === "number" ? errorStatus : 2;
throw error;
if (error instanceof CancelError) {
throw error;
}
if (options.watching) {
console.error(`${chalk.redBright(error.name)}: ${error.message}`);
}
else {
process.exitCode = typeof errorStatus === "number" ? errorStatus : 2;
throw error;
}
}
}

View File

@@ -7,6 +7,7 @@ import which from "which";
import { spawn } from "child_process";
import assert from "assert";
import JSONC from "jsonc-parser";
import { CancelError } from "@esfx/canceltoken";
/**
* Executes the provided command once with the supplied arguments.
@@ -18,6 +19,7 @@ import JSONC from "jsonc-parser";
* @property {boolean} [ignoreExitCode]
* @property {boolean} [hidePrompt]
* @property {boolean} [waitForExit=true]
* @property {import("@esfx/canceltoken").CancelToken} [token]
*/
export async function exec(cmd, args, options = {}) {
return /**@type {Promise<{exitCode?: number}>}*/(new Promise((resolve, reject) => {
@@ -26,16 +28,24 @@ export async function exec(cmd, args, options = {}) {
if (!options.hidePrompt) console.log(`> ${chalk.green(cmd)} ${args.join(" ")}`);
const proc = spawn(which.sync(cmd), args, { stdio: waitForExit ? "inherit" : "ignore" });
if (waitForExit) {
const onCanceled = () => {
proc.kill();
};
const subscription = options.token?.subscribe(onCanceled);
proc.on("exit", exitCode => {
if (exitCode === 0 || ignoreExitCode) {
resolve({ exitCode: exitCode ?? undefined });
}
else {
reject(new Error(`Process exited with code: ${exitCode}`));
const reason = options.token?.signaled ? options.token.reason ?? new CancelError() :
new Error(`Process exited with code: ${exitCode}`);
reject(reason);
}
subscription?.unsubscribe();
});
proc.on("error", error => {
reject(error);
subscription?.unsubscribe();
});
}
else {
@@ -150,8 +160,12 @@ export function getDirSize(root) {
.reduce((acc, num) => acc + num, 0);
}
class Deferred {
/**
* @template T
*/
export class Deferred {
constructor() {
/** @type {Promise<T>} */
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
@@ -162,13 +176,15 @@ class Deferred {
export class Debouncer {
/**
* @param {number} timeout
* @param {() => Promise<any>} action
* @param {() => Promise<any> | void} action
*/
constructor(timeout, action) {
this._timeout = timeout;
this._action = action;
}
get empty() { return !this._deferred; }
enqueue() {
if (this._timer) {
clearTimeout(this._timer);