From deb0d7e1b5521c690fb78e8455c2eecc66333fb2 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 13 Sep 2026 04:56:35 +0800 Subject: [PATCH] test(file-reference): publish restored workspace fixtures atomically --- .../file-reference-local/tests/search.spec.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/context/file-reference-local/tests/search.spec.ts b/packages/context/file-reference-local/tests/search.spec.ts index 1b50e812b4..a12b41a5b6 100644 --- a/packages/context/file-reference-local/tests/search.spec.ts +++ b/packages/context/file-reference-local/tests/search.spec.ts @@ -1,4 +1,4 @@ -import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' +import { chmod, mkdir, mkdtemp, readdir, rename, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' @@ -18,12 +18,12 @@ vi.mock('node:fs/promises', async (importOriginal) => { const actual = await importOriginal() return { ...actual, - readdir: (async (path: unknown, ...rest: never[]) => { + readdir: vi.fn((async (path: unknown, ...rest: never[]) => { if (fsControl.denyReaddir !== undefined && String(path) === fsControl.denyReaddir) { throw Object.assign(new Error('EACCES: injected unreadable directory'), { code: 'EACCES' }) } return (actual.readdir as (path: unknown, ...args: never[]) => Promise)(path, ...rest) - }) as typeof actual.readdir, + }) as typeof actual.readdir), } }) @@ -68,6 +68,7 @@ afterEach(async () => { for (const locked of locks.splice(0)) await chmod(locked, 0o700).catch(() => undefined) for (const instance of searches.splice(0)) instance.dispose() await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))) + vi.mocked(readdir).mockClear() }) describe('file-reference grammar', () => { @@ -198,6 +199,8 @@ describe('WorkspaceFileSearch', () => { const files = search(root) const signal = new AbortController().signal expect(await files.list('README', signal)).toEqual([{ path: 'README.md', kind: 'file' }]) + const rootReads = () => vi.mocked(readdir).mock.calls.filter(([path]) => String(path) === root).length + const initialReads = rootReads() // A root that vanishes under a live index: an unreadable branch costs its // own candidates, but an unreadable root must not be published as an @@ -205,13 +208,16 @@ describe('WorkspaceFileSearch', () => { await rm(root, { recursive: true, force: true }) files.invalidate() expect(await files.list('README', signal)).toEqual([{ path: 'README.md', kind: 'file' }]) - await new Promise((resolve) => { setTimeout(resolve, 50) }) - expect(await files.list('README', signal)).toEqual([{ path: 'README.md', kind: 'file' }]) + await vi.waitFor(async () => { + expect(await files.list('README', signal)).toEqual([{ path: 'README.md', kind: 'file' }]) + expect(rootReads()).toBeGreaterThan(initialReads + 1) + }) - // The failed attempt left the index stale, so its return is picked up - // without waiting for another invalidation. - await mkdir(root, { recursive: true }) - await writeFile(join(root, 'restored.ts'), 'restored') + // A retry can read the root as soon as it exists; publish its files together. + const replacement = await mkdtemp(`${root}-replacement-`) + roots.push(replacement) + await writeFile(join(replacement, 'restored.ts'), 'restored') + await rename(replacement, root) await vi.waitFor(async () => { expect(await files.list('restored', signal)).toEqual([{ path: 'restored.ts', kind: 'file' }]) })