From db8a01272eafef5f8c20553f87e9d1a2ea8e62b8 Mon Sep 17 00:00:00 2001 From: Christian Aurich Date: Tue, 25 Aug 2026 23:58:39 -0300 Subject: [PATCH] test: deflake fastutf8stream destroy and reopen tests Both tests read the destination file with no ordering guarantee against the fs.write() that Utf8Stream still has in flight, so under load the read can observe an empty file. In test-fastutf8stream-destroy the read is issued right after destroy(). In test-fastutf8stream-reopen it is ordered on 'drain', documented as emitted when the buffer has drained enough to allow continued writing, which says nothing about the bytes being observable in the file. The reopen path also emits a 'drain' of its own from a nextTick before the write has landed. Order both reads on 'write' instead, documented as emitted when a write operation has completed and emitted from #release() once the underlying write returned. In sync mode it is emitted from within write(), so the listener is attached before the write call. No data is lost by Utf8Stream here: re-reading the file after a failed assertion shows the expected content. This corrects an expectation of the tests, not the runtime. Signed-off-by: Christian Aurich --- test/parallel/test-fastutf8stream-destroy.js | 7 +++++-- test/parallel/test-fastutf8stream-reopen.js | 15 +++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/test/parallel/test-fastutf8stream-destroy.js b/test/parallel/test-fastutf8stream-destroy.js index bdf401f330ef..04f7cad8088f 100644 --- a/test/parallel/test-fastutf8stream-destroy.js +++ b/test/parallel/test-fastutf8stream-destroy.js @@ -34,8 +34,11 @@ function getTempFile() { assert.throws(() => stream.write('hello world\n'), Error); - readFile(dest, 'utf8', common.mustSucceed((data) => { - assert.strictEqual(data, 'hello world\n'); + // Reading now would race the fs.write() still in flight. + stream.once('write', common.mustCall(() => { + readFile(dest, 'utf8', common.mustSucceed((data) => { + assert.strictEqual(data, 'hello world\n'); + })); })); stream.on('finish', common.mustNotCall()); diff --git a/test/parallel/test-fastutf8stream-reopen.js b/test/parallel/test-fastutf8stream-reopen.js index d706a737c2cd..eb067beff3e1 100644 --- a/test/parallel/test-fastutf8stream-reopen.js +++ b/test/parallel/test-fastutf8stream-reopen.js @@ -43,9 +43,9 @@ function runTests(sync) { stream.reopen(); stream.once('ready', common.mustCall(() => { - assert.ok(stream.write('after reopen\n')); - - stream.once('drain', common.mustCall(() => { + // 'drain' can come from reopen() before this write completes. 'write' + // is emitted synchronously in sync mode, so attach before writing. + stream.once('write', common.mustCall(() => { readFile(after, 'utf8', common.mustSucceed((data) => { assert.strictEqual(data, 'hello world\nsomething else\n'); readFile(dest, 'utf8', common.mustSucceed((data) => { @@ -54,6 +54,8 @@ function runTests(sync) { })); })); })); + + assert.ok(stream.write('after reopen\n')); })); })); } @@ -85,9 +87,8 @@ function runTests(sync) { assert.strictEqual(stream.file, after); stream.once('ready', common.mustCall(() => { - assert.ok(stream.write('after reopen\n')); - - stream.once('drain', common.mustCall(() => { + // As above: 'drain' can come from reopen() before the write completes. + stream.once('write', common.mustCall(() => { readFile(dest, 'utf8', common.mustSucceed((data) => { assert.strictEqual(data, 'hello world\nsomething else\n'); readFile(after, 'utf8', common.mustSucceed((data) => { @@ -96,6 +97,8 @@ function runTests(sync) { })); })); })); + + assert.ok(stream.write('after reopen\n')); })); })); }