Skip to content

Commit a31f86a

Browse files
authored
fix: coalesce concurrent watch-mode restarts, deflake the pool stderr fixture (#10822)
1 parent 1ffafc0 commit a31f86a

3 files changed

Lines changed: 56 additions & 3 deletions

File tree

packages/vitest/src/node/core.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,30 @@ export class Vitest {
261261
)
262262
}
263263

264-
private async _restart(reason?: string) {
264+
private _restartPromise?: Promise<void>
265+
private _restartQueued = false
266+
267+
// Restarts must not overlap: chokidar regularly delivers several change
268+
// events for one edit, and a restart that starts while another is still
269+
// re-creating the servers reports `onServerRestart` to reporters that were
270+
// re-instantiated but not yet initialized.
271+
private _restart(reason?: string): Promise<void> {
272+
if (this._restartPromise) {
273+
this._restartQueued = true
274+
return this._restartPromise
275+
}
276+
this._restartPromise = (async () => {
277+
do {
278+
this._restartQueued = false
279+
await this._restartNow(reason)
280+
} while (this._restartQueued)
281+
})().finally(() => {
282+
this._restartPromise = undefined
283+
})
284+
return this._restartPromise
285+
}
286+
287+
private async _restartNow(reason?: string) {
265288
await Promise.all(this._onRestartListeners.map(fn => fn(reason)))
266289
this.report('onServerRestart', reason)
267290
await this.close()

test/e2e/fixtures/pool/write-to-stdout-and-stderr.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { test } from 'vitest'
22
import EventEmitter from 'node:events'
33

4-
test('write to streams', () => {
4+
test('write to streams', async () => {
55
process.stdout.write('Worker writing to stdout')
66
process.stderr.write('Worker writing to stderr')
77

8+
// `emitWarning` prints to stderr asynchronously: without waiting for the
9+
// event (plus one tick for the default handler's write), a fast worker
10+
// teardown can exit before the warning reaches the captured stderr
11+
const warning = new Promise<void>(resolve => process.once('warning', () => resolve()))
812
triggerNodeWarning()
13+
await warning
14+
await new Promise(resolve => setImmediate(resolve))
915
})
1016

1117
function triggerNodeWarning() {
@@ -16,4 +22,4 @@ function triggerNodeWarning() {
1622
emitter.addListener('message', () => {})
1723
}
1824

19-
class TestFixturesCustomEmitter extends EventEmitter {}
25+
class TestFixturesCustomEmitter extends EventEmitter {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { runVitest } from '#test-utils'
2+
import { expect, test } from 'vitest'
3+
4+
// chokidar regularly delivers several change events for one config edit, each
5+
// triggering a restart. A restart that begins while another is still
6+
// re-creating the servers used to report `onServerRestart` to reporters that
7+
// were re-instantiated but not yet initialized, crashing the run with
8+
// "Cannot read properties of undefined (reading 'logger')".
9+
test('concurrent restarts are coalesced instead of overlapping', async () => {
10+
const { ctx, vitest } = await runVitest({
11+
root: 'fixtures/watch',
12+
watch: true,
13+
})
14+
15+
const restart = (ctx as any)._restart.bind(ctx)
16+
await Promise.all([restart('config'), restart('config'), restart('config')])
17+
18+
expect(vitest.stdout).toContain('Restarting due to config changes')
19+
expect(vitest.stderr).not.toContain('Cannot read properties')
20+
21+
// the restarted instance is functional: a rerun still works
22+
await ctx!.rerunFiles()
23+
expect(vitest.stdout).toContain('RERUN')
24+
})

0 commit comments

Comments
 (0)
Sponsor
SponsoredKunjungi sekarang
Promo