Improve focused tests
MODIFY
packages/@romejs-runtime/rome/test.ts
packages/@romejs-runtime/rome/test.ts
@@ -85,7 +85,7 @@ export interface TestHelper {
message?: string,
opts?: TestSnapshotOptions,
): string;
- inlineSnapshot(received: unknown, expected?: string): void;
+ inlineSnapshot(received: unknown, expected?: string | boolean | number): void;
namedSnapshot(
name: string,
expected: unknown,
@@ -102,11 +102,7 @@ export type GlobalTestOptions =
| undefined
| {
dirname?: string;
- register?: (
- err: Error,
- opts: TestOptions,
- callback?: TestCallback,
- ) => void;
+ register?: (err: Error, opts: TestOptions, callback: TestCallback) => void;
};
type NamelessTestOptions = {
@@ -128,7 +124,7 @@ export const testOptions: NonNullable<GlobalTestOptions> =
function registerTest(
callsiteError: Error,
opts: TestOptions,
- callback: undefined | TestCallback,
+ callback: TestCallback,
) {
const register = testOptions.register;
@@ -147,37 +143,21 @@ function splitArgs(
args: TestRegisterFunctionArgs,
): {
options: TestOptions;
- callback: undefined | TestCallback;
+ callback: TestCallback;
} {
- const name: TestName = args[0];
+ const name = args.shift();
if (typeof name !== 'string' && !Array.isArray(name)) {
throw new Error('Expected test name to be a string or an array of strings');
}
- args.shift();
-
- let foundOptions;
- let options: NamelessTestOptions = {};
- let callback;
-
- // Try callback which will always be at the end
- const callbackOrOpts = args.pop();
- if (typeof callbackOrOpts === 'function' || callbackOrOpts === undefined) {
- callback = callbackOrOpts;
- } else if (isOptionsObject(callbackOrOpts)) {
- options = callbackOrOpts;
- foundOptions = true;
- } else {
- throw new Error('Expected to find callback or options at the end');
- }
- // Try options which should be in the middle position
- if (!foundOptions && args.length > 0) {
- const maybeOptions = args.pop();
- if (isOptionsObject(maybeOptions)) {
- options = maybeOptions;
- } else {
- throw new Error('Expected to find test options');
+ const callback = args.pop();
+ if (typeof callback !== 'function') {
+ throw new Error('Expected options callback');
}
+
+ const options = args.pop();
+ if (!isOptionsObject(options)) {
+ throw new Error('Expected options object');
}
if (args.length > 0) {
@@ -194,28 +174,18 @@ function splitArgs(
}
type TestRegisterFunctionArgs =
- | [TestName]
| [TestName, TestCallback]
| [TestName, NamelessTestOptions, TestCallback];
type TestRegisterFunction = (...args: TestRegisterFunctionArgs) => void;
-export const test: {
- (
- ...args: TestRegisterFunctionArgs
- ): void;
- skip: TestRegisterFunction;
+export const test: TestRegisterFunction & {
only: TestRegisterFunction;
} = function(...args: TestRegisterFunctionArgs) {
const {options, callback} = splitArgs(args);
registerTest(new Error(), options, callback);
};
-test.skip = function(...args: TestRegisterFunctionArgs) {
- const {options} = splitArgs(args);
- registerTest(new Error(), options, undefined);
-};
-
test.only = function(...args: TestRegisterFunctionArgs) {
const {options, callback} = splitArgs(args);
registerTest(

MODIFY
packages/@romejs/cli-diagnostics/printAdvice.ts
packages/@romejs/cli-diagnostics/printAdvice.ts
@@ -18,7 +18,7 @@ import {
DiagnosticAdviceList,
DiagnosticAdviceLog,
DiagnosticAdviceStacktrace,
- getDiagnosticHeader,
+ diagnosticLocationToMarkupFilelink,
} from '@romejs/diagnostics';
import {Position} from '@romejs/parser-core';
import {showInvisibles, toLines} from './utils';
@@ -390,7 +390,7 @@ function printStacktrace(
// Add source
if (filename !== undefined && line !== undefined && column !== undefined) {
- const header = getDiagnosticHeader({
+ const header = diagnosticLocationToMarkupFilelink({
filename,
start: {
index: ob1Number0Neg1,

MODIFY
packages/@romejs/core/common/bridges/TestWorkerBridge.ts
packages/@romejs/core/common/bridges/TestWorkerBridge.ts
@@ -9,14 +9,17 @@ import {Diagnostic, DiagnosticOrigin} from '@romejs/diagnostics';
import {TestMasterRunnerOptions} from '../../master/testing/types';
import {Bridge} from '@romejs/events';
import {JSONFileReference} from '../types/files';
-import {TestWorkerFileResult} from '@romejs/core/test-worker/TestWorkerRunner';
+import {
+ FocusedTest,
+ TestWorkerFileResult,
+} from '@romejs/core/test-worker/TestWorkerRunner';
export type TestRef = {
filename: string;
testName: string;
};
-export type TestWorkerBridgeRunOptions = {
+export type TestWorkerPrepareTestOptions = {
id: number;
file: JSONFileReference;
projectFolder: string;
@@ -25,6 +28,15 @@ export type TestWorkerBridgeRunOptions = {
options: TestMasterRunnerOptions;
};
+export type TestWorkerPrepareTestResult = {
+ focusedTests: Array<FocusedTest>;
+};
+
+export type TestWorkerRunTestOptions = {
+ id: number;
+ onlyFocusedTests: boolean;
+};
+
export default class TestWorkerBridge extends Bridge {
inspectorDetails = this.createEvent<
void,
@@ -36,23 +48,20 @@ export default class TestWorkerBridge extends Bridge {
direction: 'server->client',
});
- prepareTest = this.createEvent<TestWorkerBridgeRunOptions, void>({
+ prepareTest = this.createEvent<
+ TestWorkerPrepareTestOptions,
+ TestWorkerPrepareTestResult
+ >({
name: 'prepareTest',
direction: 'server->client',
});
- runTest = this.createEvent<number, TestWorkerFileResult>({
+ runTest = this.createEvent<TestWorkerRunTestOptions, TestWorkerFileResult>({
name: 'runTest',
direction: 'server->client',
});
- testsFound = this.createEvent<
- Array<{
- ref: TestRef;
- isSkipped: boolean;
- }>,
- void
- >({
+ testsFound = this.createEvent<Array<TestRef>, void>({
name: 'onTestFounds',
direction: 'server<-client',
});

MODIFY
packages/@romejs/core/master/commands/ci.ts
packages/@romejs/core/master/commands/ci.ts
@@ -74,6 +74,7 @@ export default createMasterCommand({
await test.callback(
req,
{
+ focusAllowed: false,
coverage: false,
freezeSnapshots: !flags.fix,
updateSnapshots: flags.fix,

MODIFY
packages/@romejs/core/master/commands/test.ts
packages/@romejs/core/master/commands/test.ts
@@ -28,21 +28,21 @@ export default createMasterCommand({
showAllCoverage: c.get('showAllCoverage').asBoolean(false),
updateSnapshots: c.get('updateSnapshots').asBoolean(false),
freezeSnapshots: c.get('freezeSnapshots').asBoolean(false),
+ focusAllowed: c.get('focusAllowed').asBoolean(true),
syncTests: c.get('syncTests').asBoolean(false),
};
},
async callback(req: MasterRequest, commandFlags: Flags): Promise<void> {
- const {reporter} = req;
-
const {paths} = await req.getFilesFromArgs({
- tryAlternateArg: (path) =>
- path.hasExtension('test')
- ? undefined
- : path.getParent().append(
+ tryAlternateArg: (path) => {
+ if (path.hasExtension('test')) {
+ return undefined;
+ } else {
+ return path.getParent().append(
`${path.getExtensionlessBasename()}.test${path.getExtensions()}`,
- )
- ,
-
+ );
+ }
+ },
test: (path) => path.hasExtension('test'),
noun: 'test',
verb: 'testing',
@@ -58,8 +58,6 @@ export default createMasterCommand({
disabledDiagnosticCategory: 'tests/disabled',
});
- reporter.info(`Bundling test files`);
-
let addDiagnostics: Diagnostics = [];
const tests: TestSources = new Map();
@@ -87,16 +85,10 @@ export default createMasterCommand({
);
}
- reporter.info(`Running tests`);
-
const runner = new TestMasterRunner({
addDiagnostics,
options: {
- coverage: commandFlags.coverage,
- showAllCoverage: commandFlags.showAllCoverage,
- updateSnapshots: commandFlags.updateSnapshots,
- freezeSnapshots: commandFlags.freezeSnapshots,
- syncTests: commandFlags.syncTests,
+ ...commandFlags,
verboseDiagnostics: req.query.requestFlags.verboseDiagnostics,
},
sources: tests,

MODIFY
packages/@romejs/core/master/testing/TestMasterRunner.ts
packages/@romejs/core/master/testing/TestMasterRunner.ts
* LICENSE file in the root directory of this source tree.
*/
-import {Reporter} from '@romejs/cli-reporter';
-import {deriveDiagnosticFromError, descriptions} from '@romejs/diagnostics';
+import {Reporter, ReporterProgress} from '@romejs/cli-reporter';
+import {
+ deriveDiagnosticFromError,
+ descriptions,
+ diagnosticLocationToMarkupFilelink,
+} from '@romejs/diagnostics';
import {TestRef} from '../../common/bridges/TestWorkerBridge';
import {Master, MasterRequest, TestWorkerBridge} from '@romejs/core';
import {DiagnosticsPrinter} from '@romejs/cli-diagnostics';
@@ -41,11 +45,14 @@ import {
TestWorkerContainers,
} from './types';
import {formatPercent, percentInsideCoverageFolder, sortMapKeys} from './utils';
-import {escapeMarkup, markup} from '@romejs/string-markup';
+import {escapeMarkup, markup, safeMarkup} 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');
-import {TestWorkerFileResult} from '@romejs/core/test-worker/TestWorkerRunner';
+import {
+ FocusedTest,
+ TestWorkerFileResult,
+} from '@romejs/core/test-worker/TestWorkerRunner';
import {FileReference} from '@romejs/core/common/types/files';
class BridgeStructuredError extends NativeStructuredError {
@@ -57,6 +64,10 @@ class BridgeStructuredError extends NativeStructuredError {
bridge: Bridge;
}
+function grammarNumberTests(num: number): string {
+ return `<grammarNumber plural="tests" singular="test">${num}</grammarNumber>`;
+}
+
function getProgressTestRefText(ref: TestRef) {
return markup`<filelink target="${ref.filename}" />: ${escapeMarkup(
ref.testName,
@@ -85,6 +96,10 @@ function findAvailablePort(): Promise<number> {
});
}
+type TestProgress = {
+ teardown: () => void;
+};
+
export default class TestMasterRunner {
constructor(opts: TestMasterRunnerConstructorOptions) {
this.sources = opts.sources;
@@ -96,7 +111,7 @@ export default class TestMasterRunner {
this.ignoreBridgeEndError = new Set();
- this.sourcesQueue = Array.from(opts.sources.entries());
+ this.sourcesQueue = Array.from(opts.sources.values());
this.coverageCollector = new CoverageCollector();
@@ -110,6 +125,8 @@ export default class TestMasterRunner {
updatedInlineSnapshots: 0,
};
+ this.focusedTests = [];
+
this.runningTests = new Map();
this.testFileCounter = 0;
@@ -142,7 +159,7 @@ export default class TestMasterRunner {
master: Master;
cwd: AbsoluteFilePath;
printer: DiagnosticsPrinter;
- sourcesQueue: Array<[string, TestSource]>;
+ sourcesQueue: Array<TestSource>;
testFileCounter: number;
ignoreBridgeEndError: Set<Bridge>;
@@ -164,6 +181,8 @@ export default class TestMasterRunner {
createdSnapshots: number;
};
+ focusedTests: Array<FocusedTest>;
+
async processTestResult(
ref: FileReference,
{inlineSnapshotUpdates, snapshotCounts}: TestWorkerFileResult,
@@ -207,7 +226,10 @@ export default class TestMasterRunner {
}
}
- async runWorker({bridge, process, inspector}: TestWorkerContainer) {
+ async prepareWorker(
+ {bridge, process, inspector}: TestWorkerContainer,
+ progress: ReporterProgress,
+ ): Promise<() => Promise<void>> {
const {options: opts, sourcesQueue} = this;
const req = this.request;
const {flags} = req.client;
@@ -225,29 +247,27 @@ export default class TestMasterRunner {
);
}
- const nextTest = async () => {
+ const tests: Array<{
+ ref: FileReference;
+ id: number;
+ }> = [];
+
+ const prepareTest = async () => {
if (sourcesQueue.length === 0) {
return;
}
- const item = sourcesQueue.pop();
- if (item === undefined) {
- throw new Error('testQueue.length was validated above');
- }
- const [filename, {ref, code, sourceMap}] = item;
-
- // Source map locations will always be resolved in the worker, but this is in case we need to resolve them in master in the case of an unresponsive worker
- // TODO remove this after test has ran
- const removeSourceMap = sourceMapManager.addSourceMap(
- filename,
- () => sourceMap.toConsumer(),
- );
+ const item = sourcesQueue.pop()!;
+ const {ref, code} = item;
const id = this.testFileCounter;
this.testFileCounter++;
+ const progressText = `<filelink target="${ref.uid}" />`;
+ progress.pushText(progressText);
+
try {
- await bridge.prepareTest.call({
+ const {focusedTests} = await bridge.prepareTest.call({
id,
options: opts,
projectFolder: req.master.projectManager.assertProjectExisting(
@@ -258,35 +278,39 @@ export default class TestMasterRunner {
code,
});
- const result = await bridge.runTest.call(id);
- await this.processTestResult(ref, result);
- } finally {
- removeSourceMap();
+ this.focusedTests = this.focusedTests.concat(focusedTests);
+
+ tests.push({id, ref});
+ } catch (err) {
+ this.handlePossibleBridgeError(err);
}
- await nextTest();
+ progress.popText(progressText);
+ progress.tick();
+
+ await prepareTest();
};
+ await prepareTest();
+
+ return async () => {
try {
- await nextTest();
- } catch (err) {
- if (err instanceof BridgeError || err instanceof BridgeStructuredError) {
- if (!this.ignoreBridgeEndError.has(err.bridge)) {
- this.printer.processor.addDiagnostic(
- deriveDiagnosticFromError({
- category: 'tests/timeout',
- error: err,
- }),
- );
- }
- } else {
- throw err;
+ for (const {ref, id} of tests) {
+ const result = await bridge.runTest.call({
+ id,
+ onlyFocusedTests: this.focusedTests.length > 0,
+ });
+ await this.processTestResult(ref, result);
}
+ } catch (err) {
+ this.handlePossibleBridgeError(err);
} finally {
if (inspector !== undefined) {
if (opts.coverage) {
if (inspector.alive) {
- const profile = await inspector.call('Profiler.takePreciseCoverage');
+ const profile = await inspector.call(
+ 'Profiler.takePreciseCoverage',
+ );
this.coverageCollector.addCoverage(profile.get('result').asAny());
// Not really necessary but let's clean up anyway for completeness
@@ -302,6 +326,22 @@ export default class TestMasterRunner {
process.kill();
}
+ };
+ }
+
+ handlePossibleBridgeError(err: Error) {
+ if (err instanceof BridgeError || err instanceof BridgeStructuredError) {
+ if (!this.ignoreBridgeEndError.has(err.bridge)) {
+ this.printer.processor.addDiagnostic(
+ deriveDiagnosticFromError({
+ category: 'tests/failure',
+ error: err,
+ }),
+ );
+ }
+ } else {
+ throw err;
+ }
}
async spawnWorker(flags: TestWorkerFlags): Promise<TestWorkerContainer> {
@@ -360,17 +400,27 @@ export default class TestMasterRunner {
const {inspectorUrl} = await bridge.inspectorDetails.call();
- let inspector;
+ let inspector: undefined | InspectorClient;
if (inspectorUrl !== undefined) {
- const locInspector = new InspectorClient(await createClient(inspectorUrl));
- inspector = locInspector;
- await locInspector.call('Debugger.enable');
+ const client = new InspectorClient(await createClient(inspectorUrl));
+ inspector = client;
+ await client.call('Debugger.enable');
bridge.endEvent.subscribe(() => {
- locInspector.end();
+ client.end();
});
}
+ bridge.testsFound.subscribe((tests) => {
+ for (const ref of tests) {
+ this.onTestFound(ref);
+ }
+ });
+
+ bridge.testDiagnostic.subscribe(({diagnostic, origin}) => {
+ this.printer.processor.addDiagnostic(diagnostic, origin);
+ });
+
return {
bridge,
process: proc,
@@ -405,15 +455,25 @@ export default class TestMasterRunner {
async init() {
this.workers = await this.setupWorkers();
- const teardown = this.setupProgress();
const workerContainers: TestWorkerContainers = this.getWorkers();
- await Promise.all(
- workerContainers.map((container) => this.runWorker(container)),
+ // Prepare all tests
+ const progress = this.reporter.progress({
+ title: 'Preparing',
+ });
+ progress.setTotal(this.sourcesQueue.length);
+ const callbacks = await Promise.all(
+ workerContainers.map((container) =>
+ this.prepareWorker(container, progress)
+ ),
);
+ progress.end();
- teardown();
+ // Run tests
+ const runProgress = this.setupRunProgress();
+ await Promise.all(callbacks.map((callback) => callback()));
+ runProgress.teardown();
this.throwPrinter();
}
@@ -527,6 +587,14 @@ export default class TestMasterRunner {
return `${ref.filename}: ${ref.testName}`;
}
+ getTotalTests(): number {
+ if (this.focusedTests.length > 0) {
+ return this.focusedTests.length;
+ } else {
+ return this.progress.totalTests;
+ }
+ }
+
onTestStart(
container: TestWorkerContainer,
ref: TestRef,
@@ -554,11 +622,7 @@ export default class TestMasterRunner {
);
}
- onTestFound(data: TestRef, isSkipped: boolean) {
- if (isSkipped) {
- return;
- }
-
+ onTestFound(data: TestRef) {
data;
this.progress.totalTests++;
}
@@ -578,13 +642,14 @@ export default class TestMasterRunner {
this.progress.finishedTests++;
}
- setupProgress(): () => void {
+ setupRunProgress(): TestProgress {
const workers = this.getWorkers();
const progress = this.request.reporter.progress({
persistent: true,
title: 'Running',
});
+ progress.setTotal(this.getTotalTests());
for (let i = 0; i < workers.length; i++) {
const container = workers[i];
@@ -637,23 +702,12 @@ export default class TestMasterRunner {
}
});
- bridge.testsFound.subscribe((tests) => {
- for (const {ref, isSkipped} of tests) {
- this.onTestFound(ref, isSkipped);
- }
- progress.setTotal(this.progress.totalTests);
- });
-
bridge.testStart.subscribe((data) => {
ourRunningTests.add(this.refToKey(data.ref));
this.onTestStart(container, data.ref, data.timeout);
progress.pushText(getProgressTestRefText(data.ref));
});
- bridge.testDiagnostic.subscribe(({diagnostic, origin}) => {
- this.printer.processor.addDiagnostic(diagnostic, origin);
- });
-
bridge.testFinish.subscribe((data) => {
this.onTestFinished(data.ref);
progress.popText(getProgressTestRefText(data.ref));
@@ -661,8 +715,10 @@ export default class TestMasterRunner {
});
}
- return function() {
+ return {
+ teardown() {
progress.end();
+ },
};
}
@@ -832,6 +888,39 @@ export default class TestMasterRunner {
}
}
+ printFocusedTestWarning(reporter: Reporter) {
+ const {focusedTests} = this;
+ if (focusedTests.length === 0) {
+ return;
+ }
+
+ const formattedFocusedTests = focusedTests.map(({testName, location}) => {
+ const loc = this.printer.processor.normalizer.normalizeLocation(location);
+
+ return markup`<emphasis>${testName}</emphasis> at <emphasis>${safeMarkup(
+ diagnosticLocationToMarkupFilelink(loc),
+ )}</emphasis>`;
+ });
+
+ if (focusedTests.length === 1) {
+ reporter.warn(`Only ran the focused test ${formattedFocusedTests[0]}`);
+ } else {
+ reporter.warn(
+ `Only ran the following <number emphasis>${focusedTests.length}</number> focused ${grammarNumberTests(
+ focusedTests.length,
+ )}`,
+ );
+ reporter.list(formattedFocusedTests);
+ }
+
+ const otherTotal = this.progress.totalTests - this.focusedTests.length;
+ reporter.info(
+ `<number emphasis>${otherTotal}</number> other ${grammarNumberTests(
+ otherTotal,
+ )} ignored`,
+ );
+ }
+
printSnapshotCounts(reporter: Reporter) {
const {
createdSnapshots,
@@ -879,10 +968,16 @@ export default class TestMasterRunner {
printer.onFooterPrint((reporter, isError) => {
this.printCoverageReport(isError);
this.printSnapshotCounts(reporter);
+ this.printFocusedTestWarning(reporter);
if (!isError) {
+ // Don't say "all" when we have focused tests
+ let prefix = this.focusedTests.length === 0 ? 'All ' : '';
+ const totalCount = this.getTotalTests();
reporter.success(
- `All <emphasis>${humanizeNumber(this.progress.totalTests)}</emphasis> tests passed!`,
+ `${prefix}<emphasis>${humanizeNumber(totalCount)}</emphasis> ${grammarNumberTests(
+ totalCount,
+ )} passed!`,
);
return true;
}

MODIFY
packages/@romejs/core/master/testing/types.ts
packages/@romejs/core/master/testing/types.ts
@@ -28,6 +28,7 @@ export type TestMasterRunnerConstructorOptions = {
};
export type TestMasterRunnerOptions = {
+ focusAllowed: boolean;
coverage: boolean;
showAllCoverage: boolean;
updateSnapshots: boolean;

MODIFY
packages/@romejs/core/test-worker/SnapshotManager.ts
packages/@romejs/core/test-worker/SnapshotManager.ts
@@ -266,7 +266,7 @@ export default class SnapshotManager {
async save() {
// If there'a s focused test then we don't write or validate a snapshot
- if (this.runner.hasFocusedTest) {
+ if (this.runner.hasFocusedTests) {
return;
}

MODIFY
packages/@romejs/core/test-worker/TestWorker.ts
packages/@romejs/core/test-worker/TestWorker.ts
* LICENSE file in the root directory of this source tree.
*/
-import {TestWorkerBridgeRunOptions} from '../common/bridges/TestWorkerBridge';
+import {
+ TestWorkerPrepareTestOptions,
+ TestWorkerPrepareTestResult,
+ TestWorkerRunTestOptions,
+} from '../common/bridges/TestWorkerBridge';
import {deriveDiagnosticFromError} from '@romejs/diagnostics';
import {TestWorkerBridge} from '@romejs/core';
import {createBridgeFromParentProcess} from '@romejs/events';
@@ -62,25 +66,28 @@ export default class TestWorker {
return this.prepareTest(data);
});
- bridge.runTest.subscribe((id: number) => {
- return this.runTest(id);
+ bridge.runTest.subscribe((opts) => {
+ return this.runTest(opts);
});
return bridge;
}
- async runTest(id: number): Promise<TestWorkerFileResult> {
+ async runTest(opts: TestWorkerRunTestOptions): Promise<TestWorkerFileResult> {
+ const {id} = opts;
const runner = this.runners.get(id);
if (runner === undefined) {
throw new Error(`No runner ${id} found`);
} else {
- return await runner.run();
+ return await runner.run(opts);
}
}
- async prepareTest(opts: TestWorkerBridgeRunOptions): Promise<void> {
+ async prepareTest(
+ opts: TestWorkerPrepareTestOptions,
+ ): Promise<TestWorkerPrepareTestResult> {
const runner = new TestWorkerRunner(opts, this.bridge);
- await runner.prepare();
this.runners.set(opts.id, runner);
+ return await runner.prepare();
}
}

MODIFY
packages/@romejs/core/test-worker/TestWorkerRunner.ts
packages/@romejs/core/test-worker/TestWorkerRunner.ts
@@ -9,6 +9,7 @@ import {UnknownObject} from '@romejs/typescript-helpers';
import {
Diagnostic,
DiagnosticAdvice,
+ DiagnosticLocation,
DiagnosticLogCategory,
DiagnosticOrigin,
INTERNAL_ERROR_LOG_ADVICE,
@@ -27,7 +28,9 @@ import {
import {
TestRef,
default as TestWorkerBridge,
- TestWorkerBridgeRunOptions,
+ TestWorkerPrepareTestOptions,
+ TestWorkerPrepareTestResult,
+ TestWorkerRunTestOptions,
} from '../common/bridges/TestWorkerBridge';
import {TestMasterRunnerOptions} from '../master/testing/types';
import SnapshotManager, {
@@ -42,7 +45,12 @@ import {
} from '../common/types/files';
import {AbsoluteFilePath, createAbsoluteFilePath} from '@romejs/path';
import {escapeMarkup, markup} from '@romejs/string-markup';
-import {ErrorFrames, getErrorStructure} from '@romejs/v8';
+import {
+ ErrorFrames,
+ createErrorFromStructure,
+ getErrorStructure,
+ getSourceLocationFromErrorFrame,
+} from '@romejs/v8';
import prettyFormat from '@romejs/pretty-format';
const MAX_RUNNING_TESTS = 20;
@@ -95,8 +103,18 @@ export type TestWorkerFileResult = {
inlineSnapshotUpdates: Array<InlineSnapshotUpdate>;
};
+type FoundTest = {
+ options: TestOptions;
+ callback: TestCallback;
+};
+
+export type FocusedTest = {
+ testName: string;
+ location: DiagnosticLocation;
+};
+
export default class TestWorkerRunner {
- constructor(opts: TestWorkerBridgeRunOptions, bridge: TestWorkerBridge) {
+ constructor(opts: TestWorkerPrepareTestOptions, bridge: TestWorkerBridge) {
this.opts = opts;
this.locked = false;
this.file = convertTransportFileReference(opts.file);
@@ -111,28 +129,25 @@ export default class TestWorkerRunner {
this.hasDiagnostics = false;
this.consoleAdvice = [];
- this.hasFocusedTest = false;
+ this.hasFocusedTests = false;
+ this.focusedTests = [];
+ this.pendingErrors = [];
this.foundTests = new Map();
}
- foundTests: Map<
- string,
- {
- callsiteError: Error;
- options: TestOptions;
- callback: undefined | TestCallback;
- }
- >;
- hasFocusedTest: boolean;
+ foundTests: Map<string, FoundTest>;
+ hasFocusedTests: boolean;
+ focusedTests: Array<FocusedTest>;
bridge: TestWorkerBridge;
projectFolder: AbsoluteFilePath;
file: FileReference;
options: TestMasterRunnerOptions;
snapshotManager: SnapshotManager;
- opts: TestWorkerBridgeRunOptions;
+ opts: TestWorkerPrepareTestOptions;
locked: boolean;
consoleAdvice: DiagnosticAdvice;
hasDiagnostics: boolean;
+ pendingErrors: Array<Error>;
createConsole(): Partial<Console> {
const addDiagnostic = (
@@ -213,7 +228,7 @@ export default class TestWorkerRunner {
register: (
callsiteError: Error,
opts: TestOptions,
- callback?: TestCallback,
+ callback: TestCallback,
) => {
this.registerTest(callsiteError, opts, callback);
},
@@ -291,7 +306,7 @@ export default class TestWorkerRunner {
registerTest(
callsiteError: Error,
options: TestOptions,
- callback: undefined | TestCallback,
+ callback: TestCallback,
) {
if (this.locked) {
throw new Error("Test can't be added outside of init");
@@ -311,12 +326,27 @@ export default class TestWorkerRunner {
{
callback,
options,
- callsiteError,
},
);
if (options.only === true) {
- this.hasFocusedTest = true;
+ const callsiteStruct = getErrorStructure(callsiteError, 1);
+
+ this.focusedTests.push({
+ testName,
+ location: getSourceLocationFromErrorFrame(callsiteStruct.frames[0]),
+ });
+
+ this.hasFocusedTests = true;
+
+ if (!this.options.focusAllowed) {
+ this.pendingErrors.push(
+ createErrorFromStructure({
+ ...callsiteStruct,
+ message: 'Focused tests are not allowed due to a set flag',
+ }),
+ );
+ }
}
}
@@ -447,7 +477,7 @@ export default class TestWorkerRunner {
}
}
- async run(): Promise<TestWorkerFileResult> {
+ async run(opts: TestWorkerRunTestOptions): Promise<TestWorkerFileResult> {
const promises: Set<Promise<void>> = new Set();
const {foundTests} = this;
@@ -463,9 +493,16 @@ export default class TestWorkerRunner {
});
}
+ // We could be pretending we have focused tests here but at least one file was execueted with
+ // focused tests
+ if (opts.onlyFocusedTests) {
+ this.hasFocusedTests = true;
+ }
+
// Execute all the tests
- for (const [testName, {options, callback}] of foundTests) {
- if (callback === undefined) {
+ for (const [testName, test] of foundTests) {
+ const {options, callback} = test;
+ if (this.hasFocusedTests && !test.options.only) {
continue;
}
@@ -509,6 +546,10 @@ export default class TestWorkerRunner {
});
}
+ for (const error of this.pendingErrors) {
+ await this.onError(undefined, {error});
+ }
+
return {
inlineSnapshotUpdates: this.snapshotManager.inlineSnapshotsUpdates,
snapshotCounts: this.snapshotManager.snapshotCounts,
@@ -516,20 +557,12 @@ export default class TestWorkerRunner {
}
async emitFoundTests() {
- const tests = [];
-
- for (const [testName, {callback, options}] of this.foundTests) {
- let isSkipped = callback === undefined;
- if (this.hasFocusedTest && options.only !== true) {
- isSkipped = true;
- }
+ const tests: Array<TestRef> = [];
+ for (const testName of this.foundTests.keys()) {
tests.push({
- ref: {
filename: this.file.real.join(),
testName,
- },
- isSkipped,
});
}
@@ -564,15 +597,13 @@ export default class TestWorkerRunner {
}
}
- async prepare(): Promise<void> {
- return this.wrap(async () => {
- // Setup
+ async prepare(): Promise<TestWorkerPrepareTestResult> {
+ await this.wrap(async () => {
await this.snapshotManager.init();
await this.discoverTests();
await this.emitFoundTests();
-
- // Execute
this.lockTests();
});
+ return {focusedTests: this.focusedTests};
}
}

MODIFY
packages/@romejs/diagnostics/derive.ts
packages/@romejs/diagnostics/derive.ts
@@ -11,8 +11,7 @@ import {
DiagnosticOrigin,
Diagnostics,
} from './types';
-import {Position} from '@romejs/parser-core';
-import {escapeMarkup, markup} from '@romejs/string-markup';
+import {escapeMarkup} from '@romejs/string-markup';
import {
ErrorFrames,
getErrorStructure,
@@ -21,6 +20,7 @@ import {
import {DiagnosticCategory} from './categories';
import {createBlessedDiagnosticMessage} from './descriptions';
import DiagnosticsNormalizer from './DiagnosticsNormalizer';
+import {diagnosticLocationToMarkupFilelink} from './helpers';
function normalizeArray<T>(val: undefined | Array<T>): Array<T> {
if (Array.isArray(val)) {
@@ -55,25 +55,6 @@ export function mergeDiagnostics(
};
}
-export function getDiagnosticHeader(
- opts: {
- filename: undefined | string;
- start: undefined | Position;
- },
-): string {
- const {start, filename} = opts;
-
- if (filename === undefined) {
- return 'unknown';
- }
-
- if (start === undefined) {
- return markup`<filelink target="${filename}" />`;
- }
-
- return markup`<filelink target="${filename}" line="${start.line}" column="${start.column}" />`;
-}
-
export function derivePositionlessKeyFromDiagnostic(diag: Diagnostic): string {
const normalizer = new DiagnosticsNormalizer({
stripPositions: true,
@@ -100,10 +81,7 @@ export function deriveRootAdviceFromDiagnostic(
const advice: DiagnosticAdvice = [];
const {description, fixable, location} = diag;
- let header = getDiagnosticHeader({
- start: location.start,
- filename: location.filename,
- });
+ let header = diagnosticLocationToMarkupFilelink(location);
if (diag.label !== undefined) {
header += ` <emphasis>${diag.label}</emphasis>`;

MODIFY
packages/@romejs/diagnostics/helpers.ts
packages/@romejs/diagnostics/helpers.ts
@@ -149,3 +149,19 @@ export function buildDuplicateLocationAdvice(
...locationAdvice,
];
}
+
+export function diagnosticLocationToMarkupFilelink(
+ loc: DiagnosticLocation,
+): string {
+ const {start, filename} = loc;
+
+ if (filename === undefined) {
+ return 'unknown';
+ }
+
+ if (start === undefined) {
+ return markup`<filelink target="${filename}" />`;
+ }
+
+ return markup`<filelink target="${filename}" line="${start.line}" column="${start.column}" />`;
+}

Improve focused tests