node: update test/common (#17786)

This commit is contained in:
Meghan Denny
2025-03-07 00:32:23 -08:00
committed by GitHub
parent 85f49a7a1a
commit 2e6cbd9a4d
19 changed files with 192 additions and 291 deletions

View File

@@ -25,7 +25,7 @@ function replaceWindowsPaths(str) {
}
function replaceFullPaths(str) {
return str.replaceAll(path.resolve(__dirname, '../..'), '');
return str.replaceAll('\\\'', "'").replaceAll(path.resolve(__dirname, '../..'), '');
}
function transform(...args) {
@@ -77,7 +77,11 @@ async function spawnAndAssert(filename, transform = (x) => x, { tty = false, ...
test({ skip: 'Skipping pseudo-tty tests, as pseudo terminals are not available on Windows.' });
return;
}
const flags = common.parseTestFlags(filename);
let flags = common.parseTestFlags(filename);
if (options.flags) {
flags = [...options.flags, ...flags];
}
const executable = tty ? (process.env.PYTHON || 'python3') : process.execPath;
const args =
tty ?

View File

@@ -27,16 +27,27 @@ function runBenchmark(name, env) {
child.on('exit', (code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
// This bit makes sure that each benchmark file is being sent settings such
// that the benchmark file runs just one set of options. This helps keep the
// benchmark tests from taking a long time to run. Therefore, each benchmark
// file should result in three lines of output: a blank line, a line with
// the name of the benchmark file, and a line with the only results that we
// get from testing the benchmark file.
assert.ok(
/^(?:\n.+?\n.+?\n)+$/.test(stdout),
`benchmark file not running exactly one configuration in test: ${stdout}`,
);
// benchmark tests from taking a long time to run. Therefore, stdout should be composed as follows:
// The first and last lines should be empty.
// Each test should be separated by a blank line.
// The first line of each test should contain the test's name.
// The second line of each test should contain the configuration for the test.
// If the test configuration is not a group, there should be exactly two lines.
// Otherwise, it is possible to have more than two lines.
const splitTests = stdout.split(/\n\s*\n/);
for (let testIdx = 1; testIdx < splitTests.length - 1; testIdx++) {
const lines = splitTests[testIdx].split('\n');
assert.ok(/.+/.test(lines[0]));
if (!lines[1].includes('group="')) {
assert.strictEqual(lines.length, 2, `benchmark file not running exactly one configuration in test: ${stdout}`);
}
}
});
}

View File

@@ -60,13 +60,14 @@ function checkOutput(str, check) {
return { passed: true };
}
function expectSyncExit(child, {
function expectSyncExit(caller, spawnArgs, {
status,
signal,
stderr: stderrCheck,
stdout: stdoutCheck,
trim = false,
}) {
const child = spawnSync(...spawnArgs);
const failures = [];
let stderrStr, stdoutStr;
if (status !== undefined && child.status !== status) {
@@ -83,7 +84,18 @@ function expectSyncExit(child, {
console.error(`${tag} --- stdout ---`);
console.error(stdoutStr === undefined ? child.stdout.toString() : stdoutStr);
console.error(`${tag} status = ${child.status}, signal = ${child.signal}`);
throw new Error(`${failures.join('\n')}`);
const error = new Error(`${failures.join('\n')}`);
if (spawnArgs[2]) {
error.options = spawnArgs[2];
}
let command = spawnArgs[0];
if (Array.isArray(spawnArgs[1])) {
command += ' ' + spawnArgs[1].join(' ');
}
error.command = command;
Error.captureStackTrace(error, caller);
throw error;
}
// If status and signal are not matching expectations, fail early.
@@ -114,12 +126,11 @@ function expectSyncExit(child, {
function spawnSyncAndExit(...args) {
const spawnArgs = args.slice(0, args.length - 1);
const expectations = args[args.length - 1];
const child = spawnSync(...spawnArgs);
return expectSyncExit(child, expectations);
return expectSyncExit(spawnSyncAndExit, spawnArgs, expectations);
}
function spawnSyncAndExitWithoutError(...args) {
return expectSyncExit(spawnSync(...args), {
return expectSyncExit(spawnSyncAndExitWithoutError, [...args], {
status: 0,
signal: null,
});
@@ -127,8 +138,7 @@ function spawnSyncAndExitWithoutError(...args) {
function spawnSyncAndAssert(...args) {
const expectations = args.pop();
const child = spawnSync(...args);
return expectSyncExit(child, {
return expectSyncExit(spawnSyncAndAssert, [...args], {
status: 0,
signal: null,
...expectations,

View File

@@ -34,23 +34,6 @@ const modp2buf = Buffer.from([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
function testDH({ publicKey: alicePublicKey, privateKey: alicePrivateKey },
{ publicKey: bobPublicKey, privateKey: bobPrivateKey },
expectedValue) {
const buf1 = crypto.diffieHellman({
privateKey: alicePrivateKey,
publicKey: bobPublicKey,
});
const buf2 = crypto.diffieHellman({
privateKey: bobPrivateKey,
publicKey: alicePublicKey,
});
assert.deepStrictEqual(buf1, buf2);
if (expectedValue !== undefined)
assert.deepStrictEqual(buf1, expectedValue);
}
// Asserts that the size of the given key (in chars or bytes) is within 10% of
// the expected size.
function assertApproximateSize(key, expectedSize) {
@@ -139,7 +122,6 @@ let opensslCli = null;
module.exports = {
modp2buf,
testDH,
assertApproximateSize,
testEncryptDecrypt,
testSignVerify,

View File

@@ -15,7 +15,6 @@ const types = {
TXT: 16,
ANY: 255,
CAA: 257,
SRV: 33,
};
const classes = {
@@ -197,11 +196,11 @@ function writeDNSPacket(parsed) {
buffers.push(new Uint16Array([
parsed.id,
parsed.flags === undefined ? kStandardResponseFlags : parsed.flags,
parsed.questions && parsed.questions.length,
parsed.answers && parsed.answers.length,
parsed.authorityAnswers && parsed.authorityAnswers.length,
parsed.additionalRecords && parsed.additionalRecords.length,
parsed.flags ?? kStandardResponseFlags,
parsed.questions?.length,
parsed.answers?.length,
parsed.authorityAnswers?.length,
parsed.additionalRecords?.length,
]));
for (const q of parsed.questions) {

View File

@@ -126,7 +126,7 @@ var finalizationRegistry = new FinalizationRegistry(heldValue => {
function onGC(value, holder) {
if (holder?.ongc) {
finalizationRegistry.register(value, { ongc: holder.ongc });
}
}
@@ -137,5 +137,3 @@ module.exports = {
checkIfCollectableByCounting,
onGC,
};

View File

@@ -81,24 +81,6 @@ class SettingsFrame extends Frame {
}
}
class DataFrame extends Frame {
constructor(id, payload, padlen = 0, final = false) {
let len = payload.length;
let flags = 0;
if (final) flags |= FLAG_EOS;
const buffers = [payload];
if (padlen > 0) {
buffers.unshift(Buffer.from([padlen]));
buffers.push(PADDING.slice(0, padlen));
len += padlen + 1;
flags |= FLAG_PADDED;
}
super(len, 0, flags, id);
buffers.unshift(this[kFrameData]);
this[kFrameData] = Buffer.concat(buffers);
}
}
class HeadersFrame extends Frame {
constructor(id, payload, padlen = 0, final = false) {
let len = payload.length;
@@ -138,7 +120,6 @@ class AltSvcFrame extends Frame {
module.exports = {
Frame,
AltSvcFrame,
DataFrame,
HeadersFrame,
SettingsFrame,
PingFrame,

View File

@@ -110,7 +110,7 @@ function parseTestFlags(filename = process.argv[1]) {
// `worker_threads`) and child processes.
// If the binary was built without-ssl then the crypto flags are
// invalid (bad option). The test itself should handle this case.
if ((process.argv.length === 2 || process.argv.length === 3) &&
if (process.argv.length === 2 &&
!process.env.NODE_SKIP_FLAG_CHECK &&
isMainThread &&
hasCrypto &&
@@ -259,15 +259,13 @@ const PIPE = (() => {
// `$node --abort-on-uncaught-exception $file child`
// the process aborts.
function childShouldThrowAndAbort() {
let testCmd = '';
const escapedArgs = escapePOSIXShell`"${process.argv[0]}" --abort-on-uncaught-exception "${process.argv[1]}" child`;
if (!isWindows) {
// Do not create core files, as it can take a lot of disk space on
// continuous testing and developers' machines
testCmd += 'ulimit -c 0 && ';
escapedArgs[0] = 'ulimit -c 0 && ' + escapedArgs[0];
}
testCmd += `"${process.argv[0]}" --abort-on-uncaught-exception `;
testCmd += `"${process.argv[1]}" child`;
const child = exec(testCmd);
const child = exec(...escapedArgs);
child.on('exit', function onExit(exitCode, signal) {
const errMsg = 'Test should have aborted ' +
`but instead exited with exit code ${exitCode}` +
@@ -400,57 +398,6 @@ if (global.Storage) {
);
}
if (global.Bun) {
knownGlobals.push(
global.addEventListener,
global.alert,
global.confirm,
global.dispatchEvent,
global.postMessage,
global.prompt,
global.removeEventListener,
global.reportError,
global.Bun,
global.File,
global.process,
global.Blob,
global.Buffer,
global.BuildError,
global.BuildMessage,
global.HTMLRewriter,
global.Request,
global.ResolveError,
global.ResolveMessage,
global.Response,
global.TextDecoder,
global.AbortSignal,
global.BroadcastChannel,
global.CloseEvent,
global.DOMException,
global.ErrorEvent,
global.Event,
global.EventTarget,
global.FormData,
global.Headers,
global.MessageChannel,
global.MessageEvent,
global.MessagePort,
global.PerformanceEntry,
global.PerformanceObserver,
global.PerformanceObserverEntryList,
global.PerformanceResourceTiming,
global.PerformanceServerTiming,
global.PerformanceTiming,
global.TextEncoder,
global.URL,
global.URLSearchParams,
global.WebSocket,
global.Worker,
global.onmessage,
global.onerror
);
}
function allowGlobals(...allowlist) {
knownGlobals = knownGlobals.concat(allowlist);
}
@@ -1252,15 +1199,6 @@ const common = {
get checkoutEOL() {
return fs.readFileSync(__filename).includes('\r\n') ? '\r\n' : '\n';
},
get isInsideDirWithUnusualChars() {
return __dirname.includes('%') ||
(!isWindows && __dirname.includes('\\')) ||
__dirname.includes('$') ||
__dirname.includes('\n') ||
__dirname.includes('\r') ||
__dirname.includes('\t');
},
};
const validProperties = new Set(Object.keys(common));

View File

@@ -11,11 +11,11 @@ const {
childShouldThrowAndAbort,
createZeroFilledFile,
enoughTestMem,
escapePOSIXShell,
expectsError,
expectWarning,
getArrayBufferViews,
getBufferSources,
getCallSite,
getTTYfd,
hasCrypto,
hasIntl,
@@ -65,11 +65,11 @@ export {
createRequire,
createZeroFilledFile,
enoughTestMem,
escapePOSIXShell,
expectsError,
expectWarning,
getArrayBufferViews,
getBufferSources,
getCallSite,
getPort,
getTTYfd,
hasCrypto,

View File

@@ -255,8 +255,8 @@ class InspectorSession {
const callFrame = message.params.callFrames[0];
const location = callFrame.location;
const scriptPath = this._scriptsIdsByUrl.get(location.scriptId);
assert.strictEqual(scriptPath.toString(),
expectedScriptPath.toString(),
assert.strictEqual(decodeURIComponent(scriptPath),
decodeURIComponent(expectedScriptPath),
`${scriptPath} !== ${expectedScriptPath}`);
assert.strictEqual(location.lineNumber, line);
return true;
@@ -271,6 +271,14 @@ class InspectorSession {
`break on ${url}:${line}`);
}
waitForPauseOnStart() {
return this
.waitForNotification(
(notification) =>
notification.method === 'Debugger.paused' && notification.params.reason === 'Break on start',
'break on start');
}
pausedDetails() {
return this._pausedDetails;
}
@@ -309,6 +317,10 @@ class InspectorSession {
return notification.method === 'Runtime.executionContextDestroyed' &&
notification.params.executionContextId === 1;
});
await this.waitForDisconnect();
}
async waitForDisconnect() {
while ((await this._instance.nextStderrString()) !==
'Waiting for the debugger to disconnect...');
await this.disconnect();

View File

@@ -0,0 +1,23 @@
'use strict';
const net = require('net');
const options = { port: 0, reusePort: true };
function checkSupportReusePort() {
return new Promise((resolve, reject) => {
const server = net.createServer().listen(options);
server.on('listening', () => {
server.close(resolve);
});
server.on('error', (err) => {
console.log('The `reusePort` option is not supported:', err.message);
server.close();
reject(err);
});
});
}
module.exports = {
checkSupportReusePort,
options,
};

View File

@@ -1,32 +0,0 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const gcTrackerMap = new WeakMap();
const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER';
function onGC(obj, gcListener) {
const async_hooks = require('async_hooks');
const onGcAsyncHook = async_hooks.createHook({
init: common.mustCallAtLeast(function(id, type) {
if (this.trackedId === undefined) {
assert.strictEqual(type, gcTrackerTag);
this.trackedId = id;
}
}),
destroy(id) {
assert.notStrictEqual(this.trackedId, -1);
if (id === this.trackedId) {
this.gcListener.ongc();
onGcAsyncHook.disable();
}
},
}).enable();
onGcAsyncHook.gcListener = gcListener;
gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag));
obj = null;
}
module.exports = onGC;

View File

@@ -59,7 +59,12 @@ function _validateContent(report, fields = []) {
// Verify that all sections are present as own properties of the report.
const sections = ['header', 'nativeStack', 'javascriptStack', 'libuv',
'environmentVariables', 'sharedObjects', 'resourceUsage', 'workers'];
'sharedObjects', 'resourceUsage', 'workers'];
if (!process.report.excludeEnv) {
sections.push('environmentVariables');
}
if (!isWindows)
sections.push('userLimits');
@@ -105,7 +110,7 @@ function _validateContent(report, fields = []) {
'glibcVersionRuntime', 'glibcVersionCompiler', 'cwd',
'reportVersion', 'networkInterfaces', 'threadId'];
checkForUnknownFields(header, headerFields);
assert.strictEqual(header.reportVersion, 3); // Increment as needed.
assert.strictEqual(header.reportVersion, 5); // Increment as needed.
assert.strictEqual(typeof header.event, 'string');
assert.strictEqual(typeof header.trigger, 'string');
assert(typeof header.filename === 'string' || header.filename === null);
@@ -251,7 +256,7 @@ function _validateContent(report, fields = []) {
assert(typeof usage.free_memory, 'string');
assert(typeof usage.total_memory, 'string');
assert(typeof usage.available_memory, 'string');
// This field may not exsit
// This field may not exist
if (report.resourceUsage.constrained_memory) {
assert(typeof report.resourceUsage.constrained_memory, 'string');
}
@@ -294,19 +299,21 @@ function _validateContent(report, fields = []) {
resource.type === 'loop' ? 'undefined' : 'boolean');
});
// Verify the format of the environmentVariables section.
for (const [key, value] of Object.entries(report.environmentVariables)) {
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof value, 'string');
if (!process.report.excludeEnv) {
// Verify the format of the environmentVariables section.
for (const [key, value] of Object.entries(report.environmentVariables)) {
assert.strictEqual(typeof key, 'string');
assert.strictEqual(typeof value, 'string');
}
}
// Verify the format of the userLimits section on non-Windows platforms.
if (!isWindows) {
const userLimitsFields = ['core_file_size_blocks', 'data_seg_size_kbytes',
const userLimitsFields = ['core_file_size_blocks', 'data_seg_size_bytes',
'file_size_blocks', 'max_locked_memory_bytes',
'max_memory_size_kbytes', 'open_files',
'max_memory_size_bytes', 'open_files',
'stack_size_bytes', 'cpu_time_seconds',
'max_user_processes', 'virtual_memory_kbytes'];
'max_user_processes', 'virtual_memory_bytes'];
checkForUnknownFields(report.userLimits, userLimitsFields);
for (const [type, limits] of Object.entries(report.userLimits)) {
assert.strictEqual(typeof type, 'string');

View File

@@ -13,7 +13,7 @@ function addLibraryPath(env) {
return;
}
env = env || process.env;
env ||= process.env;
env.LD_LIBRARY_PATH =
(env.LD_LIBRARY_PATH ? env.LD_LIBRARY_PATH + path.delimiter : '') +

View File

@@ -1,5 +1,4 @@
'use strict';
require('../common');
module.exports = function tick(x, cb) {
function ontick() {

View File

@@ -46,8 +46,6 @@ function refresh(useSpawn = false) {
}
function onexit(useSpawn) {
if (process.env.KEEP_TEMP) return;
// Change directory to avoid possible EBUSY
if (isMainThread)
process.chdir(testRoot);

View File

@@ -1,101 +0,0 @@
'use strict';
const { internalBinding } = require('internal/test/binding');
const { JSUDPWrap } = internalBinding('js_udp_wrap');
const EventEmitter = require('events');
// FakeUDPWrap is a testing utility that emulates a UDP connection
// for the sake of making UDP tests more deterministic.
class FakeUDPWrap extends EventEmitter {
constructor() {
super();
this._handle = new JSUDPWrap();
this._handle.onreadstart = () => this._startReading();
this._handle.onreadstop = () => this._stopReading();
this._handle.onwrite =
(wrap, buffers, addr) => this._write(wrap, buffers, addr);
this._handle.getsockname = (obj) => {
Object.assign(obj, { address: '127.0.0.1', family: 'IPv4', port: 1337 });
return 0;
};
this.reading = false;
this.bufferedReceived = [];
this.emitBufferedImmediate = null;
}
_emitBuffered = () => {
if (!this.reading) return;
if (this.bufferedReceived.length > 0) {
this.emitReceived(this.bufferedReceived.shift());
this.emitBufferedImmediate = setImmediate(this._emitBuffered);
} else {
this.emit('wantRead');
}
};
_startReading() {
this.reading = true;
this.emitBufferedImmediate = setImmediate(this._emitBuffered);
}
_stopReading() {
this.reading = false;
clearImmediate(this.emitBufferedImmediate);
}
_write(wrap, buffers, addr) {
this.emit('send', { buffers, addr });
setImmediate(() => this._handle.onSendDone(wrap, 0));
}
afterBind() {
this._handle.onAfterBind();
}
emitReceived(info) {
if (!this.reading) {
this.bufferedReceived.push(info);
return;
}
const {
buffers,
addr: {
family = 4,
address = '127.0.0.1',
port = 1337,
},
flags = 0,
} = info;
let familyInt;
switch (family) {
case 'IPv4': familyInt = 4; break;
case 'IPv6': familyInt = 6; break;
default: throw new Error('bad family');
}
for (const buffer of buffers) {
this._handle.emitReceived(buffer, familyInt, address, port, flags);
}
}
}
function makeUDPPair() {
const serverSide = new FakeUDPWrap();
const clientSide = new FakeUDPWrap();
serverSide.on('send',
(chk) => setImmediate(() => clientSide.emitReceived(chk)));
clientSide.on('send',
(chk) => setImmediate(() => serverSide.emitReceived(chk)));
return { serverSide, clientSide };
}
module.exports = {
FakeUDPWrap,
makeUDPPair,
};

View File

@@ -458,8 +458,16 @@ class StatusLoader {
load() {
const dir = path.join(__dirname, '..', 'wpt');
const statusFile = path.join(dir, 'status', `${this.path}.json`);
const result = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
let statusFile = path.join(dir, 'status', `${this.path}.json`);
let result;
if (fs.existsSync(statusFile)) {
result = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
} else {
statusFile = path.join(dir, 'status', `${this.path}.cjs`);
result = require(statusFile);
}
this.rules.addRules(result);
const subDir = fixtures.path('wpt', this.path);
@@ -870,22 +878,16 @@ class WPTRunner {
addTestResult(spec, item) {
let result = this.results[spec.filename];
if (!result) {
result = this.results[spec.filename] = {};
}
result ||= this.results[spec.filename] = {};
if (item.status === kSkip) {
// { filename: { skip: 'reason' } }
result[kSkip] = item.reason;
} else {
// { filename: { fail: { expected: [ ... ],
// unexpected: [ ... ] } }}
if (!result[item.status]) {
result[item.status] = {};
}
result[item.status] ||= {};
const key = item.expected ? 'expected' : 'unexpected';
if (!result[item.status][key]) {
result[item.status][key] = [];
}
result[item.status][key] ||= [];
const hasName = result[item.status][key].includes(item.name);
if (!hasName) {
result[item.status][key].push(item.name);

View File

@@ -0,0 +1,70 @@
'use strict';
const { runInNewContext, runInThisContext } = require('vm');
const { setFlagsFromString } = require('v8');
const { parentPort, workerData } = require('worker_threads');
const { ResourceLoader } = require(workerData.wptRunner);
const resource = new ResourceLoader(workerData.wptPath);
if (workerData.needsGc) {
// See https://github.com/nodejs/node/issues/16595#issuecomment-340288680
setFlagsFromString('--expose-gc');
globalThis.gc = runInNewContext('gc');
}
globalThis.self = global;
globalThis.GLOBAL = {
isWindow() { return false; },
isShadowRealm() { return false; },
};
globalThis.require = require;
// This is a mock for non-fetch tests that use fetch to resolve
// a relative fixture file.
// Actual Fetch API WPTs are executed in nodejs/undici.
globalThis.fetch = function fetch(file) {
return resource.readAsFetch(workerData.testRelativePath, file);
};
if (workerData.initScript) {
runInThisContext(workerData.initScript);
}
runInThisContext(workerData.harness.code, {
filename: workerData.harness.filename,
});
// eslint-disable-next-line no-undef
add_result_callback((result) => {
parentPort.postMessage({
type: 'result',
result: {
status: result.status,
name: result.name,
message: result.message,
stack: result.stack,
},
});
});
// Keep the event loop alive
const timeout = setTimeout(() => {
parentPort.postMessage({
type: 'completion',
status: { status: 2 },
});
}, 2 ** 31 - 1); // Max timeout is 2^31-1, when overflown the timeout is set to 1.
// eslint-disable-next-line no-undef
add_completion_callback((_, status) => {
clearTimeout(timeout);
parentPort.postMessage({
type: 'completion',
status,
});
});
for (const scriptToRun of workerData.scriptsToRun) {
runInThisContext(scriptToRun.code, { filename: scriptToRun.filename });
}