node:test's mock.fn(original[, implementation]) and t.mock.fn compile.
A Mock<F> is the function-with-properties record { %call, mock }: the
callable is a wrapper closure that shares the mock context with the mock
field. The context records each call's arguments, result, error and this,
and keeps the current implementation, the original and the one-time
implementations by call index. fn.mock supports, as in Node 24.22:
- calls, read as a copy of the log;
- callCount();
- mockImplementation(fn);
- mockImplementationOnce(fn[, onCall]), for the next call by default, a
later registration for the same index replacing the earlier one;
- resetCalls();
- restore(), which brings back the original.
A throwing implementation records the error and rethrows it. Functions
with fixed parameters or a single rest array parameter are supported; a
union return type needs an undefined arm. mock.fn() without an original,
mock.method, the tracker's reset and restoreAll, and t.mock.method are
refused with SC2020.
The node:test fixture mock-fn compares the behavior with Node, and
tests/diagnostics/mock-fences.ts covers the refusals.
Atomics operations on 8-, 16- and 32-bit integer arrays are sequentially
consistent atomic instructions on the element, and no longer take the
process-wide shared-memory lock. Other reads and writes of shared storage
are relaxed atomic accesses: element reads and writes access the element,
and copies, fills, conversions, searches and sorts read shared bytes into
a private copy and write back only the range they change, so concurrent
accesses to the same bytes are free of data races. Non-shared storage
keeps its plain memory accesses. Atomics.wait and Atomics.notify queue
waiters in buckets chosen by location, each with its own lock and
condition variable, so waiting threads no longer serialize other Atomics.
Atomics.store answers +0 for a value that truncates to -0, as Node does.
The shared storage runtime test also runs under ThreadSanitizer.
node:test changes, matching Node 24.22:
- t.before, t.after, t.beforeEach and t.afterEach lower. t.before runs
when it is called; a throw fails the test and the subtests that follow,
which skip their body and beforeEach hooks and run their afterEach hooks.
t.beforeEach and t.afterEach wrap every descendant subtest. t.after runs
after the test's afterEach chain, also when the body throws, and a throw
fails the test.
- t.plan(count) counts the test's t.assert calls and subtests. A mismatch
fails the test with Node's "plan expected N assertions but received M"
string, unless an assertion failed first.
- t.fullName joins the names from the outermost suite or test with " > ".
- Tests and suites under a todo suite or test are todo, with a bare TODO
directive, unless they are skipped or carry their own todo message. A
todo suite reports its directive.
- describe.only, suite.only and { only: true } on a suite mark it. At each
level, when a child is marked or contains a marked test or suite, only
those children run.
- The failing-tests section lists failures in completion order, so
subtests come before the test that ran them, and prints thrown strings
quoted, as Node inspects them.
The fixtures ctx-hooks, ctx-plan, todo-suites, suite-variants,
only-nested and thrown-values compare the reporter output and exit code
with Node.
pipeline() accepts a source function (an async generator function called
with { signal }) and a destination async function, following
pipeline.js: the destination's promise value resolves the
stream/promises form and reaches the callback's second argument, and
pipeline() answers a PassThrough for it. A rejected destination destroys
that PassThrough with the reason and fails the pipeline.
Stages may take the { signal } argument: the pipeline creates its own
AbortController when one does, and aborts it when the pipeline finishes
or fails, as finishImpl does. Stage functions take the source and
{ signal } through the closure ABI, and the PassThrough and the value
are released with the pipeline.
The stream unit now links the emitter unit whenever it links, so a
pipeline of functions builds without constructing a stream. An async
generator destination and reading the PassThrough a function destination
returns are refused at compile time.
Encoding an array key added each element's size to a running length
with unchecked arithmetic, and wrote into a fixed stack buffer whenever
the sum fit. A sparse array with a large length (2^29 elements or more)
wrapped the sum on 32-bit targets, so the write landed before the buffer;
on every target the encoder also visited every index of such an array.
Array keys now have at most 65536 elements, and their encoding at most
2^31 - 1 bytes. The size is computed first with sums that cannot wrap,
the buffer is sized to it, and keys over either limit make the lookup or
insert throw a TypeError before anything is read or written. The Node.js
implementation throws the same TypeError for keys of more than 65536
elements. Sparse elements are walked in one pass in index order, and the
index growth computation checks its size arithmetic too.
InternTable keys can be arrays of numbers or of strings, compared by their
elements: numbers by SameValueZero, strings by content, and holes equal to
each other only. The runtime stores an array key as one byte string that
encodes its elements; the Node.js implementation encodes it into a Map key
the same way.
Replaced table indexes are now freed. Every thread that uses a table
records the epoch it read at the start of each lookup and insert, and an
index retired in an earlier epoch than every recorded one is freed. Worker
threads stop holding retirements back when they exit.
Number indexes keep a tag byte per slot ahead of the key and value pairs,
so a lookup of an absent key reads the dense tag bytes instead of pairs.
getOrInsertComputed lowers an arrow callback with an expression body in
place, with its parameter bound to the key, so interning through
`(key) => new T(...)` creates no closure.
@scriptc/experimental exports InternTable<K, V>: a named table that every
thread of a scriptc program shares and that each thread has its own copy
of under Node.js. Keys are strings or numbers with Map equality; values are
published (@scriptc/threads) when stored, and the first stored value of a
key is kept. Members are get, has, size, getOrInsert and
getOrInsertComputed.
The runtime table (scr_itable.c) splits each table into 64 segments.
Lookups take no lock and do not write; inserts take a segment's spin lock
for a probe and a few stores, and callbacks run without a lock. Number
tables keep keys and values in the index; string tables keep entries in
segment-owned chunks behind tagged slots. Replaced indexes are kept.
The compiler lowers the class to a synthetic class with one f64 handle
field and five intrinsics, with interned helpers per member and type and
an in-place lowering of getOrInsertComputed that creates a function
expression callback only on a miss. Stored values go through the publish
intrinsic, so their types get the write guard. scriptc refuses names that
are not string literals, one name opened with different type arguments,
and key or value types a table cannot store.
scr_publish walks small graphs in inline buffers and reads
SCRIPTC_PUBLISH_TRACE once, instead of allocating and calling getenv on
every call.
createCipheriv, createDecipheriv, update(), setAAD() and setAuthTag()
now read the value a typed-ref box holds when it is a string, view,
ArrayBuffer or KeyObject. ERR_INVALID_ARG_TYPE names the class of a
class instance that arrives through an untyped value, as Node does,
instead of Object. An unknown output encoding that is not a string is
rendered as Node's %s formats it (util.inspect at depth 0, or the
object's own toString); a function, or an instance of a class that
defines toString, throws a coded SC2020 error. Cipher programs link the
inspect unit for that rendering.
5250 passes each argument through `any`, including class instances,
records and arrays that Node rejects, and 5254 covers output encodings
that are not strings from JavaScript.
pipeline() accepts a run of async generator function stages between
streams, following pipeline.js: each stage is called during setup with the
previous stream (a Readable is async iterable) or the previous stage's
generator, and the last generator is pumped into the next stream with
pumpToNode's write, drain and end waits. An error in the pump loop returns
the generator before the pipeline finishes, so the stage's finally runs.
A stage's source parameter is typed as what the pipeline passes it and may
only head for await loops. Function sources and destinations, stages that
take the signal argument, and stages separated by streams are refused.
Readable.from accepts sync and async generators of strings and Buffers,
following from.js: values are pushed while push() accepts, an async
generator's next() promise is awaited once per value, and destroy runs
the generator's throw() and return() before the destroy callback.
for await over a stream follows createAsyncIterator: the loop registers
its 'readable' listener, reads through read(), and a parked loop wakes
through the 'readable' tick. push(null) before the first read ends the
stream after the next read, finished() answers an already settled stream
with its result on a nextTick, read(0) on an object-mode stream reads
nothing, and write(string, 'buffer') throws ERR_UNKNOWN_ENCODING.
- dns.lookup(hostname, callback), lookup(hostname, family, callback)
and lookup(hostname, options, callback) with family (0/4/6, 'IPv4',
'IPv6'), all, order and verbatim, validated with Node's messages.
getaddrinfo answers any family, ordered verbatim or one family first;
literal addresses answer on the next tick, an empty hostname gives
Node's null answer and DEP0118 warning, and failures carry code,
errno, syscall and hostname.
- dns.lookupService(address, port, callback) checks the address and
port as Node does and answers getnameinfo's host and service.
- dns.getServers() lists the nameservers in /etc/resolv.conf, and
getDefaultResultOrder/setDefaultResultOrder keep the default order
('verbatim').
- A typed callback receives "" and NaN (or []) after a failure, where
Node passes undefined; the limitations page says so.
Corpus 5648-5650 cover each case; the options-record diagnostics no longer
list the all and hints options as refused.
- socket.remotePort, remoteFamily, localAddress, localPort, localFamily,
bytesRead, readyState, connecting and pending. The peer's fields are
cached at first read as remoteAddress is (undefined while
connecting), and the local ones are undefined while connecting and
after close.
- write(data, cb) runs cb with null once the data is flushed, and
end(cb) / end(data, cb) once the socket finishes.
- 'close' listeners receive hadError, 'ready' fires after 'connect',
and 'data' listeners may take the string chunks setEncoding gives.
- setTimeout(ms, cb) adds cb as a once('timeout') listener (0 removes
it), and setTimeout throws ERR_OUT_OF_RANGE for a negative, NaN or
infinite msecs where it disabled the timer before. setKeepAlive
chains.
- server.maxConnections closes connections past the cap and emits
'drop' with their addresses; server.getConnections(cb) answers the
count on the next tick.
- net.isIP, isIPv4 and isIPv6 follow Node's address patterns.
- A callback adapted to a void return keeps the identity of the
function it wraps, so removing it by that function finds it.
Corpus 5644-5647 cover each case.
The fs validation ladder and the callback dispatch check their options
argument as Node's getOptions does. null, undefined and functions take
the defaults, and a string is the encoding. Numbers, booleans, bigints
and symbols throw ERR_INVALID_ARG_TYPE ('The "options" argument must be
one of type string or object'). assertEncoding lets 'buffer' through;
before, every 'buffer' encoding threw ERR_INVALID_ARG_VALUE.
With 'buffer':
- readFile and readFileSync read the file first, so a missing file
reports ENOENT. Then buffer.toString('buffer') throws
ERR_UNKNOWN_ENCODING; an empty file answers "".
- fs.mkdtemp's callback receives the path as a Buffer.
- mkdtempSync, opendirSync, createReadStream and createWriteStream
throw the fence error.
fs.mkdtemp's callback form now checks its encoding. It also returns its
path in the requested encoding: re-encoded text for names other than
utf8, as the binding does.
Corpus 5545 covers these forms against Node 24.22.
- server.close() closes idle keep-alive connections, and every open
connection closes after keepAliveTimeout + keepAliveTimeoutBuffer, so a
closed server whose clients keep their connections no longer stays up.
The Keep-Alive response header gives keepAliveTimeout in whole seconds
and is omitted when it is 0.
- A client request through an Agent with neither keepAlive nor a finite
maxSockets sends Connection: close.
- "socket hang up" and "aborted" errors carry code ECONNRESET, a client
head over the header limit fails with HPE_HEADER_OVERFLOW, and TLS
verification failures carry their openssl code.
- Outgoing header values keep the type they were set with: getHeader and
getHeaders answer numbers and arrays, appendHeader turns an existing
value into an array, and setHeader(name, string[]) writes one line per
element.
- Repeated incoming header fields join with ", " ("; " for cookie),
Node's single-value fields keep the first value, and set-cookie reads
as an array under @types/node, on server requests and client responses.
Fetch reads response headers line by line.
- Expect: 100-continue is answered with 100 Continue before 'request';
other expectations answer 417.
- HEAD requests and 1xx/204/304 responses send no body or length header,
and HTTP/1.0 requests without a set length get an unframed body and
Connection: close.
- The 16 KiB maxHeaderSize applies to the request target plus header
names and values (431 on the server).
- https.Agent TLS options are refused by name instead of being dropped,
and server.address() lowers for @types/node's
string | AddressInfo | null result.
Corpus 5555-5563 and the node-types http-headers-address fixture cover
each case.
opendirSync(path[, options]) and fs.promises.opendir(path[, options])
return a Dir that reads one Dirent at a time in OS order, as libuv's
uv_fs_readdir does. Dir supports path, readSync(), read(), closeSync(),
close() and for await. readSync and read answer null at the end. A
closed Dir throws ERR_DIR_CLOSED from readSync and closeSync, and
rejects with it from read and close.
for await over a Dir reads with read() until null and closes the Dir
in a finally, as Node's Dir.entries() does: normal exhaustion, break
and a throw all close it, and a loop whose body closes the Dir ends
with ERR_DIR_CLOSED.
A Dir also crosses 'unknown' slots as a native handle (Dir), so a
closure returning one can be passed to untyped JavaScript functions.
Through such a slot, path and closeSync() work, and other members throw
the "not supported yet" Error.
Open failures carry the opendir syscall and no path. options.bufferSize
is checked after the directory opens, with Node's ERR_OUT_OF_RANGE
messages. The options must be an object literal with a numeric
bufferSize and a utf8 encoding. Recursive walks, buffer encodings and
the callback forms are not lowered. TypeScript sources get a compile
diagnostic for them; in JavaScript sources, recursive walks and the
callback forms throw the fence error when the call runs.
Corpus 5544 covers both forms against Node 24.22.