test(files): the native-ignore watcher test waits on delivered events, not a fixed sleep (#30236)

It flaked on loaded CI shards when a single write never reached the watcher. Files are now rewritten until seen, readiness is proven before the storm, and a sentinel written after the storm replaces the 300 ms debounce sleep. Disabling the ignores still fails it.
This commit is contained in:
Neil
2026-10-18 00:10:44 -07:00
committed by GitHub
parent 262a61703a
commit e6eaabb587
@@ -5,6 +5,8 @@ import { subscribe, type AsyncSubscription, type Event } from '@parcel/watcher'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { WATCHER_IGNORE_DIRS, buildParcelWatcherIgnoreOptions } from './filesystem-watcher-ignore'
const DELIVERY_TIMEOUT_MS = 8_000
describe('filesystem watcher native ignores', () => {
let root: string | null = null
let subscription: AsyncSubscription | null = null
@@ -41,6 +43,21 @@ describe('filesystem watcher native ignores', () => {
},
buildParcelWatcherIgnoreOptions(WATCHER_IGNORE_DIRS)
)
const seen = (file: string): boolean => events.some((event) => event.path === file)
// Why: under CI load a single write can miss delivery, so rewrite until each file is seen.
const writeUntilSeen = async (files: string[]): Promise<void> => {
await vi.waitFor(
async () => {
const missing = files.filter((file) => !seen(file))
await Promise.all(missing.map((file) => writeFile(file, 'source')))
expect(missing).toEqual([])
},
{ timeout: DELIVERY_TIMEOUT_MS, interval: 200 }
)
}
// Proves both watched levels are live before the storm, so the ignore check can't pass vacuously.
await writeUntilSeen([join(root, 'ready.ts'), join(nestedSource, 'ready.ts')])
const generatedNames = Array.from({ length: 20 }, (_, index) => `generated-${index}.js`)
// Windows forbids control characters in filenames.
@@ -55,18 +72,11 @@ describe('filesystem watcher native ignores', () => {
if (process.platform !== 'win32') {
sourceFiles.push(join(root, 'source\nnewline.ts'), join(nestedSource, 'source\nnewline.ts'))
}
await Promise.all(sourceFiles.map((file) => writeFile(file, 'source')))
await writeUntilSeen(sourceFiles)
// Why: events arrive in order, so once a file written after the storm is seen, any
// unignored generated event is already recorded. Replaces a fixed debounce sleep.
await writeUntilSeen([join(nestedSource, 'sentinel.ts')])
await vi.waitFor(
() => {
for (const sourceFile of sourceFiles) {
expect(events.some((event) => event.path === sourceFile)).toBe(true)
}
},
{ timeout: 8_000 }
)
// Why: ignored callbacks must stay absent after the native debounce has drained.
await new Promise((resolve) => setTimeout(resolve, 300))
expect(errors).toEqual([])
const generatedPaths = new Set(generatedFiles)
expect(events.filter((event) => generatedPaths.has(event.path))).toEqual([])