mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
feat: setup files, close #28
This commit is contained in:
parent
99dfdcae7b
commit
bb0cd220ef
@ -2,7 +2,8 @@ import type { MatchersObject } from './integrations/chai/types'
|
||||
import type { UserOptions } from './types'
|
||||
|
||||
export * from './types'
|
||||
export * from './runtime/suite'
|
||||
export { suite, test, describe, it } from './runtime/suite'
|
||||
export * from './runtime/hooks'
|
||||
export * from './integrations/chai'
|
||||
export * from './integrations/sinon'
|
||||
|
||||
|
||||
@ -78,6 +78,9 @@ export async function initViteServer(options: CliOptions = {}) {
|
||||
if (process.env.VITEST_MIN_THREADS)
|
||||
resolved.minThreads = parseInt(process.env.VITEST_MIN_THREADS)
|
||||
|
||||
resolved.setupFiles = Array.from(resolved.setupFiles || [])
|
||||
.map(i => resolve(root, i))
|
||||
|
||||
return {
|
||||
server,
|
||||
config: resolved,
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { basename } from 'path'
|
||||
import { performance } from 'perf_hooks'
|
||||
import { nanoid } from 'nanoid/non-secure'
|
||||
import type { File, Suite, Test } from '../types'
|
||||
import type { ResolvedConfig, File, Suite, Test } from '../types'
|
||||
import { interpretOnlyMode } from '../utils'
|
||||
import { clearContext, createSuiteHooks, defaultSuite } from './suite'
|
||||
import { context } from './context'
|
||||
import { setHooks } from './map'
|
||||
import { processError } from './error'
|
||||
import { context } from './context'
|
||||
import { runSetupFiles } from './setup'
|
||||
|
||||
export async function collectTests(paths: string[]) {
|
||||
export async function collectTests(paths: string[], config: ResolvedConfig) {
|
||||
const files: File[] = []
|
||||
|
||||
for (const filepath of paths) {
|
||||
@ -26,6 +27,7 @@ export async function collectTests(paths: string[]) {
|
||||
|
||||
clearContext()
|
||||
try {
|
||||
await runSetupFiles(config)
|
||||
await import(filepath)
|
||||
|
||||
for (const c of [defaultSuite, ...context.tasks]) {
|
||||
|
||||
@ -1,6 +1,41 @@
|
||||
import type { GlobalContext } from '../types'
|
||||
import type { Awaitable, RuntimeContext, SuiteCollector } from '../types'
|
||||
|
||||
export const context: GlobalContext = {
|
||||
export const context: RuntimeContext = {
|
||||
tasks: [],
|
||||
currentSuite: null,
|
||||
}
|
||||
|
||||
export function collectTask(task: SuiteCollector) {
|
||||
context.currentSuite?.tasks.push(task)
|
||||
}
|
||||
|
||||
export async function runWithSuite(suite: SuiteCollector, fn: (() => Awaitable<void>)) {
|
||||
const prev = context.currentSuite
|
||||
context.currentSuite = suite
|
||||
await fn()
|
||||
context.currentSuite = prev
|
||||
}
|
||||
|
||||
export function getDefaultTestTimeout() {
|
||||
return process.__vitest_worker__?.config?.testTimeout ?? 5000
|
||||
}
|
||||
|
||||
export function getDefaultHookTimeout() {
|
||||
return process.__vitest_worker__?.config?.hookTimeout ?? 5000
|
||||
}
|
||||
|
||||
export function withTimeout<T extends((...args: any[]) => any)>(fn: T, _timeout?: number): T {
|
||||
const timeout = _timeout ?? getDefaultTestTimeout()
|
||||
if (timeout <= 0 || timeout === Infinity)
|
||||
return fn
|
||||
|
||||
return ((...args: (T extends ((...args: infer A) => any) ? A : never)) => {
|
||||
return Promise.race([fn(...args), new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
clearTimeout(timer)
|
||||
reject(new Error(`Test timed out in ${timeout}ms.`))
|
||||
}, timeout)
|
||||
timer.unref()
|
||||
})]) as Awaitable<void>
|
||||
}) as T
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { ResolvedConfig } from '../types'
|
||||
import { setupGlobalEnv, withEnv } from './env'
|
||||
import { setupGlobalEnv, withEnv } from './setup'
|
||||
import { startTests } from './run'
|
||||
|
||||
export async function run(files: string[], config: ResolvedConfig): Promise<void> {
|
||||
await setupGlobalEnv(config)
|
||||
|
||||
await withEnv(config.environment, async() => {
|
||||
await startTests(files)
|
||||
await startTests(files, config)
|
||||
})
|
||||
}
|
||||
|
||||
9
src/runtime/hooks.ts
Normal file
9
src/runtime/hooks.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type { SuiteHooks } from '../types'
|
||||
import { getDefaultHookTimeout, withTimeout } from './context'
|
||||
import { getCurrentSuite } from './suite'
|
||||
|
||||
// suite hooks
|
||||
export const beforeAll = (fn: SuiteHooks['beforeAll'][0], timeout?: number) => getCurrentSuite().on('beforeAll', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const afterAll = (fn: SuiteHooks['afterAll'][0], timeout?: number) => getCurrentSuite().on('afterAll', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const beforeEach = (fn: SuiteHooks['beforeEach'][0], timeout?: number) => getCurrentSuite().on('beforeEach', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const afterEach = (fn: SuiteHooks['afterEach'][0], timeout?: number) => getCurrentSuite().on('afterEach', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
@ -1,6 +1,6 @@
|
||||
import { performance } from 'perf_hooks'
|
||||
import type { HookListener } from 'vitest'
|
||||
import type { Test, Suite, SuiteHooks, Task } from '../types'
|
||||
import type { ResolvedConfig, Test, Suite, SuiteHooks, Task } from '../types'
|
||||
import { getSnapshotClient } from '../integrations/snapshot/chai'
|
||||
import { hasFailed, hasTests, partitionSuiteChildren } from '../utils'
|
||||
import { getFn, getHooks } from './map'
|
||||
@ -8,7 +8,7 @@ import { rpc, send } from './rpc'
|
||||
import { collectTests } from './collect'
|
||||
import { processError } from './error'
|
||||
|
||||
async function callHook<T extends keyof SuiteHooks>(suite: Suite, name: T, args: SuiteHooks[T][0] extends HookListener<infer A> ? A : never) {
|
||||
export async function callSuiteHook<T extends keyof SuiteHooks>(suite: Suite, name: T, args: SuiteHooks[T][0] extends HookListener<infer A> ? A : never) {
|
||||
await Promise.all(getHooks(suite)[name].map(fn => fn(...(args as any))))
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export async function runTest(test: Test) {
|
||||
process.__vitest_worker__.current = test
|
||||
|
||||
try {
|
||||
await callHook(test.suite, 'beforeEach', [test, test.suite])
|
||||
await callSuiteHook(test.suite, 'beforeEach', [test, test.suite])
|
||||
await getFn(test)()
|
||||
test.result.state = 'pass'
|
||||
}
|
||||
@ -40,7 +40,7 @@ export async function runTest(test: Test) {
|
||||
test.result.error = processError(e)
|
||||
}
|
||||
try {
|
||||
await callHook(test.suite, 'afterEach', [test, test.suite])
|
||||
await callSuiteHook(test.suite, 'afterEach', [test, test.suite])
|
||||
}
|
||||
catch (e) {
|
||||
test.result.state = 'fail'
|
||||
@ -75,7 +75,7 @@ export async function runSuite(suite: Suite) {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
await callHook(suite, 'beforeAll', [suite])
|
||||
await callSuiteHook(suite, 'beforeAll', [suite])
|
||||
|
||||
for (const tasksGroup of partitionSuiteChildren(suite)) {
|
||||
const computeMode = tasksGroup[0].computeMode
|
||||
@ -88,7 +88,7 @@ export async function runSuite(suite: Suite) {
|
||||
}
|
||||
}
|
||||
|
||||
await callHook(suite, 'afterAll', [suite])
|
||||
await callSuiteHook(suite, 'afterAll', [suite])
|
||||
}
|
||||
catch (e) {
|
||||
suite.result.state = 'fail'
|
||||
@ -124,8 +124,8 @@ export async function runSuites(suites: Suite[]) {
|
||||
await runSuite(suite)
|
||||
}
|
||||
|
||||
export async function startTests(paths: string[]) {
|
||||
const files = await collectTests(paths)
|
||||
export async function startTests(paths: string[], config: ResolvedConfig) {
|
||||
const files = await collectTests(paths, config)
|
||||
|
||||
send('onCollected', files)
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { Writable } from 'stream'
|
||||
import { environments } from '../env'
|
||||
import { setupChai } from '../integrations/chai/setup'
|
||||
import type { ResolvedConfig } from '../types'
|
||||
import { toArray } from '../utils'
|
||||
import { send } from './rpc'
|
||||
|
||||
let globalSetup = false
|
||||
@ -59,3 +60,13 @@ export async function withEnv(name: ResolvedConfig['environment'], fn: () => Pro
|
||||
await env.teardown(globalThis)
|
||||
}
|
||||
}
|
||||
|
||||
export async function runSetupFiles(config: ResolvedConfig) {
|
||||
const files = toArray(config.setupFiles)
|
||||
await Promise.all(
|
||||
files.map(async(file) => {
|
||||
process.__vitest_worker__.moduleCache.delete(file)
|
||||
await import(file)
|
||||
}),
|
||||
)
|
||||
}
|
||||
@ -1,24 +1,22 @@
|
||||
import { nanoid } from 'nanoid/non-secure'
|
||||
import type { SuiteHooks, Test, SuiteCollector, TestCollector, RunMode, ComputeMode, TestFactory, TestFunction, File, Suite, Awaitable, ResolvedConfig, RpcCall, RpcSend } from '../types'
|
||||
import { context } from './context'
|
||||
import type { SuiteHooks, Test, SuiteCollector, TestCollector, RunMode, ComputeMode, TestFactory, TestFunction, File, Suite, ResolvedConfig, RpcCall, RpcSend, ModuleCache } from '../types'
|
||||
import { collectTask, context, runWithSuite, withTimeout } from './context'
|
||||
import { getHooks, setFn, setHooks } from './map'
|
||||
|
||||
export const suite = createSuite()
|
||||
|
||||
export const defaultSuite = suite('')
|
||||
|
||||
function getCurrentSuite() {
|
||||
export function clearContext() {
|
||||
context.tasks.length = 0
|
||||
defaultSuite.clear()
|
||||
context.currentSuite = defaultSuite
|
||||
}
|
||||
|
||||
export function getCurrentSuite() {
|
||||
return context.currentSuite || defaultSuite
|
||||
}
|
||||
|
||||
const getDefaultTestTimeout = () => {
|
||||
return process.__vitest_worker__?.config?.testTimeout ?? 5000
|
||||
}
|
||||
|
||||
const getDefaultHookTimeout = () => {
|
||||
return process.__vitest_worker__?.config?.hookTimeout ?? 5000
|
||||
}
|
||||
|
||||
export function createSuiteHooks() {
|
||||
return {
|
||||
beforeAll: [],
|
||||
@ -84,12 +82,8 @@ function createSuiteCollector(name: string, factory: TestFactory = () => { }, mo
|
||||
|
||||
async function collect(file?: File) {
|
||||
factoryQueue.length = 0
|
||||
if (factory) {
|
||||
const prev = context.currentSuite
|
||||
context.currentSuite = collector
|
||||
await factory(test)
|
||||
context.currentSuite = prev
|
||||
}
|
||||
if (factory)
|
||||
await runWithSuite(collector, () => factory(test))
|
||||
|
||||
const allChildren = await Promise.all(
|
||||
[...factoryQueue, ...tasks]
|
||||
@ -108,7 +102,7 @@ function createSuiteCollector(name: string, factory: TestFactory = () => { }, mo
|
||||
return suite
|
||||
}
|
||||
|
||||
context.currentSuite?.tasks.push(collector)
|
||||
collectTask(collector)
|
||||
|
||||
return collector
|
||||
}
|
||||
@ -144,7 +138,6 @@ function createTestCollector(collectTest: (name: string, fn: TestFunction, mode:
|
||||
}
|
||||
|
||||
// apis
|
||||
|
||||
export const test = (function() {
|
||||
function test(name: string, fn: TestFunction, timeout?: number) {
|
||||
return getCurrentSuite().test(name, fn, timeout)
|
||||
@ -215,35 +208,6 @@ function createSuite() {
|
||||
export const describe = suite
|
||||
export const it = test
|
||||
|
||||
// hooks
|
||||
export const beforeAll = (fn: SuiteHooks['beforeAll'][0], timeout?: number) => getCurrentSuite().on('beforeAll', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const afterAll = (fn: SuiteHooks['afterAll'][0], timeout?: number) => getCurrentSuite().on('afterAll', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const beforeEach = (fn: SuiteHooks['beforeEach'][0], timeout?: number) => getCurrentSuite().on('beforeEach', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
export const afterEach = (fn: SuiteHooks['afterEach'][0], timeout?: number) => getCurrentSuite().on('afterEach', withTimeout(fn, timeout ?? getDefaultHookTimeout()))
|
||||
|
||||
// utils
|
||||
export function clearContext() {
|
||||
context.tasks.length = 0
|
||||
defaultSuite.clear()
|
||||
context.currentSuite = defaultSuite
|
||||
}
|
||||
|
||||
function withTimeout<T extends((...args: any[]) => any)>(fn: T, _timeout?: number): T {
|
||||
const timeout = _timeout ?? getDefaultTestTimeout()
|
||||
if (timeout <= 0 || timeout === Infinity)
|
||||
return fn
|
||||
|
||||
return ((...args: (T extends ((...args: infer A) => any) ? A : never)) => {
|
||||
return Promise.race([fn(...args), new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
clearTimeout(timer)
|
||||
reject(new Error(`Test timed out in ${timeout}ms.`))
|
||||
}, timeout)
|
||||
timer.unref()
|
||||
})]) as Awaitable<void>
|
||||
}) as T
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Process {
|
||||
@ -252,6 +216,7 @@ declare global {
|
||||
rpc: RpcCall
|
||||
send: RpcSend
|
||||
current?: Test
|
||||
moduleCache: Map<string, ModuleCache>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { resolve } from 'path'
|
||||
import { nanoid } from 'nanoid/non-secure'
|
||||
import type { WorkerContext, ResolvedConfig } from '../types'
|
||||
import type { WorkerContext, ResolvedConfig, ModuleCache } from '../types'
|
||||
import { distDir } from '../constants'
|
||||
import type { ExecuteOptions } from '../node/execute'
|
||||
import { executeInViteNode } from '../node/execute'
|
||||
|
||||
let _run: (files: string[], config: ResolvedConfig) => Promise<void>
|
||||
const moduleCache: ExecuteOptions['moduleCache'] = new Map()
|
||||
const moduleCache: Map<string, ModuleCache> = new Map()
|
||||
|
||||
export async function init(ctx: WorkerContext) {
|
||||
if (_run)
|
||||
@ -38,6 +37,7 @@ export default async function run(ctx: WorkerContext) {
|
||||
const rpcPromiseMap = new Map<string, { resolve: ((...args: any) => any); reject: (...args: any) => any }>()
|
||||
|
||||
process.__vitest_worker__ = {
|
||||
moduleCache,
|
||||
config,
|
||||
rpc: (method, ...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@ -120,11 +120,15 @@ export interface UserOptions {
|
||||
|
||||
/**
|
||||
* Silent mode
|
||||
* TODO: implement this
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
silent?: boolean
|
||||
|
||||
/**
|
||||
* Path to setup files
|
||||
*/
|
||||
setupFiles?: string | string[]
|
||||
}
|
||||
|
||||
export interface CliOptions extends UserOptions {
|
||||
|
||||
@ -91,7 +91,7 @@ export interface SuiteCollector {
|
||||
|
||||
export type TestFactory = (test: (name: string, fn: TestFunction) => void) => Awaitable<void>
|
||||
|
||||
export interface GlobalContext {
|
||||
export interface RuntimeContext {
|
||||
tasks: (SuiteCollector | Test)[]
|
||||
currentSuite: SuiteCollector | null
|
||||
}
|
||||
|
||||
5
test/core/test/setup.ts
Normal file
5
test/core/test/setup.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { beforeEach } from 'vitest'
|
||||
|
||||
beforeEach(() => {
|
||||
// console.log(`hi ${s.name}`)
|
||||
})
|
||||
@ -4,5 +4,8 @@ export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 1000,
|
||||
// threads: false,
|
||||
setupFiles: [
|
||||
'./test/setup.ts',
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user