Add Bridge#callSync
MODIFY
packages/@romejs/core/master/testing/TestRunner.ts
packages/@romejs/core/master/testing/TestRunner.ts
@@ -18,6 +18,7 @@ import {
InspectorClient,
CoverageCollector,
sourceMapManager,
+ NativeStructuredError,
} from '@romejs/v8';
import {deriveDiagnosticFromError} from '@romejs/diagnostics';
import {TestWorkerBridge} from '@romejs/core';
@@ -26,7 +27,7 @@ import {ManifestDefinition} from '@romejs/codec-js-manifest';
import {createAbsoluteFilePath, AbsoluteFilePath} from '@romejs/path';
import {urlToFilename} from '@romejs/v8';
import {coerce0to1} from '@romejs/ob1';
-import {createErrorFromStructure, ErrorFrame} from '@romejs/v8';
+import {ErrorFrame} from '@romejs/v8';
import {BridgeError} from '@romejs/events';
import {
TestRunnerConstructorOptions,
@@ -39,6 +40,8 @@ import {
} from './types';
import {percentInsideCoverageFolder, formatPercent, sortMapKeys} from './utils';
+class BridgeStructuredError extends NativeStructuredError {}
+
export default class TestRunner {
constructor(opts: TestRunnerConstructorOptions) {
this.sources = opts.sources;
@@ -148,7 +151,7 @@ export default class TestRunner {
try {
await nextTest();
} catch (err) {
- if (err instanceof BridgeError) {
+ if (err instanceof BridgeError || err instanceof BridgeStructuredError) {
// When a worker dies, we automatically mark all it's currently running tests as failed
// However, it will cause all the pending messages to throw an error which will bubble up here
// We can safely ignore these since we've already handled it
@@ -273,10 +276,11 @@ export default class TestRunner {
): Promise<void> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
+ resolve(
container.bridge.end(
`Test worker was unresponsive for ${duration}. We tried to collect some additional metadata but we timed out again trying to fetch it...`,
+ ),
);
- resolve();
}, 3000);
this._handleWorkerTimeout(duration, container)
@@ -287,10 +291,9 @@ export default class TestRunner {
.catch(err => {
clearTimeout(timeout);
if (err instanceof InspectorClientCloseError) {
- container.bridge.end(
+ return container.bridge.end(
`Test worker was unresponsive for ${duration}. We tried to collect some additional metadata but the inspector connection closed abruptly`,
);
- resolve();
} else {
reject(err);
}
@@ -353,7 +356,7 @@ export default class TestRunner {
}
bridge.endWithError(
- createErrorFromStructure({
+ new BridgeStructuredError({
message: `Test worker was unresponsive for <emphasis>${duration}</emphasis>. Possible infinite loop. Below is a stack trace before the test was terminated.`,
frames,
}),
@@ -427,7 +430,7 @@ export default class TestRunner {
const ourRunningTests: Set<string> = new Set();
bridge.endEvent.subscribe(error => {
- // Fail all currently running tests
+ // Cancel all currently running tests
for (const key of ourRunningTests) {
const test = this.runningTests.get(key);
if (test === undefined) {
@@ -437,18 +440,11 @@ export default class TestRunner {
const {ref} = test;
this.onTestFinished(ref);
- this.printer.addDiagnostic(
- deriveDiagnosticFromError({
- error,
+ this.printer.addDiagnostic({
category: ref.testName,
filename: ref.filename,
- }),
- {
- category: 'test',
- message:
- 'Worker died and this diagnostic was automatically generated to fail a test',
- },
- );
+ message: 'Test was cancelled',
+ });
}
});

MODIFY
packages/@romejs/core/test-worker/TestAPI.ts
packages/@romejs/core/test-worker/TestAPI.ts
@@ -523,7 +523,7 @@ export default class TestAPI {
_snapshotNamed(
name: string,
expected: unknown,
- message: string = "Snapshots don't match",
+ message: string = `Snapshot ${name} doesn't match`,
framesToPop?: number,
) {
let language: undefined | string;

MODIFY
packages/@romejs/core/test-worker/TestWorkerRunner.ts
packages/@romejs/core/test-worker/TestWorkerRunner.ts
@@ -322,6 +322,10 @@ export default class TestWorkerRunner {
// if there's 5 promises, then wait for one of them to finish
if (promises.size > MAX_RUNNING_TESTS) {
await Promise.race(Array.from(promises));
+
+ await new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
}
}

MODIFY
packages/@romejs/events/Bridge.ts
packages/@romejs/events/Bridge.ts
@@ -254,7 +254,7 @@ export default class Bridge {
}
// Notify listeners
- this.endEvent.send(err);
+ this.endEvent.callSync(err);
}
end(message: string = 'Connection died') {

MODIFY
packages/@romejs/events/Event.ts
packages/@romejs/events/Event.ts
@@ -9,6 +9,14 @@ import {EventOptions, EventSubscription} from './types';
type Callback<Param, Ret> = (param: Param) => Ret | Promise<Ret>;
+function noPromise<Ret>(ret: Ret | Promise<Ret>): Ret {
+ if (ret instanceof Promise) {
+ throw new Error('Subscription returned promise for a callSync');
+ } else {
+ return ret;
+ }
+}
+
export default class Event<Param, Ret = void> {
constructor(opts: EventOptions) {
this.subscriptions = new Set();
@@ -60,6 +68,24 @@ export default class Event<Param, Ret = void> {
}
}
+ callSync(param: Param): Ret {
+ try {
+ const {rootSubscription, subscriptions} = this;
+ if (rootSubscription === undefined) {
+ throw new Error(`No subscription for event ${this.name}`);
+ }
+
+ const ret = noPromise(rootSubscription(param));
+ for (const callback of subscriptions) {
+ noPromise(callback(param));
+ }
+ return ret;
+ } catch (err) {
+ this.onError(err);
+ throw err;
+ }
+ }
+
async call(param: Param): Promise<Ret> {
const {rootSubscription, subscriptions} = this;
if (rootSubscription === undefined) {

MODIFY
packages/@romejs/v8/errors.ts
packages/@romejs/v8/errors.ts
@@ -26,7 +26,7 @@ export type StructuredError = {
framesToPop: number;
};
-class NativeStructuredError extends Error {
+export class NativeStructuredError extends Error {
constructor(struct: Partial<StructuredError>) {
super(struct.message);
this.name = struct.name === undefined ? 'Error' : struct.name;

Add Bridge#callSync