From 0789dd92fd646f536b6c4888154de2ba188d8f8e Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 20 May 2026 20:21:34 +0000 Subject: [PATCH] test: fix flaky watch-mode tests with atomic writes Use atomic file writes (write to temp file then rename) instead of direct writeFileSync in watch-mode tests. writeFileSync is not atomic - it truncates the file before writing, creating a race window where the ESM loader can read an empty/partial file and produce SyntaxErrors. This fix affects: - test/sequential/test-watch-mode-worker.mjs - test/sequential/test-watch-mode.mjs Both now write to a temp file and rename atomically, eliminating the truncation race. The interval timing in the worker test is also aligned with the main test (2500ms) to reduce the race probability. Refs: https://github.com/nodejs/node/issues/62275 --- test/sequential/test-watch-mode-worker.mjs | 19 +++++++++++++++---- test/sequential/test-watch-mode.mjs | 20 +++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/test/sequential/test-watch-mode-worker.mjs b/test/sequential/test-watch-mode-worker.mjs index b7bc1a94a87ba4..dd58a6d58b753e 100644 --- a/test/sequential/test-watch-mode-worker.mjs +++ b/test/sequential/test-watch-mode-worker.mjs @@ -5,7 +5,7 @@ import path from 'node:path'; import { execPath } from 'node:process'; import { describe, it } from 'node:test'; import { spawn } from 'node:child_process'; -import { writeFileSync, readFileSync } from 'node:fs'; +import { writeFileSync, readFileSync, renameSync, unlinkSync } from 'node:fs'; import { inspect } from 'node:util'; import { pathToFileURL } from 'node:url'; import { createInterface } from 'node:readline'; @@ -14,9 +14,20 @@ if (common.isIBMi) common.skip('IBMi does not support `fs.watch()`'); function restart(file, content = readFileSync(file)) { - writeFileSync(file, content); - const timer = setInterval(() => writeFileSync(file, content), common.platformTimeout(250)); - return () => clearInterval(timer); + // Use atomic writes to avoid flakiness: write to a temp file, then rename. + // writeFileSync is not atomic (it truncates the file before writing), + // which can cause the ESM loader to read an empty/partial file. + const tmpFile = file + '.tmp'; + const write = () => { + writeFileSync(tmpFile, content); + renameSync(tmpFile, file); + }; + write(); + const timer = setInterval(write, common.platformTimeout(2500)); + return () => { + clearInterval(timer); + try { unlinkSync(tmpFile); } catch { /* best-effort cleanup */ } + }; } let tmpFiles = 0; diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index 707210a021f944..605b4389f25454 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -5,7 +5,7 @@ import path from 'node:path'; import { execPath } from 'node:process'; import { describe, it } from 'node:test'; import { spawn } from 'node:child_process'; -import { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; +import { writeFileSync, readFileSync, mkdirSync, renameSync, unlinkSync } from 'node:fs'; import { inspect } from 'node:util'; import { pathToFileURL } from 'node:url'; import { once } from 'node:events'; @@ -17,10 +17,20 @@ if (common.isIBMi) const supportsRecursive = common.isMacOS || common.isWindows; function restart(file, content = readFileSync(file)) { - // To avoid flakiness, we save the file repeatedly until test is done - writeFileSync(file, content); - const timer = setInterval(() => writeFileSync(file, content), common.platformTimeout(2500)); - return () => clearInterval(timer); + // Use atomic writes to avoid flakiness: write to a temp file, then rename. + // writeFileSync is not atomic (it truncates the file before writing), + // which can cause the ESM loader to read an empty/partial file. + const tmpFile = file + '.tmp'; + const write = () => { + writeFileSync(tmpFile, content); + renameSync(tmpFile, file); + }; + write(); + const timer = setInterval(write, common.platformTimeout(2500)); + return () => { + clearInterval(timer); + try { unlinkSync(tmpFile); } catch { /* best-effort cleanup */ } + }; } let tmpFiles = 0;