Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 3 additions & 2 deletions packages/vitest/src/integrations/env/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs'
import { isBuiltin } from 'node:module'
import { pathToFileURL } from 'node:url'
import { resolve } from 'pathe'
import { ModuleRunner } from 'vite/module-runner'
import { EvaluatedModules, ModuleRunner } from 'vite/module-runner'
import { VitestTransport } from '../../runtime/moduleRunner/moduleTransport'
import { environments } from './index'

Expand All @@ -24,6 +24,7 @@ export function createEnvironmentLoader(root: string, rpc: WorkerRPC): ModuleRun
if (!cachedLoader || cachedLoader.isClosed()) {
_loaders.delete(root)

const evaluatedModules = new EvaluatedModules()
const moduleRunner = new ModuleRunner({
hmr: false,
sourcemapInterceptor: 'prepareStackTrace',
Expand All @@ -46,7 +47,7 @@ export function createEnvironmentLoader(root: string, rpc: WorkerRPC): ModuleRun
async resolveId(id, importer) {
return rpc.resolve(id, importer, '__vitest__')
},
}),
}, evaluatedModules, new WeakMap()),
})
_loaders.set(root, moduleRunner)
}
Expand Down
8 changes: 7 additions & 1 deletion packages/vitest/src/runtime/moduleRunner/moduleRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ export class VitestModuleRunner
public mocker: VitestMocker
public moduleExecutionInfo: ModuleExecutionInfo
private _otel: Traces
private _callstacks: WeakMap<EvaluatedModuleNode, string[]>

constructor(private vitestOptions: VitestModuleRunnerOptions) {
const options = vitestOptions
const transport = new VitestTransport(options.transport)
const evaluatedModules = options.evaluatedModules
const callstacks = new WeakMap<EvaluatedModuleNode, string[]>()
const transport = new VitestTransport(options.transport, evaluatedModules, callstacks)
super(
{
transport,
Expand All @@ -64,6 +66,7 @@ export class VitestModuleRunner
},
options.evaluator,
)
this._callstacks = callstacks
this._otel = vitestOptions.traces || new Traces({ enabled: false })
this.moduleExecutionInfo = options.getWorkerState().moduleExecutionInfo
this.mocker = options.mocker || new VitestMocker(this, {
Expand Down Expand Up @@ -153,6 +156,9 @@ export class VitestModuleRunner
metadata?: SSRImportMetadata,
ignoreMock = false,
): Promise<any> {
// Track for a better error message if dynamic import is not resolved properly
this._callstacks.set(mod, callstack)

if (ignoreMock) {
return this._cachedRequest(url, mod, callstack, metadata)
}
Expand Down
29 changes: 25 additions & 4 deletions packages/vitest/src/runtime/moduleRunner/moduleTransport.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { FetchFunction, ModuleRunnerTransport } from 'vite/module-runner'
import type { EvaluatedModuleNode, EvaluatedModules, FetchFunction, ModuleRunnerTransport } from 'vite/module-runner'
import type { ResolveFunctionResult } from '../../types/general'
import { EnvironmentTeardownError } from '../utils'

export interface VitestTransportOptions {
fetchModule: FetchFunction
resolveId: (id: string, importer?: string) => Promise<ResolveFunctionResult | null>
}

export class VitestTransport implements ModuleRunnerTransport {
constructor(private options: VitestTransportOptions) {}
constructor(
private options: VitestTransportOptions,
private evaluatedModules: EvaluatedModules,
private callstacks: WeakMap<EvaluatedModuleNode, string[]>,
) {}

async invoke(event: any): Promise<{ result: any } | { error: any }> {
if (event.type !== 'custom') {
Expand All @@ -29,8 +34,24 @@ export class VitestTransport implements ModuleRunnerTransport {
const result = await this.options.fetchModule(...data as Parameters<FetchFunction>)
return { result }
}
catch (error) {
return { error }
catch (cause) {
if (cause instanceof EnvironmentTeardownError) {
const [id, importer] = data as Parameters<FetchFunction>
let message = `Cannot load '${id}'${importer ? ` imported from ${importer}` : ''} after the environment was torn down. `
+ `This is not a bug in Vitest.`

const moduleNode = importer ? this.evaluatedModules.getModuleById(importer) : undefined
const callstack = moduleNode ? this.callstacks.get(moduleNode) : undefined
if (callstack) {
message += ` The last recorded callstack:\n- ${[...callstack, importer, id].reverse().join('\n- ')}`
}
const error = new EnvironmentTeardownError(message)
if (cause.stack) {
error.stack = cause.stack.replace(cause.message, error.message)
}
return { error }
}
return { error: cause }
}
}
}
4 changes: 4 additions & 0 deletions packages/vitest/src/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { getSafeTimers } from '@vitest/utils/timers'

const NAME_WORKER_STATE = '__vitest_worker__'

export class EnvironmentTeardownError extends Error {
name = 'EnvironmentTeardownError'
}

export function getWorkerState(): WorkerGlobalState {
// @ts-expect-error untyped global
const workerState = globalThis[NAME_WORKER_STATE]
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/runtime/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { setupInspect } from './inspector'
import * as listeners from './listeners'
import { VitestEvaluatedModules } from './moduleRunner/evaluatedModules'
import { onCancel, rpcDone } from './rpc'
import { EnvironmentTeardownError } from './utils'

const resolvingModules = new Set<string>()

Expand All @@ -21,7 +22,7 @@ async function execute(method: 'run' | 'collect', ctx: ContextRPC, worker: Vites
// do not close the RPC channel so that we can get the error messages sent to the main thread
cleanups.push(async () => {
await Promise.all(rpc.$rejectPendingCalls(({ method, reject }) => {
reject(new Error(`[vitest-worker]: Closing rpc while "${method}" was pending`))
reject(new EnvironmentTeardownError(`[vitest-worker]: Closing rpc while "${method}" was pending`))
}))
})

Expand Down
Loading