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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ test/cli/fixtures/browser-multiple/basic-*
test/browser/html/
test/core/html/
.vitest-attachments
explainFiles.txt
explainFiles.txt
.vitest-dump
11 changes: 11 additions & 0 deletions packages/vitest/src/node/config/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,17 @@ export function resolveConfig(
resolved.server ??= {}
resolved.server.deps ??= {}

if (resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP) {
const userFolder = resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP
resolved.dumpDir = resolve(
resolved.root,
typeof userFolder === 'string' && userFolder !== 'true'
? userFolder
: '.vitest-dump',
resolved.name || 'root',
)
}

resolved.testTimeout ??= resolved.browser.enabled ? 15000 : 5000
resolved.hookTimeout ??= resolved.browser.enabled ? 30000 : 10000

Expand Down
57 changes: 45 additions & 12 deletions packages/vitest/src/node/environments/fetchModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ import type { DevEnvironment, FetchResult, Rollup, TransformResult } from 'vite'
import type { FetchFunctionOptions } from 'vite/module-runner'
import type { FetchCachedFileSystemResult } from '../../types/general'
import type { VitestResolver } from '../resolver'
import { mkdirSync } from 'node:fs'
import { rename, stat, unlink, writeFile } from 'node:fs/promises'
import { existsSync, mkdirSync } from 'node:fs'
import { readFile, rename, stat, unlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { isExternalUrl, nanoid, unwrapId } from '@vitest/utils/helpers'
import { dirname, join } from 'pathe'
import { dirname, join, resolve } from 'pathe'
import { fetchModule } from 'vite'
import { hash } from '../hash'

const created = new Set()
const promises = new Map<string, Promise<void>>()

interface DumpOptions {
dumpFolder?: string
readFromDump?: boolean
}

export function createFetchModuleFunction(
resolver: VitestResolver,
cacheFs: boolean = false,
tmpDir: string = join(tmpdir(), nanoid()),
dump?: DumpOptions,
): (
url: string,
importer: string | undefined,
Expand Down Expand Up @@ -67,18 +73,45 @@ export function createFetchModuleFunction(
}
}

const moduleRunnerModule = await fetchModule(
environment,
url,
importer,
{
...options,
inlineSourceMap: false,
},
).catch(handleRollupError)
let moduleRunnerModule: FetchResult | undefined

if (dump?.dumpFolder && dump.readFromDump) {
const path = resolve(dump?.dumpFolder, url.replace(/[^\w+]/g, '-'))
if (existsSync(path)) {
const code = await readFile(path, 'utf-8')
const matchIndex = code.lastIndexOf('\n//')
if (matchIndex !== -1) {
const { id, file } = JSON.parse(code.slice(matchIndex + 4))
moduleRunnerModule = {
code,
id,
url,
file,
invalidate: false,
}
}
}
}

if (!moduleRunnerModule) {
moduleRunnerModule = await fetchModule(
environment,
url,
importer,
{
...options,
inlineSourceMap: false,
},
).catch(handleRollupError)
}

const result = processResultSource(environment, moduleRunnerModule)

if (dump?.dumpFolder && 'code' in result) {
const path = resolve(dump?.dumpFolder, result.url.replace(/[^\w+]/g, '-'))
await writeFile(path, `${result.code}\n// ${JSON.stringify({ id: result.id, file: result.file })}`, 'utf-8')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we had the previous vite-node related module dump logic my use case was always to drag-and-drop those to source map visualizer. Maybe later (if not yet in this PR) we could generate a direct link there using something like https://github.com/AriPerkkio/ast-v8-to-istanbul/blob/main/test/utils/source-map-visualizer.ts.

}

if (!cacheFs || !('code' in result)) {
return result
}
Expand Down
72 changes: 49 additions & 23 deletions packages/vitest/src/node/pools/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RuntimeRPC } from '../../types/rpc'
import type { TestProject } from '../project'
import type { ResolveSnapshotPathHandlerContext } from '../types/config'
import { existsSync, mkdirSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { cleanUrl } from '@vitest/utils/helpers'
import { createFetchModuleFunction, handleRollupError } from '../environments/fetchModule'
Expand All @@ -13,9 +14,26 @@ interface MethodsOptions {
}

export function createMethodsRPC(project: TestProject, options: MethodsOptions = {}): RuntimeRPC {
const ctx = project.vitest
const vitest = project.vitest
const cacheFs = options.cacheFs ?? false
const fetch = createFetchModuleFunction(project._resolver, cacheFs, project.tmpDir)
project.vitest.state.metadata[project.name] ??= {
externalized: {},
duration: {},
tmps: {},
}
if (project.config.dumpDir && !existsSync(project.config.dumpDir)) {
mkdirSync(project.config.dumpDir, { recursive: true })
}
project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir
const fetch = createFetchModuleFunction(
project._resolver,
cacheFs,
project.tmpDir,
{
dumpFolder: project.config.dumpDir,
readFromDump: project.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null,
},
)
return {
async fetch(
url,
Expand All @@ -30,12 +48,20 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =

const start = performance.now()

try {
return await fetch(url, importer, environment, options)
}
finally {
project.vitest.state.transformTime += (performance.now() - start)
}
return await fetch(url, importer, environment, options).then((result) => {
const duration = performance.now() - start
project.vitest.state.transformTime += duration
const metadata = project.vitest.state.metadata[project.name]
if ('externalize' in result) {
metadata.externalized[url] = result.externalize
}
if ('tmp' in result) {
metadata.tmps[url] = result.tmp
}
metadata.duration[url] ??= []
metadata.duration[url].push(duration)
return result
})
},
async resolve(id, importer, environmentName) {
const environment = project.vite.environments[environmentName]
Expand All @@ -54,10 +80,10 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =
},

snapshotSaved(snapshot) {
ctx.snapshot.add(snapshot)
vitest.snapshot.add(snapshot)
},
resolveSnapshotPath(testPath: string) {
return ctx.snapshot.resolvePath<ResolveSnapshotPathHandlerContext>(testPath, {
return vitest.snapshot.resolvePath<ResolveSnapshotPathHandlerContext>(testPath, {
config: project.serializedConfig,
})
},
Expand All @@ -73,50 +99,50 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =
},
async onQueued(file) {
if (options.collect) {
ctx.state.collectFiles(project, [file])
vitest.state.collectFiles(project, [file])
}
else {
await ctx._testRun.enqueued(project, file)
await vitest._testRun.enqueued(project, file)
}
},
async onCollected(files) {
if (options.collect) {
ctx.state.collectFiles(project, files)
vitest.state.collectFiles(project, files)
}
else {
await ctx._testRun.collected(project, files)
await vitest._testRun.collected(project, files)
}
},
onAfterSuiteRun(meta) {
ctx.coverageProvider?.onAfterSuiteRun(meta)
vitest.coverageProvider?.onAfterSuiteRun(meta)
},
async onTaskAnnotate(testId, annotation) {
return ctx._testRun.annotate(testId, annotation)
return vitest._testRun.annotate(testId, annotation)
},
async onTaskUpdate(packs, events) {
if (options.collect) {
ctx.state.updateTasks(packs)
vitest.state.updateTasks(packs)
}
else {
await ctx._testRun.updated(packs, events)
await vitest._testRun.updated(packs, events)
}
},
async onUserConsoleLog(log) {
if (options.collect) {
ctx.state.updateUserLog(log)
vitest.state.updateUserLog(log)
}
else {
await ctx._testRun.log(log)
await vitest._testRun.log(log)
}
},
onUnhandledError(err, type) {
ctx.state.catchError(err, type)
vitest.state.catchError(err, type)
},
onCancel(reason) {
ctx.cancelCurrentRun(reason)
vitest.cancelCurrentRun(reason)
},
getCountOfFailedTests() {
return ctx.state.getCountOfFailedTests()
return vitest.state.getCountOfFailedTests()
},
}
}
11 changes: 11 additions & 0 deletions packages/vitest/src/node/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export class StateManager {
blobs?: MergedBlobs
transformTime = 0

metadata: Record<string, {
externalized: Record<string, string>
duration: Record<string, number[]>
tmps: Record<string, string>
dumpDir?: string
outline?: {
externalized: number
inlined: number
}
}> = {}

onUnhandledError?: OnUnhandledErrorCallback

/** @internal */
Expand Down
20 changes: 19 additions & 1 deletion packages/vitest/src/node/test-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { TestRunEndReason } from './types/reporter'
import assert from 'node:assert'
import { createHash } from 'node:crypto'
import { existsSync } from 'node:fs'
import { copyFile, mkdir } from 'node:fs/promises'
import { copyFile, mkdir, writeFile } from 'node:fs/promises'
import { isPrimitive } from '@vitest/utils/helpers'
import { serializeValue } from '@vitest/utils/serialize'
import { parseErrorStacktrace } from '@vitest/utils/source-map'
Expand Down Expand Up @@ -107,6 +107,24 @@ export class TestRun {
}

await this.vitest.report('onTestRunEnd', modules, [...errors] as SerializedError[], state)

for (const project in this.vitest.state.metadata) {
const meta = this.vitest.state.metadata[project]
if (!meta?.dumpDir) {
continue
}
const path = resolve(meta.dumpDir, 'vitest-metadata.json')
meta.outline = {
externalized: Object.keys(meta.externalized).length,
inlined: Object.keys(meta.tmps).length,
}
await writeFile(
path,
JSON.stringify(meta, null, 2),
'utf-8',
)
this.vitest.logger.log(`Metadata written to ${path}`)
}
}

private hasFailed(modules: TestModule[]) {
Expand Down
19 changes: 19 additions & 0 deletions packages/vitest/src/node/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ export interface InlineConfig {

server?: {
deps?: ServerDepsOptions
debug?: {
/**
* The folder where Vitest stores the contents of transformed
* test files that can be inspected manually.
*
* If `true`, Vitest dumps the files in `.vitest-dump` folder relative to the root of the project.
*
* You can also use `VITEST_DEBUG_DUMP` env variable to enable this.
*/
dump?: string | true
/**
* If dump is enabled, should Vitest load the files from there instead of transforming them.
*
* You can also use `VITEST_DEBUG_LOAD_DUMP` env variable to enable this.
*/
load?: boolean
}
}

/**
Expand Down Expand Up @@ -1010,6 +1027,8 @@ export interface ResolvedConfig
runner?: string

maxWorkers: number

dumpDir?: string
}

type NonProjectOptions
Expand Down
Loading