File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff 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 ( )
Original file line number Diff line number Diff line change 11import { test } from 'vitest'
22import 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
1117function triggerNodeWarning ( ) {
@@ -16,4 +22,4 @@ function triggerNodeWarning() {
1622 emitter . addListener ( 'message' , ( ) => { } )
1723}
1824
19- class TestFixturesCustomEmitter extends EventEmitter { }
25+ class TestFixturesCustomEmitter extends EventEmitter { }
Original file line number Diff line number Diff line change 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+ } )
You can’t perform that action at this time.
0 commit comments