Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions packages/browser/src/client/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import {
} from '@rstest/browser-manifest';
import type {
CoverageMapData,
CurrentTaskInfo,
RunnerHooks,
RuntimeConfig,
WorkerState,
} from '@rstest/core/browser-runtime';
import {
createBrowserTaskContext,
createRstestRuntime,
getCurrentTask,
globalApis,
initTaskContext,
setRealTimers,
} from '@rstest/core/browser-runtime';
import { normalize } from 'pathe';
Expand Down Expand Up @@ -152,14 +152,12 @@ const getFileTaskId = (testPath: string): string => {
return `file:${testPath}`;
};

type CurrentTaskInfo = NonNullable<WorkerState['currentTask']>;

/**
* Intercept console methods and forward to host via send().
* Returns a restore function to revert console to original.
*/
const interceptConsole = (
getCurrentTaskFallback: () => CurrentTaskInfo | undefined,
getCurrentTask: () => CurrentTaskInfo | undefined,
printConsoleTrace: boolean,
): (() => void) => {
const originalConsole = {
Expand All @@ -186,7 +184,7 @@ const interceptConsole = (

// Format message
const content = args.map(formatArg).join(' ');
const currentTask = getCurrentTask() ?? getCurrentTaskFallback();
const currentTask = getCurrentTask();

// Send to host
send({
Expand Down Expand Up @@ -421,7 +419,6 @@ const run = async () => {
send({ type: 'ready' });

setRealTimers();
await initTaskContext();

// Preload runner.js sourcemap for inline snapshot support.
// The snapshot code runs in runner.js, so we need its sourcemap
Expand Down Expand Up @@ -529,7 +526,9 @@ const run = async () => {
},
};

const runtime = await createRstestRuntime(workerState);
const runtime = await createRstestRuntime(workerState, {
taskContext: createBrowserTaskContext(),
});

// Register global APIs if globals config is enabled
if (runtimeConfig.globals) {
Expand Down Expand Up @@ -587,6 +586,10 @@ const run = async () => {
},
];

// Per-file TaskContext; taskStack supplies the concurrent attribution
// that the single-slot fallback can't.
const taskContext = createBrowserTaskContext();

const shouldInterceptConsole =
!runtimeConfig.disableConsoleIntercept ||
runtimeConfig.silent === true ||
Expand All @@ -595,7 +598,7 @@ const run = async () => {
// Intercept console methods to forward logs to host
const restoreConsole = shouldInterceptConsole
? interceptConsole(
() => taskStack[taskStack.length - 1],
() => taskContext.getCurrent() ?? taskStack[taskStack.length - 1],
runtimeConfig.disableConsoleIntercept
? false
: (runtimeConfig.printConsoleTrace ?? false),
Expand Down Expand Up @@ -635,7 +638,7 @@ const run = async () => {
syncCurrentTask();
};

const runtime = await createRstestRuntime(workerState);
const runtime = await createRstestRuntime(workerState, { taskContext });

// Register global APIs if globals config is enabled
if (runtimeConfig.globals) {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/browserRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ export { createRstestRuntime } from './runtime/api';
// Public test APIs (describe, it, expect, etc.)
export * from './runtime/api/public';
export { setRealTimers } from './runtime/util';
export { getCurrentTask, initTaskContext } from './runtime/worker/taskContext';
export { createBrowserTaskContext } from './runtime/worker/taskContext.browser';
export type { TaskContext } from './runtime/worker/taskContext';
// Types for browser runtime
export type {
CoverageMapData,
CurrentTaskInfo,
RunnerHooks,
RuntimeConfig,
Test,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/runtime/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import type {
WorkerState,
} from '../../types';
import { createRunner } from '../runner';
import type { TaskContext } from '../worker/taskContext';
import { assert, createExpect, GLOBAL_EXPECT, setupChaiConfig } from './expect';
import { createRstestUtilities } from './utilities';

export const createRstestRuntime = async (
workerState: WorkerState,
{ taskContext }: { taskContext: TaskContext },
): Promise<{
runner: {
runTests: (
Expand All @@ -26,7 +28,7 @@ export const createRstestRuntime = async (
api: Rstest;
}> => {
const [{ runner, api: runnerAPI }, { SnapshotPlugin }] = await Promise.all([
Promise.resolve(createRunner({ workerState })),
Promise.resolve(createRunner({ workerState, taskContext })),
import(/* webpackChunkName: "snapshot" */ './snapshot'),
]);

Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/runtime/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
TestInfo,
WorkerState,
} from '../../types';
import type { TaskContext } from '../worker/taskContext';
import { TestRunner } from './runner';
import { createRuntimeAPI } from './runtime';
import { traverseUpdateTest } from './task';
Expand All @@ -15,7 +16,13 @@ export const getFileTaskId = (testPath: string): string => {
return `file:${testPath}`;
};

export function createRunner({ workerState }: { workerState: WorkerState }): {
export function createRunner({
workerState,
taskContext,
}: {
workerState: WorkerState;
taskContext: TaskContext;
}): {
api: RunnerAPI;
runner: {
runTests: (
Expand All @@ -37,7 +44,7 @@ export function createRunner({ workerState }: { workerState: WorkerState }): {
testPath,
runtimeConfig: workerState.runtimeConfig,
});
const testRunner: TestRunner = new TestRunner();
const testRunner: TestRunner = new TestRunner(taskContext);

return {
api: {
Expand Down
15 changes: 7 additions & 8 deletions packages/core/src/runtime/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import type {
import { getTaskNameWithPrefix } from '../../utils/helper';
import { createExpect } from '../api/expect';
import { formatTestError } from '../util';
import {
runWithCurrentTask,
setFallbackCurrentTask,
} from '../worker/taskContext';
import type { TaskContext } from '../worker/taskContext';
import { handleFixtures } from './fixtures';
import { getFileTaskId } from './index';
import {
Expand All @@ -43,6 +40,8 @@ export class TestRunner {
private _test: TestCase | undefined;
private workerState: WorkerState | undefined;

constructor(private readonly taskContext: TaskContext) {}

async runTests({
tests,
testPath,
Expand Down Expand Up @@ -297,7 +296,7 @@ export class TestRunner {
}

if (test.type === 'suite') {
result = await runWithCurrentTask(
result = await this.taskContext.run(
{
taskId: test.testId,
taskName: test.name,
Expand Down Expand Up @@ -399,7 +398,7 @@ export class TestRunner {

errors.push(...(result.errors || []));
} else {
result = await runWithCurrentTask(
result = await this.taskContext.run(
{
taskId: test.testId,
taskName: test.name,
Expand Down Expand Up @@ -501,7 +500,7 @@ export class TestRunner {
// saves files and returns SnapshotResult
const snapshotResult = await snapshotClient.finish(testPath);

setFallbackCurrentTask({
this.taskContext.setFallback({
taskId: getFileTaskId(testPath),
taskType: 'file',
testPath,
Expand All @@ -523,7 +522,7 @@ export class TestRunner {
duration: RealDate.now() - start,
};
} finally {
setFallbackCurrentTask(undefined);
this.taskContext.setFallback(undefined);
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/runtime/worker/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import {
type InspectOptions,
inspect,
} from 'node:util';
import type { UserConsoleLog } from '../../types';
import type { CurrentTaskInfo, UserConsoleLog } from '../../types';
import { prettyTime } from '../../utils/helper';
import { color } from '../../utils/logger';
import { getCurrentTask } from './taskContext';

const RealDate = Date;

Expand All @@ -49,10 +48,12 @@ export function createCustomConsole({
onConsoleLog,
testPath,
printConsoleTrace,
getCurrentTask,
}: {
onConsoleLog: (log: UserConsoleLog) => void;
testPath: string;
printConsoleTrace: boolean;
getCurrentTask: () => CurrentTaskInfo | undefined;
}): Console {
const getConsoleTrace = () => {
const limit = Error.stackTraceLimit;
Expand Down
20 changes: 14 additions & 6 deletions packages/core/src/runtime/worker/runInPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { formatTestError, getRealTimers, setRealTimers } from '../util';
import { createForksRpcOptions, createRuntimeRpc } from './rpc';
import { createSilentConsoleController } from './silentConsole';
import { RstestSnapshotEnvironment } from './snapshot';
import { initTaskContext, setFallbackCurrentTask } from './taskContext';
import { createNodeTaskContext } from './taskContext.node';
import type { TaskContext } from './taskContext';

let sourceMaps: Record<string, string> = {};

Expand Down Expand Up @@ -113,7 +114,7 @@ const preparePool = async ({
});
globalCleanups.length = 0;

await initTaskContext();
const taskContext = createNodeTaskContext();
setRealTimers();

const cleanupFns: (() => MaybePromise<void>)[] = [];
Expand Down Expand Up @@ -172,6 +173,7 @@ const preparePool = async ({
},
testPath,
printConsoleTrace: !disableConsoleIntercept && printConsoleTrace,
getCurrentTask: () => taskContext.getCurrent(),
});
}

Expand Down Expand Up @@ -223,7 +225,9 @@ const preparePool = async ({
process.off('unhandledRejection', unhandledRejection);
});

const { api, runner } = await createRstestRuntime(workerState);
const { api, runner } = await createRstestRuntime(workerState, {
taskContext,
});

switch (testEnvironment.name) {
case 'node':
Expand Down Expand Up @@ -270,6 +274,7 @@ const preparePool = async ({
rpc,
silentConsoleController,
api,
taskContext,
unhandledErrors,
cleanup: async () => {
await Promise.all(cleanupFns.map((fn) => fn()));
Expand Down Expand Up @@ -447,6 +452,7 @@ export const runInPool = async (
}
}

let taskContext: TaskContext | undefined;
try {
const {
rstestContext,
Expand All @@ -457,7 +463,9 @@ export const runInPool = async (
cleanup,
unhandledErrors,
interopDefault,
taskContext: preparedTaskContext,
} = await preparePool(options);
taskContext = preparedTaskContext;

if (bail && (await rpc.getCountOfFailedTests()) >= bail) {
return {
Expand Down Expand Up @@ -499,7 +507,7 @@ export const runInPool = async (
// Keep file-level context only while evaluating top-level module code.
// Once the runner starts, suite/case tasks should own subsequent logs so
// passed suite buffers are not replayed by the final file-level flush.
setFallbackCurrentTask({
taskContext.setFallback({
taskId: getFileTaskId(testPath),
taskType: 'file',
testPath,
Expand All @@ -518,7 +526,7 @@ export const runInPool = async (
outputModule: options.context.outputModule,
});
} finally {
setFallbackCurrentTask(undefined);
taskContext.setFallback(undefined);
}

const results = await runner.runTests(
Expand Down Expand Up @@ -603,7 +611,7 @@ export const runInPool = async (
errors: await formatTestError(err),
};
} finally {
setFallbackCurrentTask(undefined);
taskContext?.setFallback(undefined);
await teardown();
}
};
24 changes: 24 additions & 0 deletions packages/core/src/runtime/worker/taskContext.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { CurrentTaskInfo } from '../../types';
import type { TaskContext } from './taskContext';

// Browser fallback: single slot. Browsers lack AsyncLocalStorage, so concurrent
// tasks may mis-attribute — callers must layer a hook-driven mechanism for that.
export const createBrowserTaskContext = (): TaskContext => {
let fallback: CurrentTaskInfo | undefined;

return {
getCurrent: () => fallback,
run: async (task, fn) => {
const previous = fallback;
fallback = task;
try {
return await fn();
} finally {
fallback = previous;
}
},
setFallback: (task) => {
fallback = task;
},
};
};
16 changes: 16 additions & 0 deletions packages/core/src/runtime/worker/taskContext.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AsyncLocalStorage } from 'node:async_hooks';
import type { CurrentTaskInfo } from '../../types';
import type { TaskContext } from './taskContext';

export const createNodeTaskContext = (): TaskContext => {
const storage = new AsyncLocalStorage<CurrentTaskInfo>();
let fallback: CurrentTaskInfo | undefined;

return {
getCurrent: () => storage.getStore() ?? fallback,
run: (task, fn) => storage.run(task, fn),
setFallback: (task) => {
fallback = task;
},
};
};
Loading
Loading