mirror of
https://github.com/oven-sh/bun.git
synced 2026-10-25 21:40:30 +08:00
Follow what changed in Node.js between 26.3.0 and 26.12.1
Five test files use the real `node` as a reference or as the server, and failed once the CI machines had 26.12.1. Each was traced to the release that changed the behavior. - Node-API (26.12): `node_api_create_object_with_properties` takes `const napi_value*`. Bun's copy of the header and its definition follow, so the three headers are byte-identical to upstream again. The test addon no longer declares the function itself: the header does, in either form. - node:http2 (26.11.0): the server entry points check `strictSingleValueFields` before `unknownProtocolTimeout`. Bun does too. - TLS renegotiation (26.10.0): the request of `renegotiate()` leaves ahead of a `write()` that has not finished. The fixture relied on the old order and now waits for the write. `fetch` was right to refuse. - TLS sessions (26.3.1, a security release): a session is only offered to the host it came from. The fixture names the host on every path. node's `TLSSocket` constructor does not read its own new format, so that cell accepts either answer under node and stays strict under Bun. - node:http (26.7.0): `writableFinished` says whether 'finish' was emitted. Bun's is unchanged, and the test says where the two differ. The macOS test machines keep an older node until they are baked again, so the two reference runs whose expected output changed are skipped below the release that changed it.
This commit is contained in:
9 files changed
+35
-24
No files matched your search
@@ -6543,6 +6543,10 @@ function initializeOptions(options) {
|
||||
if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS) throw $ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
|
||||
}
|
||||
|
||||
if (options.strictSingleValueFields !== undefined) {
|
||||
validateBoolean(options.strictSingleValueFields, "options.strictSingleValueFields");
|
||||
}
|
||||
|
||||
if (options.maxSessionInvalidFrames !== undefined)
|
||||
validateUint32(options.maxSessionInvalidFrames, "options.maxSessionInvalidFrames");
|
||||
|
||||
@@ -6554,10 +6558,6 @@ function initializeOptions(options) {
|
||||
validateUint32(options.unknownProtocolTimeout, "options.unknownProtocolTimeout");
|
||||
else options.unknownProtocolTimeout = 10000;
|
||||
|
||||
if (options.strictSingleValueFields !== undefined) {
|
||||
validateBoolean(options.strictSingleValueFields, "options.strictSingleValueFields");
|
||||
}
|
||||
|
||||
// Initialize http1Options bag for HTTP/1 fallback when allowHTTP1 is true.
|
||||
options.http1Options = { ...options.http1Options };
|
||||
if (options.Http1IncomingMessage !== undefined) {
|
||||
|
||||
@@ -1616,8 +1616,8 @@ extern "C" JS_EXPORT napi_status node_api_set_prototype(napi_env env,
|
||||
|
||||
extern "C" JS_EXPORT napi_status node_api_create_object_with_properties(napi_env env,
|
||||
napi_value prototype_or_null,
|
||||
napi_value* property_names,
|
||||
napi_value* property_values,
|
||||
const napi_value* property_names,
|
||||
const napi_value* property_values,
|
||||
size_t property_count,
|
||||
napi_value* result)
|
||||
{
|
||||
|
||||
@@ -55,8 +55,8 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env,
|
||||
NAPI_EXTERN napi_status NAPI_CDECL
|
||||
node_api_create_object_with_properties(napi_env env,
|
||||
napi_value prototype_or_null,
|
||||
napi_value* property_names,
|
||||
napi_value* property_values,
|
||||
const napi_value* property_names,
|
||||
const napi_value* property_values,
|
||||
size_t property_count,
|
||||
napi_value* result);
|
||||
#endif // NAPI_EXPERIMENTAL
|
||||
|
||||
@@ -290,7 +290,8 @@ for (const path of ["the listener of the server", "emit('connection')"]) {
|
||||
events: ["prefinish"],
|
||||
finished: true,
|
||||
writableEnded: true,
|
||||
writableFinished: true,
|
||||
// Since v26.7.0 node's says whether 'finish' was emitted. Bun's still says that nothing is left to write.
|
||||
writableFinished: typeof Bun !== "undefined",
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -400,7 +401,13 @@ for (const expectation of ["100-continue", "something-else"]) {
|
||||
// Only in Bun: when Node.js runs this file it must not spawn itself again.
|
||||
if (typeof Bun !== "undefined") {
|
||||
const { bunEnv, nodeExe } = await import("harness");
|
||||
const node = nodeExe();
|
||||
// Before v26.7.0 node's writableFinished did not wait for 'finish', and the macOS CI hosts can have an older node.
|
||||
const node = (() => {
|
||||
const node = nodeExe();
|
||||
if (!node) return null;
|
||||
const { stdout } = Bun.spawnSync({ cmd: [node, "-p", "process.versions.node"], env: bunEnv, stderr: "ignore" });
|
||||
return Bun.semver.order(stdout.toString().trim(), "26.7.0") >= 0 ? node : null;
|
||||
})();
|
||||
|
||||
describe("Node.js compatibility", () => {
|
||||
(node ? test : test.skip)("all tests pass in Node.js", async () => {
|
||||
|
||||
@@ -5021,9 +5021,7 @@ const optionChecksExpected = `{ strictSingleValueFields: "yes" }
|
||||
connect(https), createServer, createSecureServer, performServerHandshake
|
||||
{ unknownProtocolTimeout: -1, strictSingleValueFields: "yes" }
|
||||
TypeError [ERR_INVALID_ARG_TYPE]: The "options.strictSingleValueFields" property must be of type boolean. Received type string ('yes')
|
||||
connect(http), connect(https), connect(createConnection)
|
||||
RangeError [ERR_OUT_OF_RANGE]: The value of "options.unknownProtocolTimeout" is out of range. It must be >= 0 && <= 4294967295. Received -1
|
||||
createServer, createSecureServer, performServerHandshake
|
||||
every entry point
|
||||
other calls
|
||||
TypeError [ERR_INVALID_ARG_TYPE]: The "options.strictSingleValueFields" property must be of type boolean. Received type string ('yes')
|
||||
connect("not a url", { strictSingleValueFields: "yes" })
|
||||
@@ -5044,12 +5042,12 @@ other calls
|
||||
rejects with RangeError [ERR_OUT_OF_RANGE]: The value of "options.maxSessionInvalidFrames" is out of range. It must be >= 0 && <= 4294967295. Received -1
|
||||
promisify(connect)(https url, { maxSessionInvalidFrames: -1 })
|
||||
`;
|
||||
// An older node has no strictSingleValueFields option, and the macOS CI hosts can have one.
|
||||
// Before v26.11.0 node checked the options in another order, and the macOS CI hosts can have an older node.
|
||||
const optionChecksNode = (() => {
|
||||
const node = nodeExe();
|
||||
if (!node) return null;
|
||||
const { stdout } = Bun.spawnSync({ cmd: [node, "-p", "process.versions.node"], env: bunEnv, stderr: "ignore" });
|
||||
return parseInt(stdout.toString(), 10) >= 26 ? node : null;
|
||||
return Bun.semver.order(stdout.toString().trim(), "26.11.0") >= 0 ? node : null;
|
||||
})();
|
||||
|
||||
for (const [name, runtime] of [
|
||||
|
||||
+4
-2
@@ -303,7 +303,9 @@ async function serverWithSession() {
|
||||
export async function session() {
|
||||
const { server, port, session, connected } = await serverWithSession();
|
||||
const host = "127.0.0.1";
|
||||
const options = { session, rejectUnauthorized: false };
|
||||
// Since Node.js 26.3.1 a session is only offered to the host it came from. A socket that is already connected
|
||||
// does not say which that is, so every path names it.
|
||||
const options = { session, host, rejectUnauthorized: false };
|
||||
// The listener is attached in the turn that makes the socket: a wrap starts its handshake at once.
|
||||
async function reused(socket, event, prepare) {
|
||||
try {
|
||||
@@ -317,7 +319,7 @@ export async function session() {
|
||||
}
|
||||
try {
|
||||
return {
|
||||
"tls.connect({ port, session })": await reused(tls.connect({ port, host, ...options }), "secureConnect"),
|
||||
"tls.connect({ port, session })": await reused(tls.connect({ port, ...options }), "secureConnect"),
|
||||
"tls.connect({ socket, session })": await reused(
|
||||
tls.connect({ socket: await connected(), ...options }),
|
||||
"secureConnect",
|
||||
|
||||
@@ -5125,7 +5125,7 @@ describe("new tls.TLSSocket(socket) on the client side", () => {
|
||||
});
|
||||
|
||||
// Bun runs the functions of the fixture in this process. Node runs the same file as a
|
||||
// script, so each expected report below is also node v26.3.0's.
|
||||
// script, so each expected report below is also node v26.12.1's.
|
||||
async function underNode(mode: string) {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [nodeExe()!, join(import.meta.dir, "node-tls-client-wrap-fixture.mjs"), mode],
|
||||
@@ -5143,7 +5143,7 @@ describe("new tls.TLSSocket(socket) on the client side", () => {
|
||||
describe.concurrent.each([
|
||||
["bun", false, clientWrap.shutdown, clientWrap.mysql, clientWrap.peerCloses, clientWrap.session],
|
||||
["node", !nodeExe(), onNode("shutdown"), onNode("mysql"), onNode("peer-closes"), onNode("session")],
|
||||
] as const)("under %s", (_runtime, skip, shutdown, mysql, peerCloses, session) => {
|
||||
] as const)("under %s", (runtime, skip, shutdown, mysql, peerCloses, session) => {
|
||||
// Each cell calls the method and then destroy(): end() alone leaves a wrap open, on node too.
|
||||
it.skipIf(skip)(
|
||||
"end(), end(cb), destroySoon() and destroy() do not throw, and destroy() closes the wrap",
|
||||
@@ -5178,7 +5178,9 @@ describe("new tls.TLSSocket(socket) on the client side", () => {
|
||||
expect(await session()).toEqual({
|
||||
"tls.connect({ port, session })": true,
|
||||
"tls.connect({ socket, session })": true,
|
||||
"new TLSSocket(socket, { session })": true,
|
||||
// Since v26.3.1 node's getSession() puts the host in front of the session, and its constructor is the one
|
||||
// place that does not take it off again. The macOS CI hosts can have an older node.
|
||||
"new TLSSocket(socket, { session })": runtime === "bun" ? true : expect.any(Boolean),
|
||||
"tls.connect({ port }), then setSession()": true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -476,8 +476,13 @@ const backToBackRenegotiationServer = /* js */ `
|
||||
socket.resume();
|
||||
const responseFirst = head.startsWith("GET /response-first ");
|
||||
let asked = 0;
|
||||
let answered = false;
|
||||
(function ask() {
|
||||
if (asked === 3 && responseFirst) socket.write("HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nok");
|
||||
// Since Node.js 26.10 the request of a renegotiate() leaves ahead of a write() that has not finished.
|
||||
if (asked === 3 && responseFirst && !answered) {
|
||||
answered = true;
|
||||
return socket.write("HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\n\\r\\nok", ask);
|
||||
}
|
||||
if (asked === 4) {
|
||||
if (isHttp) socket.end("HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nConnection: close\\r\\n\\r\\nok");
|
||||
return;
|
||||
|
||||
@@ -3968,9 +3968,6 @@ test_napi_adjust_external_memory(const Napi::CallbackInfo &info) {
|
||||
extern "C" {
|
||||
napi_status node_api_set_prototype(napi_env env, napi_value object,
|
||||
napi_value value);
|
||||
napi_status node_api_create_object_with_properties(
|
||||
napi_env env, napi_value prototype_or_null, napi_value *property_names,
|
||||
napi_value *property_values, size_t property_count, napi_value *result);
|
||||
napi_status node_api_is_sharedarraybuffer(napi_env env, napi_value value,
|
||||
bool *result);
|
||||
napi_status node_api_create_sharedarraybuffer(napi_env env, size_t byte_length,
|
||||
|
||||
Reference in new issue
Block a user