Enable multiple test workers
MODIFY
packages/@romejs/cli-reporter/ProgressBase.ts
packages/@romejs/cli-reporter/ProgressBase.ts
@@ -72,7 +72,7 @@ export default class ProgressBase implements ReporterProgress {
getText(): undefined | string {
const {text} = this;
- if (text === undefined) {
+ if (text === undefined || text === '') {
return undefined;
} else {
return this.reporter.stripMarkup(text);

MODIFY
packages/@romejs/cli/testWorker.ts
packages/@romejs/cli/testWorker.ts
import setProcessTitle from './utils/setProcessTitle';
import {TestWorker} from '@romejs/core';
+import {parseCLIFlagsFromProcess} from '@romejs/cli-flags';
+import {TestWorkerFlags} from '@romejs/core/test-worker/TestWorker';
-export default function testWorker() {
+export default async function testWorker() {
setProcessTitle('test-worker');
+
+ const parser = parseCLIFlagsFromProcess({
+ programName: 'rome test-worker',
+ defineFlags(c): TestWorkerFlags {
+ return {
+ inspectorPort: c.get('inspectorPort').asNumberFromString(),
+ };
+ },
+ });
+ const flags: TestWorkerFlags = await parser.init();
+
const worker = new TestWorker();
- worker.init();
+ worker.init(flags);
}

MODIFY
packages/@romejs/consume/Consumer.ts
packages/@romejs/consume/Consumer.ts
@@ -976,6 +976,44 @@ export default class Consumer {
return coerce1(this.asNumber());
}
+ asNumberFromString(def?: number): number {
+ this.declareDefinition({
+ type: 'number',
+ default: def,
+ required: true,
+ });
+ return this._asNumberFromString(def);
+ }
+
+ asNumberFromStringOrVoid(def?: number): undefined | number {
+ this.declareDefinition({
+ type: 'number',
+ default: def,
+ required: false,
+ });
+
+ if (this.exists()) {
+ return this._asNumberFromString(def);
+ } else {
+ return undefined;
+ }
+ }
+
+ _asNumberFromString(def?: number): number {
+ if (def !== undefined && !this.exists()) {
+ return def;
+ }
+
+ const str = this._asString();
+ const num = Number(str);
+ if (isNaN(num)) {
+ this.unexpected('Expected valid number');
+ return NaN;
+ } else {
+ return num;
+ }
+ }
+
asNumber(def?: number): number {
this.declareDefinition({
type: 'number',

MODIFY
packages/@romejs/core/common/utils/fork.ts
packages/@romejs/core/common/utils/fork.ts
@@ -10,9 +10,10 @@ import child = require('child_process');
export default function fork(
processType: string,
- opts?: child.ForkOptions,
+ opts: child.ForkOptions = {},
+ args: Array<string> = [],
): child.ChildProcess {
- return child.fork(BIN.join(), [], {
+ return child.fork(BIN.join(), args, {
stdio: 'inherit',
execArgv: CHILD_ARGS,
...opts,

MODIFY
packages/@romejs/core/master/testing/TestRunner.ts
packages/@romejs/core/master/testing/TestRunner.ts
@@ -46,6 +46,9 @@ import {
} from './types';
import {percentInsideCoverageFolder, formatPercent, sortMapKeys} from './utils';
import {escapeMarkup, markup} from '@romejs/string-markup';
+import {MAX_WORKER_COUNT} from '@romejs/core/common/constants';
+import {TestWorkerFlags} from '@romejs/core/test-worker/TestWorker';
+import net = require('net');
class BridgeStructuredError extends NativeStructuredError {
constructor(struct: Partial<StructuredError>, bridge: Bridge) {
@@ -62,6 +65,25 @@ function getProgressTestRefText(ref: TestRef) {
)}`;
}
+function findAvailablePort(): Promise<number> {
+ return new Promise((resolve, reject) => {
+ // When you create a server without specifying a port then the OS will choose a port number for you!
+ const server = net.createServer();
+ server.unref();
+ server.on('error', reject);
+ server.listen(undefined, () => {
+ const address = server.address();
+ if (address == null || typeof address === 'string') {
+ throw new Error('Invalid address value');
+ }
+
+ server.close(() => {
+ resolve(address.port);
+ });
+ });
+ });
+}
+
export default class TestRunner {
constructor(opts: TestRunnerConstructorOptions) {
this.sources = opts.sources;
@@ -221,10 +243,10 @@ export default class TestRunner {
}
}
- async spawnWorker(): Promise<TestWorkerContainer> {
+ async spawnWorker(flags: TestWorkerFlags): Promise<TestWorkerContainer> {
const proc = fork('test-worker', {
stdio: 'pipe',
- });
+ }, ['--inspector-port', String(flags.inspectorPort)]);
const {stdout, stderr} = proc;
if (stdout == null || stderr == null) {
@@ -283,7 +305,14 @@ export default class TestRunner {
}
async setupWorkers(): Promise<TestWorkerContainers> {
- const containers: TestWorkerContainers = [await this.spawnWorker()];
+ // TODO some smarter logic. we may not need all these workers
+ const containerPromises: Array<Promise<TestWorkerContainer>> = [];
+ for (let i = 0; i < MAX_WORKER_COUNT; i++) {
+ const inspectorPort = await findAvailablePort();
+ containerPromises.push(this.spawnWorker({inspectorPort}));
+ }
+
+ const containers: TestWorkerContainers = await Promise.all(containerPromises);
// Every 5 seconds, ping the worker and wait a max of 5 seconds, if we receive no response then consider the worker dead
for (const container of containers) {
@@ -473,7 +502,7 @@ export default class TestRunner {
const progress = this.request.reporter.progress({
persistent: true,
- title: 'Running tests',
+ title: 'Running',
});
for (let i = 0; i < workers.length; i++) {

MODIFY
packages/@romejs/core/test-worker/TestWorker.ts
packages/@romejs/core/test-worker/TestWorker.ts
@@ -12,6 +12,10 @@ import {createBridgeFromParentProcess} from '@romejs/events';
import TestWorkerRunner from './TestWorkerRunner';
import inspector = require('inspector');
+export type TestWorkerFlags = {
+ inspectorPort: number;
+};
+
export default class TestWorker {
constructor() {
this.bridge = this.buildBridge();
@@ -21,9 +25,8 @@ export default class TestWorker {
runners: Map<number, TestWorkerRunner>;
bridge: TestWorkerBridge;
- async init() {
- // TODO randomly generate an open port
- inspector.open();
+ async init(flags: TestWorkerFlags) {
+ inspector.open(flags.inspectorPort);
await this.bridge.handshake();
}

Enable multiple test workers