feat: setup files, close #28

This commit is contained in:
Anthony Fu 2021-12-13 08:29:58 +08:00
parent 99dfdcae7b
commit bb0cd220ef
14 changed files with 107 additions and 69 deletions

View File

@ -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'

View File

@ -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,

View File

@ -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]) {

View File

@ -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
}

View File

@ -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
View 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()))

View File

@ -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)

View File

@ -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)
}),
)
}

View 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>
}
}
}

View File

@ -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) => {

View File

@ -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 {

View File

@ -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
View File

@ -0,0 +1,5 @@
import { beforeEach } from 'vitest'
beforeEach(() => {
// console.log(`hi ${s.name}`)
})

View File

@ -4,5 +4,8 @@ export default defineConfig({
test: {
testTimeout: 1000,
// threads: false,
setupFiles: [
'./test/setup.ts',
],
},
})