test_runner: backport #64706 to v22.x — non-ASCII test stdout can silently drop a whole test file
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 新手友好度
- 58/100
- Issue 類型
- 缺陷
- 描述清晰度
- 描述清楚
- 活躍度
- 活躍
- 技術堆疊
- javascript, node.js
- 領域
- testing-qa
研究方向
從修正 #64706 開始,並比較 v22.x-staging 上的 lib/internal/test_runner/runner.js,然後在 v22.x 線上使用 node --test 執行 repro.mjs 範例。修正已回移植,且非 ASCII stdout 的重現不再損壞或遺失測試檔案,即表示完成。
由索引模型根據 Issue 內容生成。
描述
Request
Please consider backporting #64706 (fix for #64061) to the v22.x release line.
State as of 2026-09-09, checked with the GitHub contents API against lib/internal/test_runner/runner.js, looking for ) >>> 0) + kSerializedSizeHeader:
| ref | has the fix |
|---|---|
main |
yes |
v26.x |
yes |
v24.x, v24.x-staging |
yes |
v22.x, v22.x-staging |
no |
#64706 carries no dont-land-on-v22.x label, and I could not find an existing backport request or backport PR for v22.x.
The line is actively released — the most recent v22 release is v22.23.2 (2026-07-29), three days after #64706 landed on main (2026-07-26).
Why this may deserve more weight than the original report suggested
#64061 was reported as an intermittent CI flake. On v22.19.0 I traced the trigger, and it turns out not to be exotic: it is ordinary non-ASCII text written to stdout by a test.
FileTest.#processRawBuffer advances bufferHead past a consumed message and then reads the next four bytes as a length without re-checking for the FF 0F header:
bufferHead = TypedArrayPrototypeSubarray(concatenatedBuffer, fullMessageSize);
this.#rawBufferSize = TypedArrayPrototypeGetLength(bufferHead);
...
while (bufferHead?.length >= kSerializedSizeHeader) {
const fullMessageSize = (
bufferHead[kV8HeaderLength] << 24 |
bufferHead[kV8HeaderLength + 1] << 16 |
bufferHead[kV8HeaderLength + 2] << 8 |
bufferHead[kV8HeaderLength + 3]
) + kSerializedSizeHeader; // signed 32-bit
if (this.#rawBufferSize < fullMessageSize) break;
When a test's own stdout follows a report message inside the same read, those bytes are interpreted as a length. Every UTF-8 lead/continuation byte is >= 0x80, so any non-ASCII output — CJK, emoji, accented Latin — can make fullMessageSize negative, which defeats the break guard and hands garbage to DefaultDeserializer.
In our repository the emitter was a server start-up banner containing an emoji, printed by the code under test. Three sightings over two days, always the same test file.
Minimal deterministic reproduction
No load, no concurrency, no flakiness. One write():
// repro.mjs — node --test repro.mjs
import { writeSync } from 'node:fs';
import test from 'node:test';
import { DefaultSerializer } from 'node:v8';
test('a passing test', () => {});
const s = new DefaultSerializer();
s.writeHeader();
s.writeValue({ __proto__: null, type: 'test:diagnostic',
data: { __proto__: null, nesting: 0, message: 'hi', file: 'repro.mjs' } });
const payload = s.releaseBuffer();
const size = Buffer.alloc(4);
size.writeUInt32BE(payload.length);
const message = Buffer.concat([Buffer.from([0xff, 0x0f]), size, payload]);
// a report message, immediately followed by ordinary test stdout, in ONE write
const tail = process.env.ASCII ? '\nserver listening -> http://localhost:3456\n'
: '\n\u{1F7E1} 서버 실행 중 -> http://localhost:3456\n';
writeSync(1, Buffer.concat([message, Buffer.from(tail, 'utf8')]));
On v22.19.0, Windows 11 x64:
$ node --test repro.mjs
not ok 1 - repro.mjs
error: 'Unable to deserialize cloned data due to invalid or unsupported version.'
# fail 1
$ ASCII=1 node --test repro.mjs
ok 1 - a passing test
# fail 0
The only difference between the two runs is whether the trailing stdout is ASCII.
I only have v22.19.0 on this machine, so I have not run this against a fixed release line — the report above is strictly about v22.19.0.
Three symptoms, and one of them is silent
Also observed on v22.19.0, depending on where in the stream the corruption lands:
- a file-level failure carrying the message above (the symptom in #64061);
- the file's results vanish with no failure and no summary. Running a poisoned file together with a healthy one printed only the healthy file's
ok 1/ok 2— no1..N, no# tests, no# fail, no error text. The exit code was1, but the TAP body reads as a clean pass; - with a length byte below
0x80but large, the runner waits for a message that never arrives and hangs.
Symptom 2 is the reason I am filing this: on a line that is still in service, a whole test file can drop out of a run while the output looks green, and only the exit code disagrees.
Offer
If the missing step is just a backport-requested-v22.x label or an approval, I am happy to open the [v22.x backport] PR against v22.x-staging — please say so.
- 主要語言
- JavaScript
- 星號
- 122k
- 分支
- 38.4k
- 平均合併
- 4 天 10 小時
- 30 天內合併 PR
- 276
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
nodejs/node 的其他 Issue
-
doc
難度 1/5 1 小時以內 新手友好度 90/100
維護者通常 1 天內回覆
-
doc
難度 2/5 1-3 小時 新手友好度 65/100
維護者通常 1 天內回覆
-
build
難度 1/5 1 小時以內 新手友好度 88/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 84/100
nodejs/node#65994 · 2 則留言 · 2 個 reaction ·
維護者通常 1 天內回覆
-
feature request
難度 2/5 1-3 小時 新手友好度 68/100
維護者通常 1 天內回覆
相似的 Issue
-
難度 2/5 1-3 小時 新手友好度 85/100
維護者通常 3 天內回覆
-
Add: Atlas TV未關閉channels:add check:passed
難度 2/5 1-3 小時 新手友好度 74/100
維護者通常 4 天內回覆
-
bug
難度 2/5 1-3 小時 新手友好度 82/100
jaegertracing/jaeger-ui#4547 · 3 則留言 ·
維護者通常 1 天內回覆
-
feedback simulation workshop
難度 2/5 1-3 小時 新手友好度 75/100
githubnext/gh-aw-workshop#4090 ·
維護者通常 1 天內回覆
-
bug deck: add to staging level: missing p-feature: Manage Submissions p-feature: Submissions and process priority: MUST HAVE ready for dev lead role: missing size: missing time sensitive
難度 2/5 1-3 小時 新手友好度 67/100
hackforla/tdm-calculator#3581 ·
維護者通常 2 天內回覆