[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: stop the runner before restarting, restart on workspace config change (#6859)

authored by

Vladimir and committed by
GitHub
(Nov 8, 2024, 10:02 AM +0100) b01df47d 6e793c64

+180 -19
+59
test/test-utils/index.ts
··· 1 1 import type { Options } from 'tinyexec' 2 2 import type { UserConfig as ViteUserConfig } from 'vite' 3 + import type { WorkspaceProjectConfiguration } from 'vitest/config' 3 4 import type { UserConfig, Vitest, VitestRunMode } from 'vitest/node' 4 5 import fs from 'node:fs' 5 6 import { Readable, Writable } from 'node:stream' ··· 233 234 export function resolvePath(baseUrl: string, path: string) { 234 235 const filename = fileURLToPath(baseUrl) 235 236 return resolve(dirname(filename), path) 237 + } 238 + 239 + export function useFS(root: string, structure: Record<string, string | ViteUserConfig | WorkspaceProjectConfiguration[]>) { 240 + const files = new Set<string>() 241 + const hasConfig = Object.keys(structure).some(file => file.includes('.config.')) 242 + if (!hasConfig) { 243 + structure['./vitest.config.js'] = {} 244 + } 245 + for (const file in structure) { 246 + const filepath = resolve(root, file) 247 + files.add(filepath) 248 + const content = typeof structure[file] === 'string' 249 + ? structure[file] 250 + : `export default ${JSON.stringify(structure[file])}` 251 + fs.mkdirSync(dirname(filepath), { recursive: true }) 252 + fs.writeFileSync(filepath, String(content), 'utf-8') 253 + } 254 + onTestFinished(() => { 255 + if (process.env.VITEST_FS_CLEANUP !== 'false') { 256 + fs.rmSync(root, { recursive: true, force: true }) 257 + } 258 + }) 259 + return { 260 + editFile: (file: string, callback: (content: string) => string) => { 261 + const filepath = resolve(root, file) 262 + if (!files.has(filepath)) { 263 + throw new Error(`file ${file} is outside of the test file system`) 264 + } 265 + const content = fs.readFileSync(filepath, 'utf-8') 266 + fs.writeFileSync(filepath, callback(content)) 267 + }, 268 + createFile: (file: string, content: string) => { 269 + if (file.startsWith('..')) { 270 + throw new Error(`file ${file} is outside of the test file system`) 271 + } 272 + const filepath = resolve(root, file) 273 + if (!files.has(filepath)) { 274 + throw new Error(`file ${file} already exists in the test file system`) 275 + } 276 + createFile(filepath, content) 277 + }, 278 + } 279 + } 280 + 281 + export async function runInlineTests( 282 + structure: Record<string, string | ViteUserConfig | WorkspaceProjectConfiguration[]>, 283 + config?: UserConfig, 284 + ) { 285 + const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`) 286 + const fs = useFS(root, structure) 287 + const vitest = await runVitest({ 288 + root, 289 + ...config, 290 + }) 291 + return { 292 + fs, 293 + ...vitest, 294 + } 236 295 }
+5
test/watch/vitest.config.ts
··· 1 1 import { defineConfig } from 'vitest/config' 2 2 3 3 export default defineConfig({ 4 + server: { 5 + watch: { 6 + ignored: ['**/fixtures/**'], 7 + }, 8 + }, 4 9 test: { 5 10 reporters: 'verbose', 6 11 include: ['test/**/*.test.*'],
+2 -2
test/workspaces-browser/vitest.workspace.ts
··· 8 8 root: './space_browser_inline', 9 9 browser: { 10 10 enabled: true, 11 - name: process.env.BROWSER || 'chrome', 11 + name: process.env.BROWSER || 'chromium', 12 12 headless: true, 13 - provider: process.env.PROVIDER || 'webdriverio', 13 + provider: process.env.PROVIDER || 'playwright', 14 14 }, 15 15 alias: { 16 16 'test-alias-from-vitest': new URL('./space_browser_inline/test-alias-to.ts', import.meta.url).pathname,
+93
test/watch/test/config-watching.test.ts
··· 1 + import { expect, test } from 'vitest' 2 + import { runInlineTests } from '../../test-utils' 3 + 4 + const ts = String.raw 5 + 6 + test('reruns tests when configs change', async () => { 7 + const { fs, vitest } = await runInlineTests({ 8 + 'vitest.workspace.ts': [ 9 + './project-1', 10 + './project-2', 11 + ], 12 + 'vitest.config.ts': {}, 13 + 'project-1/vitest.config.ts': {}, 14 + 'project-1/basic.test.ts': ts` 15 + import { test } from 'vitest' 16 + test('basic test 1', () => {}) 17 + `, 18 + 'project-2/vitest.config.ts': {}, 19 + 'project-2/basic.test.ts': ts` 20 + import { test } from 'vitest' 21 + test('basic test 2', () => {}) 22 + `, 23 + }, { watch: true }) 24 + 25 + await vitest.waitForStdout('Waiting for file changes') 26 + vitest.resetOutput() 27 + 28 + // editing the project config should trigger a restart 29 + fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`) 30 + 31 + await vitest.waitForStdout('Restarting due to config changes...') 32 + await vitest.waitForStdout('Waiting for file changes') 33 + vitest.resetOutput() 34 + 35 + // editing the root config should trigger a restart 36 + fs.editFile('./vitest.config.ts', c => `\n${c}`) 37 + 38 + await vitest.waitForStdout('Restarting due to config changes...') 39 + await vitest.waitForStdout('Waiting for file changes') 40 + vitest.resetOutput() 41 + 42 + // editing the workspace config should trigger a restart 43 + fs.editFile('./vitest.workspace.ts', c => `\n${c}`) 44 + 45 + await vitest.waitForStdout('Restarting due to config changes...') 46 + await vitest.waitForStdout('Waiting for file changes') 47 + }) 48 + 49 + test('rerun stops the previous browser server and restarts multiple times without port mismatch', async () => { 50 + const { fs, vitest } = await runInlineTests({ 51 + 'vitest.workspace.ts': [ 52 + './project-1', 53 + ], 54 + 'vitest.config.ts': {}, 55 + 'project-1/vitest.config.ts': { 56 + test: { 57 + browser: { 58 + enabled: true, 59 + name: 'chromium', 60 + provider: 'playwright', 61 + headless: true, 62 + }, 63 + }, 64 + }, 65 + 'project-1/basic.test.ts': ts` 66 + import { test } from 'vitest' 67 + test('basic test 1', () => {}) 68 + `, 69 + }, { watch: true }) 70 + 71 + await vitest.waitForStdout('Waiting for file changes') 72 + vitest.resetOutput() 73 + 74 + // editing the project config the first time restarts the browser server 75 + fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`) 76 + 77 + await vitest.waitForStdout('Restarting due to config changes...') 78 + await vitest.waitForStdout('Waiting for file changes') 79 + 80 + expect(vitest.stdout).not.toContain('is in use, trying another one...') 81 + expect(vitest.stderr).not.toContain('is in use, trying another one...') 82 + vitest.resetOutput() 83 + 84 + // editing the project the second time also restarts the server 85 + fs.editFile('./project-1/vitest.config.ts', c => `\n${c}`) 86 + 87 + await vitest.waitForStdout('Restarting due to config changes...') 88 + await vitest.waitForStdout('Waiting for file changes') 89 + 90 + expect(vitest.stdout).not.toContain('is in use, trying another one...') 91 + expect(vitest.stderr).not.toContain('is in use, trying another one...') 92 + vitest.resetOutput() 93 + })
+2 -2
test/workspaces-browser/space_browser/vitest.config.ts
··· 4 4 test: { 5 5 browser: { 6 6 enabled: true, 7 - name: process.env.BROWSER || 'chrome', 7 + name: process.env.BROWSER || 'chromium', 8 8 headless: true, 9 - provider: process.env.PROVIDER || 'webdriverio', 9 + provider: process.env.PROVIDER || 'playwright', 10 10 }, 11 11 }, 12 12 })
+17 -10
packages/vitest/src/node/core.ts
··· 83 83 public distPath = distDir 84 84 85 85 private _cachedSpecs = new Map<string, WorkspaceSpec[]>() 86 + private _workspaceConfigPath?: string 86 87 87 88 /** @deprecated use `_cachedSpecs` */ 88 89 projectTestFiles = this._cachedSpecs ··· 110 111 this._browserLastPort = defaultBrowserPort 111 112 this.pool?.close?.() 112 113 this.pool = undefined 114 + this.closingPromise = undefined 115 + this.projects = [] 116 + this.resolvedProjects = [] 117 + this._workspaceConfigPath = undefined 113 118 this.coverageProvider = undefined 114 119 this.runningPromise = undefined 115 120 this._cachedSpecs.clear() ··· 145 150 const serverRestart = server.restart 146 151 server.restart = async (...args) => { 147 152 await Promise.all(this._onRestartListeners.map(fn => fn())) 153 + this.report('onServerRestart') 154 + await this.close() 148 155 await serverRestart(...args) 149 - // watcher is recreated on restart 150 - this.unregisterWatcher() 151 - this.registerWatcher() 152 156 } 153 157 154 158 // since we set `server.hmr: false`, Vite does not auto restart itself 155 159 server.watcher.on('change', async (file) => { 156 160 file = normalize(file) 157 161 const isConfig = file === server.config.configFile 162 + || this.resolvedProjects.some(p => p.server.config.configFile === file) 163 + || file === this._workspaceConfigPath 158 164 if (isConfig) { 159 165 await Promise.all(this._onRestartListeners.map(fn => fn('config'))) 166 + this.report('onServerRestart', 'config') 167 + await this.close() 160 168 await serverRestart() 161 - // watcher is recreated on restart 162 - this.unregisterWatcher() 163 - this.registerWatcher() 164 169 } 165 170 }) 166 171 } ··· 174 179 await this.cache.results.readFromCache() 175 180 } 176 181 catch { } 177 - 178 - await Promise.all(this._onSetServer.map(fn => fn())) 179 182 180 183 const projects = await this.resolveWorkspace(cliOptions) 181 184 this.resolvedProjects = projects ··· 193 196 if (this.config.testNamePattern) { 194 197 this.configOverride.testNamePattern = this.config.testNamePattern 195 198 } 199 + 200 + await Promise.all(this._onSetServer.map(fn => fn())) 196 201 } 197 202 198 203 public provide<T extends keyof ProvidedContext & string>(key: T, value: ProvidedContext[T]) { ··· 235 240 || this.projects[0] 236 241 } 237 242 238 - private async getWorkspaceConfigPath(): Promise<string | null> { 243 + private async getWorkspaceConfigPath(): Promise<string | undefined> { 239 244 if (this.config.workspace) { 240 245 return this.config.workspace 241 246 } ··· 251 256 }) 252 257 253 258 if (!workspaceConfigName) { 254 - return null 259 + return undefined 255 260 } 256 261 257 262 return join(configDir, workspaceConfigName) ··· 259 264 260 265 private async resolveWorkspace(cliOptions: UserConfig) { 261 266 const workspaceConfigPath = await this.getWorkspaceConfigPath() 267 + 268 + this._workspaceConfigPath = workspaceConfigPath 262 269 263 270 if (!workspaceConfigPath) { 264 271 return [await this._createCoreProject()]
+2 -1
packages/vitest/src/node/workspace.ts
··· 432 432 ) 433 433 } 434 434 435 + this.closingPromise = undefined 435 436 this.testProject = new TestProject(this) 436 437 437 438 this.server = server ··· 476 477 if (!this.closingPromise) { 477 478 this.closingPromise = Promise.all( 478 479 [ 479 - this.server.close(), 480 + this.server?.close(), 480 481 this.typechecker?.stop(), 481 482 this.browser?.close(), 482 483 this.clearTmpDir(),
-4
packages/vitest/src/node/cli/cli-api.ts
··· 73 73 stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout) 74 74 } 75 75 76 - ctx.onServerRestart((reason) => { 77 - ctx.report('onServerRestart', reason) 78 - }) 79 - 80 76 ctx.onAfterSetServer(() => { 81 77 if (ctx.config.standalone) { 82 78 ctx.init()