|
1 | 1 | import { spawnSync } from 'node:child_process'; |
2 | | -import { chmod, mkdir, readFile, rm, utimes, writeFile } from 'node:fs/promises'; |
| 2 | +import { chmod, mkdir, readFile, rename, rm, utimes, writeFile } from 'node:fs/promises'; |
3 | 3 | import * as path from 'node:path'; |
4 | 4 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
5 | 5 |
|
@@ -33,6 +33,13 @@ async function holdLock(pid: number, token = 'theirs') { |
33 | 33 | await mkdir(lock()); |
34 | 34 | await writeFile(path.join(lock(), 'owner.json'), JSON.stringify({ pid, token })); |
35 | 35 | } |
| 36 | +/** Release that lock as a holder does: moved aside in one step, so a waiter |
| 37 | + * never renames its own lock onto the emptied directory mid-removal. */ |
| 38 | +async function releaseLock() { |
| 39 | + const aside = `${lock()}.released`; |
| 40 | + await rename(lock(), aside); |
| 41 | + await rm(aside, { recursive: true }); |
| 42 | +} |
36 | 43 | const lockToken = async () => (JSON.parse(await readFile(path.join(lock(), 'owner.json'), 'utf8')) as { token: string }).token; |
37 | 44 | /** A pid no process has: one that just exited. */ |
38 | 45 | const deadPid = () => spawnSync(process.execPath, ['-e', '']).pid!; |
@@ -86,7 +93,7 @@ describe('createSurfaceIdAllocator', () => { |
86 | 93 | void waiting.then(() => { settled = true; }); |
87 | 94 | await new Promise((resolve) => setTimeout(resolve, 50)); |
88 | 95 | expect(settled).toBe(false); |
89 | | - await rm(lock(), { recursive: true }); |
| 96 | + await releaseLock(); |
90 | 97 | expect(await waiting).toEqual(['surface:1']); |
91 | 98 | await expect(readFile(path.join(lock(), 'owner.json'))).rejects.toThrow(); |
92 | 99 | }); |
|
0 commit comments