Skip to content

Commit

Permalink
fix: stop the runner before restarting, restart on workspace config c…
Browse files Browse the repository at this point in the history
…hange (#6859)
  • Loading branch information
sheremet-va authored Nov 8, 2024
1 parent 6e793c6 commit b01df47
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 19 deletions.
4 changes: 0 additions & 4 deletions packages/vitest/src/node/cli/cli-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ export async function startVitest(
stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout)
}

ctx.onServerRestart((reason) => {
ctx.report('onServerRestart', reason)
})

ctx.onAfterSetServer(() => {
if (ctx.config.standalone) {
ctx.init()
Expand Down
27 changes: 17 additions & 10 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class Vitest {
public distPath = distDir

private _cachedSpecs = new Map<string, WorkspaceSpec[]>()
private _workspaceConfigPath?: string

/** @deprecated use `_cachedSpecs` */
projectTestFiles = this._cachedSpecs
Expand Down Expand Up @@ -110,6 +111,10 @@ export class Vitest {
this._browserLastPort = defaultBrowserPort
this.pool?.close?.()
this.pool = undefined
this.closingPromise = undefined
this.projects = []
this.resolvedProjects = []
this._workspaceConfigPath = undefined
this.coverageProvider = undefined
this.runningPromise = undefined
this._cachedSpecs.clear()
Expand Down Expand Up @@ -145,22 +150,22 @@ export class Vitest {
const serverRestart = server.restart
server.restart = async (...args) => {
await Promise.all(this._onRestartListeners.map(fn => fn()))
this.report('onServerRestart')
await this.close()
await serverRestart(...args)
// watcher is recreated on restart
this.unregisterWatcher()
this.registerWatcher()
}

// since we set `server.hmr: false`, Vite does not auto restart itself
server.watcher.on('change', async (file) => {
file = normalize(file)
const isConfig = file === server.config.configFile
|| this.resolvedProjects.some(p => p.server.config.configFile === file)
|| file === this._workspaceConfigPath
if (isConfig) {
await Promise.all(this._onRestartListeners.map(fn => fn('config')))
this.report('onServerRestart', 'config')
await this.close()
await serverRestart()
// watcher is recreated on restart
this.unregisterWatcher()
this.registerWatcher()
}
})
}
Expand All @@ -175,8 +180,6 @@ export class Vitest {
}
catch { }

await Promise.all(this._onSetServer.map(fn => fn()))

const projects = await this.resolveWorkspace(cliOptions)
this.resolvedProjects = projects
this.projects = projects
Expand All @@ -193,6 +196,8 @@ export class Vitest {
if (this.config.testNamePattern) {
this.configOverride.testNamePattern = this.config.testNamePattern
}

await Promise.all(this._onSetServer.map(fn => fn()))
}

public provide<T extends keyof ProvidedContext & string>(key: T, value: ProvidedContext[T]) {
Expand Down Expand Up @@ -235,7 +240,7 @@ export class Vitest {
|| this.projects[0]
}

private async getWorkspaceConfigPath(): Promise<string | null> {
private async getWorkspaceConfigPath(): Promise<string | undefined> {
if (this.config.workspace) {
return this.config.workspace
}
Expand All @@ -251,7 +256,7 @@ export class Vitest {
})

if (!workspaceConfigName) {
return null
return undefined
}

return join(configDir, workspaceConfigName)
Expand All @@ -260,6 +265,8 @@ export class Vitest {
private async resolveWorkspace(cliOptions: UserConfig) {
const workspaceConfigPath = await this.getWorkspaceConfigPath()

this._workspaceConfigPath = workspaceConfigPath

if (!workspaceConfigPath) {
return [await this._createCoreProject()]
}
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ export class WorkspaceProject {
)
}

this.closingPromise = undefined
this.testProject = new TestProject(this)

this.server = server
Expand Down Expand Up @@ -476,7 +477,7 @@ export class WorkspaceProject {
if (!this.closingPromise) {
this.closingPromise = Promise.all(
[
this.server.close(),
this.server?.close(),
this.typechecker?.stop(),
this.browser?.close(),
this.clearTmpDir(),
Expand Down
59 changes: 59 additions & 0 deletions test/test-utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Options } from 'tinyexec'
import type { UserConfig as ViteUserConfig } from 'vite'
import type { WorkspaceProjectConfiguration } from 'vitest/config'
import type { UserConfig, Vitest, VitestRunMode } from 'vitest/node'
import fs from 'node:fs'
import { Readable, Writable } from 'node:stream'
Expand Down Expand Up @@ -234,3 +235,61 @@ export function resolvePath(baseUrl: string, path: string) {
const filename = fileURLToPath(baseUrl)
return resolve(dirname(filename), path)
}

export function useFS(root: string, structure: Record<string, string | ViteUserConfig | WorkspaceProjectConfiguration[]>) {
const files = new Set<string>()
const hasConfig = Object.keys(structure).some(file => file.includes('.config.'))
if (!hasConfig) {
structure['./vitest.config.js'] = {}
}
for (const file in structure) {
const filepath = resolve(root, file)
files.add(filepath)
const content = typeof structure[file] === 'string'
? structure[file]
: `export default ${JSON.stringify(structure[file])}`
fs.mkdirSync(dirname(filepath), { recursive: true })
fs.writeFileSync(filepath, String(content), 'utf-8')
}
onTestFinished(() => {
if (process.env.VITEST_FS_CLEANUP !== 'false') {
fs.rmSync(root, { recursive: true, force: true })
}
})
return {
editFile: (file: string, callback: (content: string) => string) => {
const filepath = resolve(root, file)
if (!files.has(filepath)) {
throw new Error(`file ${file} is outside of the test file system`)
}
const content = fs.readFileSync(filepath, 'utf-8')
fs.writeFileSync(filepath, callback(content))
},
createFile: (file: string, content: string) => {
if (file.startsWith('..')) {
throw new Error(`file ${file} is outside of the test file system`)
}
const filepath = resolve(root, file)
if (!files.has(filepath)) {
throw new Error(`file ${file} already exists in the test file system`)
}
createFile(filepath, content)
},
}
}

export async function runInlineTests(
structure: Record<string, string | ViteUserConfig | WorkspaceProjectConfiguration[]>,
config?: UserConfig,
) {
const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
const fs = useFS(root, structure)
const vitest = await runVitest({
root,
...config,
})
return {
fs,
...vitest,
}
}
93 changes: 93 additions & 0 deletions test/watch/test/config-watching.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { expect, test } from 'vitest'
import { runInlineTests } from '../../test-utils'

const ts = String.raw

test('reruns tests when configs change', async () => {
const { fs, vitest } = await runInlineTests({
'vitest.workspace.ts': [
'./project-1',
'./project-2',
],
'vitest.config.ts': {},
'project-1/vitest.config.ts': {},
'project-1/basic.test.ts': ts`
import { test } from 'vitest'
test('basic test 1', () => {})
`,
'project-2/vitest.config.ts': {},
'project-2/basic.test.ts': ts`
import { test } from 'vitest'
test('basic test 2', () => {})
`,
}, { watch: true })

await vitest.waitForStdout('Waiting for file changes')
vitest.resetOutput()

// editing the project config should trigger a restart
fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`)

await vitest.waitForStdout('Restarting due to config changes...')
await vitest.waitForStdout('Waiting for file changes')
vitest.resetOutput()

// editing the root config should trigger a restart
fs.editFile('./vitest.config.ts', c => `\n${c}`)

await vitest.waitForStdout('Restarting due to config changes...')
await vitest.waitForStdout('Waiting for file changes')
vitest.resetOutput()

// editing the workspace config should trigger a restart
fs.editFile('./vitest.workspace.ts', c => `\n${c}`)

await vitest.waitForStdout('Restarting due to config changes...')
await vitest.waitForStdout('Waiting for file changes')
})

test('rerun stops the previous browser server and restarts multiple times without port mismatch', async () => {
const { fs, vitest } = await runInlineTests({
'vitest.workspace.ts': [
'./project-1',
],
'vitest.config.ts': {},
'project-1/vitest.config.ts': {
test: {
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright',
headless: true,
},
},
},
'project-1/basic.test.ts': ts`
import { test } from 'vitest'
test('basic test 1', () => {})
`,
}, { watch: true })

await vitest.waitForStdout('Waiting for file changes')
vitest.resetOutput()

// editing the project config the first time restarts the browser server
fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`)

await vitest.waitForStdout('Restarting due to config changes...')
await vitest.waitForStdout('Waiting for file changes')

expect(vitest.stdout).not.toContain('is in use, trying another one...')
expect(vitest.stderr).not.toContain('is in use, trying another one...')
vitest.resetOutput()

// editing the project the second time also restarts the server
fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`)

await vitest.waitForStdout('Restarting due to config changes...')
await vitest.waitForStdout('Waiting for file changes')

expect(vitest.stdout).not.toContain('is in use, trying another one...')
expect(vitest.stderr).not.toContain('is in use, trying another one...')
vitest.resetOutput()
})
5 changes: 5 additions & 0 deletions test/watch/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
server: {
watch: {
ignored: ['**/fixtures/**'],
},
},
test: {
reporters: 'verbose',
include: ['test/**/*.test.*'],
Expand Down
4 changes: 2 additions & 2 deletions test/workspaces-browser/space_browser/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export default defineProject({
test: {
browser: {
enabled: true,
name: process.env.BROWSER || 'chrome',
name: process.env.BROWSER || 'chromium',
headless: true,
provider: process.env.PROVIDER || 'webdriverio',
provider: process.env.PROVIDER || 'playwright',
},
},
})
4 changes: 2 additions & 2 deletions test/workspaces-browser/vitest.workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export default defineWorkspace([
root: './space_browser_inline',
browser: {
enabled: true,
name: process.env.BROWSER || 'chrome',
name: process.env.BROWSER || 'chromium',
headless: true,
provider: process.env.PROVIDER || 'webdriverio',
provider: process.env.PROVIDER || 'playwright',
},
alias: {
'test-alias-from-vitest': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
Expand Down

0 comments on commit b01df47

Please sign in to comment.