Files
react-native-devtools-frontend/test/shared/master.ts
T
Paul Lewis ae5e8fb631 Adds support for e2e parallelization
Change-Id: I9af281bc40223a5ceb2a6f71ce247970beea4b02
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2116050
Commit-Queue: Paul Lewis <aerotwist@chromium.org>
Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
2020-03-25 16:45:33 +00:00

197 lines
4.8 KiB
TypeScript

// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable no-console */
// no-console disabled here as this is a test runner and expects to output to the console
import {ChildProcessWithoutNullStreams, spawn} from 'child_process';
import * as cluster from 'cluster';
import {join} from 'path';
import {WorkerMessage} from './worker-message.js';
let envJobs = Number(process.env['JOBS']);
if (Number.isNaN(envJobs) || envJobs < 1) {
envJobs = 4;
}
// Multiple workers in debug mode is difficult to manage, so drop to a single worker.
const envDebug = !!process.env['DEBUG'];
if (envDebug) {
envJobs = 1;
}
const workerCount = envJobs;
const taskList: string[] = [];
const ports = new Map<cluster.Worker, number>();
export function purge() {
taskList.length = 0;
}
export function killWorkers() {
const workerNames = Object.keys(cluster.workers);
for (const workerName of workerNames) {
const worker = cluster.workers[workerName];
if (!worker) {
continue;
}
worker.process.kill('SIGTERM');
}
}
export function enqueue(tasks: string[]) {
for (const task of tasks) {
taskList.push(task);
}
// Wake up the workers.
const workerNames = Object.keys(cluster.workers);
for (const workerName of workerNames) {
const worker = cluster.workers[workerName];
if (!worker) {
continue;
}
worker.send({ping: true});
}
}
let hostedModeServer: ChildProcessWithoutNullStreams;
let exitCode = 0;
let finishCount = 0;
export function init() {
finishCount = workerCount;
console.log(`Creating ${workerCount} worker${(workerCount === 1 ? '' : 's')}.`);
// Silence the stdout of individual workers.
cluster.settings.silent = true;
for (let w = 0; w < workerCount; w++) {
const worker = cluster.fork({silent: true});
worker.on('message', onWorkerMessage);
worker.on('exit', onWorkerDisconnect);
ports.set(worker, 9222 + w);
}
console.log('Spawning hosted mode server');
const serverScriptPath = join(__dirname, '..', '..', 'scripts', 'hosted_mode', 'server.js');
const cwd = join(__dirname, '..', '..');
const {execPath} = process;
hostedModeServer = spawn(execPath, [serverScriptPath], {cwd});
hostedModeServer.on('error', handleHostedModeError);
hostedModeServer.stderr.on('data', handleHostedModeError);
// Debug mode: write out server responses.
if (envDebug) {
hostedModeServer.stdout.on('data', (message: any) => {
console.log(`Hosted mode server: ${message}`);
});
}
}
function handleHostedModeError(data: Error) {
console.log(`Hosted mode server: ${data}`);
interruptionHandler();
}
export function interruptionHandler() {
exitCode = 1;
shutdown();
}
function shutdown() {
killWorkers();
console.log('\n');
console.log('Stopping hosted mode server');
hostedModeServer.kill();
console.log(`Exiting with status code ${exitCode}`);
process.exit(exitCode);
}
function locateWorkerByPid(pid: number): cluster.Worker {
const workerName = Object.keys(cluster.workers).find(key => {
const worker = cluster.workers[key];
if (!worker) {
return;
}
return worker.process.pid === pid;
});
if (!workerName) {
throw new Error(`Received message from pid ${pid}, but was unable to find a corresponding worker`);
}
return cluster.workers[workerName] as cluster.Worker;
}
const envVerbose = !!process.env['VERBOSE'];
function log(pid: number, msg: string, alwaysShow = false) {
const showMessages = envVerbose || alwaysShow;
if (!showMessages) {
return;
}
console.log(`Worker (${pid}): ${msg}`);
}
function onWorkerMessage({pid, details}: {pid: number, details: WorkerMessage}) {
const worker = locateWorkerByPid(pid);
switch (details) {
case 'requestTask': {
const task = taskList.shift();
if (!task) {
log(pid, 'Finished');
finishCount--;
if (finishCount === 0 && !envDebug) {
shutdown();
}
break;
}
log(pid, `Requested task, being given ${task}`);
worker.send({
type: 'task',
task,
});
break;
}
case 'notifyInit': {
log(pid, 'Ready - Sending port');
worker.send({
port: ports.get(worker),
});
break;
}
case 'failure': {
log(pid, 'Failure');
exitCode = 1;
break;
}
// All other messages, e.g. pass/fail from tests.
default: {
console.log(details);
break;
}
}
}
function onWorkerDisconnect(code: number, signal: NodeJS.Signals) {
if (signal) {
console.log('Worker killed by ' + signal);
} else if (code) {
console.log('Worker exited with code ' + code);
} else {
console.log('Worker exited successfully');
}
}