From 9f6c16fe02131c4d6061ef96e980e4e98ef9b781 Mon Sep 17 00:00:00 2001 From: affaan-m <124439313+affaan-m@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:04:21 -0400 Subject: [PATCH] fix(tests): make guarded parent replacement portable to Windows --- tests/lib/guarded-write.test.js | 74 +++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/tests/lib/guarded-write.test.js b/tests/lib/guarded-write.test.js index e3b163264..44e537e96 100644 --- a/tests/lib/guarded-write.test.js +++ b/tests/lib/guarded-write.test.js @@ -113,21 +113,81 @@ test('a directory destination is refused without writes', ({ file, options }) => assert.throws(() => writeFileNoFollow(file, 'off', options)); assert.ok(fs.statSync(file).isDirectory()); }); -test('a parent replacement at open is refused', ({ root, options }) => { +test('a parent replacement before native open is refused even when the file identity is unchanged', ({ root, options }) => { const parent = path.join(root, 'plugins'); fs.mkdirSync(parent); const file = path.join(parent, 'entry.js'); fs.writeFileSync(file, 'old activation bytes'); - replaceMethod('openSync', original => (...args) => { - const fd = original(...args); + const fileIdentity = fs.statSync(file, { bigint: true }); + const originalOpen = fs.openSync; + const originalClose = fs.closeSync; + const originalTruncate = fs.ftruncateSync; + let descriptor; + let swaps = 0; + let closes = 0; + let truncates = 0; + fs.openSync = (...args) => { if (args[0] === file && typeof args[1] === 'number') { + // The writer has pinned the parent, but has not opened the file yet. + // Moving an open file's parent is not portable to Windows. Keep the + // same file inode and bytes so only the parent identity rejects this. fs.renameSync(parent, `${parent}.old`); fs.mkdirSync(parent); - fs.writeFileSync(file, 'replacement'); + fs.renameSync(path.join(`${parent}.old`, 'entry.js'), file); + swaps++; + descriptor = originalOpen(...args); + return descriptor; } - return fd; - }, () => assert.throws(() => writeFileNoFollow(file, 'off', existing(options)), /changed/)); - assert.strictEqual(fs.readFileSync(file, 'utf8'), 'replacement'); + return originalOpen(...args); + }; + fs.closeSync = fd => { if (fd === descriptor) closes++; return originalClose(fd); }; + fs.ftruncateSync = (...args) => { truncates++; return originalTruncate(...args); }; + try { + assert.throws(() => writeFileNoFollow(file, 'off', existing(options)), error => { + assert.match(error.message, /changed after preflight/); + assert.strictEqual(error.cause, undefined, 'the parent guard, not an open error, refuses'); + return true; + }); + } finally { + fs.openSync = originalOpen; + fs.closeSync = originalClose; + fs.ftruncateSync = originalTruncate; + } + assert.strictEqual(swaps, 1); + assert.strictEqual(typeof descriptor, 'number'); + assert.strictEqual(closes, 1); + assert.strictEqual(truncates, 0); + const replacementIdentity = fs.statSync(file, { bigint: true }); + assert.strictEqual(replacementIdentity.dev, fileIdentity.dev); + assert.strictEqual(replacementIdentity.ino, fileIdentity.ino); + assert.strictEqual(fs.readFileSync(file, 'utf8'), 'old activation bytes'); + assert.ok(fs.statSync(`${parent}.old`).isDirectory()); +}); +test('a denied native open preserves its cause without closing an unallocated descriptor', ({ file, options }) => { + fs.writeFileSync(file, 'old activation bytes'); + const denied = Object.assign(new Error('fixture open denied'), { code: 'EPERM' }); + const originalClose = fs.closeSync; + const originalTruncate = fs.ftruncateSync; + let closes = 0; + let truncates = 0; + fs.closeSync = fd => { closes++; return originalClose(fd); }; + fs.ftruncateSync = (...args) => { truncates++; return originalTruncate(...args); }; + try { + replaceMethod('openSync', original => (...args) => { + if (args[0] === file && typeof args[1] === 'number') throw denied; + return original(...args); + }, () => assert.throws(() => writeFileNoFollow(file, 'off', existing(options)), error => { + assert.strictEqual(error.cause, denied); + assert.strictEqual(error.code, 'EPERM'); + return true; + })); + } finally { + fs.closeSync = originalClose; + fs.ftruncateSync = originalTruncate; + } + assert.strictEqual(closes, 0); + assert.strictEqual(truncates, 0); + assert.strictEqual(fs.readFileSync(file, 'utf8'), 'old activation bytes'); }); test('destination validator is mandatory and must return the same path', ({ file, options }) => { assert.throws(() => writeFileNoFollow(file, 'off', {}), /validateDestination/);