diff --git a/test/js/node/test/.gitignore b/test/js/node/test/.gitignore index 171c39826b..131056112f 100644 --- a/test/js/node/test/.gitignore +++ b/test/js/node/test/.gitignore @@ -7,5 +7,6 @@ fixtures/snapshot fixtures/repl* .tmp.* *shadow-realm* +!test-shadow-realm-prepare-stack-trace.js !test-shadow-realm.js **/fails.txt diff --git a/test/js/node/test/parallel/test-child-process-dgram-reuseport.js b/test/js/node/test/parallel/test-child-process-dgram-reuseport.js new file mode 100644 index 0000000000..4eea13c525 --- /dev/null +++ b/test/js/node/test/parallel/test-child-process-dgram-reuseport.js @@ -0,0 +1,35 @@ +'use strict'; +const common = require('../common'); +const { checkSupportReusePort, options } = require('../common/udp'); +const assert = require('assert'); +const child_process = require('child_process'); +const dgram = require('dgram'); + +if (!process.env.isWorker) { + checkSupportReusePort().then(() => { + const socket = dgram.createSocket(options); + socket.bind(0, common.mustCall(() => { + const port = socket.address().port; + const workerOptions = { env: { ...process.env, isWorker: 1, port } }; + let count = 2; + for (let i = 0; i < 2; i++) { + const worker = child_process.fork(__filename, workerOptions); + worker.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + if (--count === 0) { + socket.close(); + } + })); + } + })); + }, () => { + common.skip('The `reusePort` is not supported'); + }); + return; +} + +const socket = dgram.createSocket(options); + +socket.bind(+process.env.port, common.mustCall(() => { + socket.close(); +})); diff --git a/test/js/node/test/parallel/test-cli-options-precedence.js b/test/js/node/test/parallel/test-cli-options-precedence.js new file mode 100644 index 0000000000..32804d187f --- /dev/null +++ b/test/js/node/test/parallel/test-cli-options-precedence.js @@ -0,0 +1,22 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); + +// The last option on the command line takes precedence: +assert.strictEqual(spawnSync(process.execPath, [ + '--max-http-header-size=1234', + '--max-http-header-size=5678', + '-p', 'http.maxHeaderSize', +], { + encoding: 'utf8' +}).stdout.trim(), '5678'); + +// The command line takes precedence over NODE_OPTIONS: +assert.strictEqual(spawnSync(process.execPath, [ + '--max-http-header-size=5678', + '-p', 'http.maxHeaderSize', +], { + encoding: 'utf8', + env: { ...process.env, NODE_OPTIONS: '--max-http-header-size=1234' } +}).stdout.trim(), '5678'); diff --git a/test/js/node/test/parallel/test-cluster-dgram-ipv6only.js b/test/js/node/test/parallel/test-cluster-dgram-ipv6only.js new file mode 100644 index 0000000000..a6283f94d4 --- /dev/null +++ b/test/js/node/test/parallel/test-cluster-dgram-ipv6only.js @@ -0,0 +1,51 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasIPv6) + common.skip('no IPv6 support'); +if (common.isWindows) + common.skip('dgram clustering is currently not supported on windows.'); + +const assert = require('assert'); +const cluster = require('cluster'); +const dgram = require('dgram'); + +// This test ensures that the `ipv6Only` option in `dgram.createSock()` +// works as expected. +if (cluster.isPrimary) { + cluster.fork().on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); +} else { + let waiting = 2; + function close() { + if (--waiting === 0) + cluster.worker.disconnect(); + } + + const socket1 = dgram.createSocket({ + type: 'udp6', + ipv6Only: true + }); + const socket2 = dgram.createSocket({ + type: 'udp4', + }); + socket1.on('error', common.mustNotCall()); + socket2.on('error', common.mustNotCall()); + + socket1.bind({ + port: 0, + address: '::', + }, common.mustCall(() => { + const { port } = socket1.address(); + socket2.bind({ + port, + address: '0.0.0.0', + }, common.mustCall(() => { + process.nextTick(() => { + socket1.close(close); + socket2.close(close); + }); + })); + })); +} diff --git a/test/js/node/test/parallel/test-cluster-dgram-reuse.js b/test/js/node/test/parallel/test-cluster-dgram-reuse.js new file mode 100644 index 0000000000..d2790b5d99 --- /dev/null +++ b/test/js/node/test/parallel/test-cluster-dgram-reuse.js @@ -0,0 +1,35 @@ +'use strict'; +const common = require('../common'); +if (common.isWindows) + common.skip('dgram clustering is currently not supported on windows.'); + +const assert = require('assert'); +const cluster = require('cluster'); +const dgram = require('dgram'); + +if (cluster.isPrimary) { + cluster.fork().on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); + return; +} + +let waiting = 2; +function close() { + if (--waiting === 0) + cluster.worker.disconnect(); +} + +const options = { type: 'udp4', reuseAddr: true }; +const socket1 = dgram.createSocket(options); +const socket2 = dgram.createSocket(options); + +socket1.bind(0, () => { + socket2.bind(socket1.address().port, () => { + // Work around health check issue + process.nextTick(() => { + socket1.close(close); + socket2.close(close); + }); + }); +}); diff --git a/test/js/node/test/parallel/test-cluster-dgram-reuseport.js b/test/js/node/test/parallel/test-cluster-dgram-reuseport.js new file mode 100644 index 0000000000..da7a90337c --- /dev/null +++ b/test/js/node/test/parallel/test-cluster-dgram-reuseport.js @@ -0,0 +1,39 @@ +'use strict'; +const common = require('../common'); +if (common.isWindows) + common.skip('dgram clustering is currently not supported on windows.'); + +const { checkSupportReusePort, options } = require('../common/udp'); +const assert = require('assert'); +const cluster = require('cluster'); +const dgram = require('dgram'); + +if (cluster.isPrimary) { + checkSupportReusePort().then(() => { + cluster.fork().on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); + }, () => { + common.skip('The `reusePort` option is not supported'); + }); + return; +} + +let waiting = 2; +function close() { + if (--waiting === 0) + cluster.worker.disconnect(); +} + +// Test if the worker requests the main process to create a socket +cluster._getServer = common.mustNotCall(); + +const socket1 = dgram.createSocket(options); +const socket2 = dgram.createSocket(options); + +socket1.bind(0, () => { + socket2.bind(socket1.address().port, () => { + socket1.close(close); + socket2.close(close); + }); +}); diff --git a/test/js/node/test/parallel/test-cluster-worker-isconnected.js b/test/js/node/test/parallel/test-cluster-worker-isconnected.js new file mode 100644 index 0000000000..75df689fd0 --- /dev/null +++ b/test/js/node/test/parallel/test-cluster-worker-isconnected.js @@ -0,0 +1,30 @@ +'use strict'; +const common = require('../common'); +const cluster = require('cluster'); +const assert = require('assert'); + +if (cluster.isPrimary) { + const worker = cluster.fork(); + + assert.strictEqual(worker.isConnected(), true); + + worker.on('disconnect', common.mustCall(() => { + assert.strictEqual(worker.isConnected(), false); + })); + + worker.on('message', function(msg) { + if (msg === 'readyToDisconnect') { + worker.disconnect(); + } + }); +} else { + function assertNotConnected() { + assert.strictEqual(cluster.worker.isConnected(), false); + } + + assert.strictEqual(cluster.worker.isConnected(), true); + cluster.worker.on('disconnect', common.mustCall(assertNotConnected)); + cluster.worker.process.on('disconnect', common.mustCall(assertNotConnected)); + + process.send('readyToDisconnect'); +} diff --git a/test/js/node/test/parallel/test-common-expect-warning.js b/test/js/node/test/parallel/test-common-expect-warning.js new file mode 100644 index 0000000000..dff32037fb --- /dev/null +++ b/test/js/node/test/parallel/test-common-expect-warning.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +if (process.argv[2] !== 'child') { + // Expected error not emitted. + { + const child = spawn( + process.execPath, [__filename, 'child', 0], { encoding: 'utf8' } + ); + child.on('exit', common.mustCall((status) => { + assert.notStrictEqual(status, 0); + })); + } + + // Expected error emitted. + { + const child = spawn( + process.execPath, [__filename, 'child', 1], { encoding: 'utf8' } + ); + child.on('exit', common.mustCall((status) => { + assert.strictEqual(status, 0); + })); + } + + // Expected error emitted too many times. + { + const child = spawn( + process.execPath, [__filename, 'child', 2], { encoding: 'utf8' } + ); + child.stderr.setEncoding('utf8'); + + let stderr = ''; + child.stderr.on('data', (data) => { + stderr += data; + }); + child.stderr.on('end', common.mustCall(() => { + assert.match(stderr, /Unexpected extra warning received/); + })); + child.on('exit', common.mustCall((status) => { + assert.notStrictEqual(status, 0); + })); + } +} else { + const iterations = +process.argv[3]; + common.expectWarning('fhqwhgads', 'fhqwhgads', 'fhqwhgads'); + for (let i = 0; i < iterations; i++) { + process.emitWarning('fhqwhgads', 'fhqwhgads', 'fhqwhgads'); + } +} diff --git a/test/js/node/test/parallel/test-config-json-schema.js b/test/js/node/test/parallel/test-config-json-schema.js new file mode 100644 index 0000000000..ee97b84b41 --- /dev/null +++ b/test/js/node/test/parallel/test-config-json-schema.js @@ -0,0 +1,44 @@ +// Flags: --no-warnings --expose-internals + +'use strict'; + +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const { hasOpenSSL3 } = require('../common/crypto'); + +if (!hasOpenSSL3) { + common.skip('this test requires OpenSSL 3.x'); +} + +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +if (process.config.variables.node_quic) { + common.skip('this test assumes default configuration options'); +} + +const { + generateConfigJsonSchema, +} = require('internal/options'); +const schemaInDoc = require('../../doc/node-config-schema.json'); +const assert = require('assert'); + +const schema = generateConfigJsonSchema(); + +// This assertion ensures that whenever we add a new env option, we also add it +// to the JSON schema. The function getEnvOptionsInputType() returns all the available +// env options, so we can generate the JSON schema from it and compare it to the +// current JSON schema. +// To regenerate the JSON schema, run: +// out/Release/node --expose-internals tools/doc/generate-json-schema.mjs +// And then run make doc to update the out/doc/node-config-schema.json file. +assert.strictEqual(JSON.stringify(schema), JSON.stringify(schemaInDoc), 'JSON schema is outdated.' + + 'Run `out/Release/node --expose-internals tools/doc/generate-json-schema.mjs` to update it.'); diff --git a/test/js/node/test/parallel/test-debug-process.js b/test/js/node/test/parallel/test-debug-process.js new file mode 100644 index 0000000000..0d10a15e2e --- /dev/null +++ b/test/js/node/test/parallel/test-debug-process.js @@ -0,0 +1,21 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const child = require('child_process'); + +if (!common.isWindows) { + common.skip('This test is specific to Windows to test winapi_strerror'); +} + +// Ref: https://github.com/nodejs/node/issues/23191 +// This test is specific to Windows. + +const cp = child.spawn('pwd'); + +cp.on('exit', common.mustCall(function() { + try { + process._debugProcess(cp.pid); + } catch (error) { + assert.strictEqual(error.message, 'The system cannot find the file specified.'); + } +})); diff --git a/test/js/node/test/parallel/test-diagnostics-channel-symbol-named.js b/test/js/node/test/parallel/test-diagnostics-channel-symbol-named.js new file mode 100644 index 0000000000..96fe0fa535 --- /dev/null +++ b/test/js/node/test/parallel/test-diagnostics-channel-symbol-named.js @@ -0,0 +1,28 @@ +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const input = { + foo: 'bar' +}; + +const symbol = Symbol('test'); + +// Individual channel objects can be created to avoid future lookups +const channel = dc.channel(symbol); + +// Expect two successful publishes later +channel.subscribe(common.mustCall((message, name) => { + assert.strictEqual(name, symbol); + assert.deepStrictEqual(message, input); +})); + +channel.publish(input); + +{ + assert.throws(() => { + dc.channel(null); + }, /ERR_INVALID_ARG_TYPE/); +} diff --git a/test/js/node/test/parallel/test-diagnostics-channel-tracing-channel-promise-error.js b/test/js/node/test/parallel/test-diagnostics-channel-tracing-channel-promise-error.js new file mode 100644 index 0000000000..f1f52d72f8 --- /dev/null +++ b/test/js/node/test/parallel/test-diagnostics-channel-tracing-channel-promise-error.js @@ -0,0 +1,38 @@ +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); + +const expectedError = new Error('test'); +const input = { foo: 'bar' }; +const thisArg = { baz: 'buz' }; + +function check(found) { + assert.deepStrictEqual(found, input); +} + +const handlers = { + start: common.mustCall(check), + end: common.mustCall(check), + asyncStart: common.mustCall(check), + asyncEnd: common.mustCall(check), + error: common.mustCall((found) => { + check(found); + assert.deepStrictEqual(found.error, expectedError); + }) +}; + +channel.subscribe(handlers); + +channel.tracePromise(function(value) { + assert.deepStrictEqual(this, thisArg); + return Promise.reject(value); +}, input, thisArg, expectedError).then( + common.mustNotCall(), + common.mustCall((value) => { + assert.deepStrictEqual(value, expectedError); + }) +); diff --git a/test/js/node/test/parallel/test-eslint-prefer-optional-chaining.js b/test/js/node/test/parallel/test-eslint-prefer-optional-chaining.js new file mode 100644 index 0000000000..0ce902b08a --- /dev/null +++ b/test/js/node/test/parallel/test-eslint-prefer-optional-chaining.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); +if ((!common.hasCrypto) || (!common.hasIntl)) { + common.skip('ESLint tests require crypto and Intl'); +} + +common.skipIfEslintMissing(); + +const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester; +const rule = require('../../tools/eslint-rules/prefer-optional-chaining'); + +new RuleTester().run('prefer-optional-chaining', rule, { + valid: [ + { + code: 'hello?.world', + options: [] + }, + ], + invalid: [ + { + code: 'hello && hello.world', + options: [], + errors: [{ message: 'Prefer optional chaining.' }], + output: 'hello?.world' + }, + { + code: 'hello && hello.world && hello.world.foobar', + options: [], + errors: [{ message: 'Prefer optional chaining.' }], + output: 'hello?.world?.foobar' + }, + ] +}); diff --git a/test/js/node/test/parallel/test-file-write-stream.js b/test/js/node/test/parallel/test-file-write-stream.js new file mode 100644 index 0000000000..573cd409db --- /dev/null +++ b/test/js/node/test/parallel/test-file-write-stream.js @@ -0,0 +1,83 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); +const fn = tmpdir.resolve('write.txt'); +tmpdir.refresh(); +const file = fs.createWriteStream(fn, { + highWaterMark: 10 +}); + +const EXPECTED = '012345678910'; + +const callbacks = { + open: -1, + drain: -2, + close: -1 +}; + +file + .on('open', function(fd) { + console.error('open!'); + callbacks.open++; + assert.strictEqual(typeof fd, 'number'); + }) + .on('drain', function() { + console.error('drain!', callbacks.drain); + callbacks.drain++; + if (callbacks.drain === -1) { + assert.strictEqual(fs.readFileSync(fn, 'utf8'), EXPECTED); + file.write(EXPECTED); + } else if (callbacks.drain === 0) { + assert.strictEqual(fs.readFileSync(fn, 'utf8'), EXPECTED + EXPECTED); + file.end(); + } + }) + .on('close', function() { + console.error('close!'); + assert.strictEqual(file.bytesWritten, EXPECTED.length * 2); + + callbacks.close++; + file.write('should not work anymore', common.expectsError({ + code: 'ERR_STREAM_WRITE_AFTER_END', + name: 'Error', + message: 'write after end' + })); + file.on('error', common.mustNotCall()); + + fs.unlinkSync(fn); + }); + +for (let i = 0; i < 11; i++) { + file.write(`${i}`); +} + +process.on('exit', function() { + for (const k in callbacks) { + assert.strictEqual(callbacks[k], 0, `${k} count off by ${callbacks[k]}`); + } + console.log('ok'); +}); diff --git a/test/js/node/test/parallel/test-finalization-registry-shutdown.js b/test/js/node/test/parallel/test-finalization-registry-shutdown.js new file mode 100644 index 0000000000..e288d8fecc --- /dev/null +++ b/test/js/node/test/parallel/test-finalization-registry-shutdown.js @@ -0,0 +1,23 @@ +// Flags: --expose-gc +'use strict'; +const common = require('../common'); + +// This test verifies that when a V8 FinalizationRegistryCleanupTask is queue +// at the last moment when JavaScript can be executed, the callback of a +// FinalizationRegistry will not be invoked and the process should exit +// normally. + +const reg = new FinalizationRegistry( + common.mustNotCall('This FinalizationRegistry callback should never be called')); + +function register() { + // Create a temporary object in a new function scope to allow it to be GC-ed. + reg.register({}); +} + +process.on('exit', () => { + // This is the final chance to execute JavaScript. + register(); + // Queue a FinalizationRegistryCleanupTask by a testing gc request. + globalThis.gc(); +}); diff --git a/test/js/node/test/parallel/test-fs-symlink-longpath.js b/test/js/node/test/parallel/test-fs-symlink-longpath.js new file mode 100644 index 0000000000..f3586317c2 --- /dev/null +++ b/test/js/node/test/parallel/test-fs-symlink-longpath.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); +const tmpDir = tmpdir.path; +const longPath = path.join(...[tmpDir].concat(Array(30).fill('1234567890'))); +fs.mkdirSync(longPath, { recursive: true }); + +// Test if we can have symlinks to files and folders with long filenames +const targetDirectory = path.join(longPath, 'target-directory'); +fs.mkdirSync(targetDirectory); +const pathDirectory = path.join(tmpDir, 'new-directory'); +fs.symlink(targetDirectory, pathDirectory, 'dir', common.mustSucceed(() => { + assert(fs.existsSync(pathDirectory)); +})); + +const targetFile = path.join(longPath, 'target-file'); +fs.writeFileSync(targetFile, 'data'); +const pathFile = path.join(tmpDir, 'new-file'); +fs.symlink(targetFile, pathFile, common.mustSucceed(() => { + assert(fs.existsSync(pathFile)); +})); diff --git a/test/js/node/test/parallel/test-fs-write-stream-encoding.js b/test/js/node/test/parallel/test-fs-write-stream-encoding.js new file mode 100644 index 0000000000..f06fae923c --- /dev/null +++ b/test/js/node/test/parallel/test-fs-write-stream-encoding.js @@ -0,0 +1,35 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const fs = require('fs'); +const stream = require('stream'); +const tmpdir = require('../common/tmpdir'); +const firstEncoding = 'base64'; +const secondEncoding = 'latin1'; + +const examplePath = fixtures.path('x.txt'); +const dummyPath = tmpdir.resolve('x.txt'); + +tmpdir.refresh(); + +const exampleReadStream = fs.createReadStream(examplePath, { + encoding: firstEncoding +}); + +const dummyWriteStream = fs.createWriteStream(dummyPath, { + encoding: firstEncoding +}); + +exampleReadStream.pipe(dummyWriteStream).on('finish', function() { + const assertWriteStream = new stream.Writable({ + write: function(chunk, enc, next) { + const expected = Buffer.from('xyz\n'); + assert(chunk.equals(expected)); + } + }); + assertWriteStream.setDefaultEncoding(secondEncoding); + fs.createReadStream(dummyPath, { + encoding: secondEncoding + }).pipe(assertWriteStream); +}); diff --git a/test/js/node/test/parallel/test-gc-http-client-connaborted.js b/test/js/node/test/parallel/test-gc-http-client-connaborted.js new file mode 100644 index 0000000000..e52a555d78 --- /dev/null +++ b/test/js/node/test/parallel/test-gc-http-client-connaborted.js @@ -0,0 +1,65 @@ +'use strict'; +// Flags: --expose-gc +// just like test-gc-http-client.js, +// but aborting every connection that comes in. + +const common = require('../common'); +const { onGC } = require('../common/gc'); +const http = require('http'); +const os = require('os'); + +const cpus = os.availableParallelism(); +let createClients = true; +let done = 0; +let count = 0; +let countGC = 0; + +function serverHandler(req, res) { + res.connection.destroy(); +} + +const server = http.createServer(serverHandler); +server.listen(0, common.mustCall(() => { + for (let i = 0; i < cpus; i++) + getAll(); +})); + +function getAll() { + if (!createClients) + return; + + const req = http.get({ + hostname: 'localhost', + pathname: '/', + port: server.address().port + }, cb).on('error', cb); + + count++; + onGC(req, { ongc }); + + setImmediate(getAll); +} + +function cb(res) { + done += 1; +} + +function ongc() { + countGC++; +} + +setImmediate(status); + +function status() { + if (done > 0) { + createClients = false; + globalThis.gc(); + console.log(`done/collected/total: ${done}/${countGC}/${count}`); + if (countGC === count) { + server.close(); + return; + } + } + + setImmediate(status); +} diff --git a/test/js/node/test/parallel/test-inspector-strip-types.js b/test/js/node/test/parallel/test-inspector-strip-types.js new file mode 100644 index 0000000000..6792221acf --- /dev/null +++ b/test/js/node/test/parallel/test-inspector-strip-types.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); +if (!process.config.variables.node_use_amaro) common.skip('Requires Amaro'); + +const { NodeInstance } = require('../common/inspector-helper.js'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const { pathToFileURL } = require('url'); + +const scriptPath = fixtures.path('typescript/ts/test-typescript.ts'); +const scriptURL = pathToFileURL(scriptPath); + +async function runTest() { + const child = new NodeInstance( + ['--inspect-brk=0'], + undefined, + scriptPath); + + const session = await child.connectInspectorSession(); + + await session.send({ method: 'NodeRuntime.enable' }); + await session.waitForNotification('NodeRuntime.waitingForDebugger'); + await session.send([ + { 'method': 'Debugger.enable' }, + { 'method': 'Runtime.enable' }, + { 'method': 'Runtime.runIfWaitingForDebugger' }, + ]); + await session.send({ method: 'NodeRuntime.disable' }); + + const scriptParsed = await session.waitForNotification((notification) => { + if (notification.method !== 'Debugger.scriptParsed') return false; + + return notification.params.url === scriptPath || notification.params.url === scriptURL.href; + }); + // Verify that the script has a sourceURL, hinting that it is a generated source. + assert(scriptParsed.params.hasSourceURL || common.isInsideDirWithUnusualChars); + + await session.waitForPauseOnStart(); + await session.runToCompletion(); + + assert.strictEqual((await child.expectShutdown()).exitCode, 0); +} + +runTest().then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-inspector-workers-flat-list.js b/test/js/node/test/parallel/test-inspector-workers-flat-list.js new file mode 100644 index 0000000000..a7b57fbb0a --- /dev/null +++ b/test/js/node/test/parallel/test-inspector-workers-flat-list.js @@ -0,0 +1,78 @@ +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const { Worker, isMainThread, parentPort, workerData } = + require('worker_threads'); + +if (!isMainThread || workerData !== 'launched by test') { + common.skip('This test only works on a main thread'); +} + +const { Session } = require('inspector'); + +const MAX_DEPTH = 3; + +let rootWorker = null; + +const runTest = common.mustCall(function() { + let reportedWorkersCount = 0; + const session = new Session(); + session.connect(); + session.on('NodeWorker.attachedToWorker', common.mustCall( + ({ params: { workerInfo } }) => { + console.log(`Worker ${workerInfo.title} was reported`); + if (++reportedWorkersCount === MAX_DEPTH) { + rootWorker.postMessage({ done: true }); + } + }, MAX_DEPTH)); + session.post('NodeWorker.enable', { waitForDebuggerOnStart: false }); +}); + +function processMessage({ child }) { + console.log(`Worker ${child} is running`); + if (child === MAX_DEPTH) { + runTest(); + } +} + +function workerCallback(message) { + parentPort.postMessage(message); +} + +function startWorker(depth, messageCallback) { + const worker = new Worker(__filename, { workerData: 'launched by test' }); + worker.on('message', messageCallback); + worker.postMessage({ depth }); + return worker; +} + +function runMainThread() { + rootWorker = startWorker(1, processMessage); +} + +function runChildWorkerThread() { + let worker = null; + parentPort.on('message', ({ child, depth, done }) => { + if (done) { + if (worker) { + worker.postMessage({ done: true }); + } + parentPort.close(); + } else if (depth) { + parentPort.postMessage({ child: depth }); + if (depth < MAX_DEPTH) { + worker = startWorker(depth + 1, workerCallback); + } + } else if (child) { + parentPort.postMessage({ child }); + } + }); +} + +if (isMainThread) { + runMainThread(); +} else { + runChildWorkerThread(); +} diff --git a/test/js/node/test/parallel/test-math-random.js b/test/js/node/test/parallel/test-math-random.js new file mode 100644 index 0000000000..bfa3335f7f --- /dev/null +++ b/test/js/node/test/parallel/test-math-random.js @@ -0,0 +1,17 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); + +const results = new Set(); +for (let i = 0; i < 10; i++) { + const result = spawnSync(process.execPath, ['-p', 'Math.random()']); + assert.strictEqual(result.status, 0); + results.add(result.stdout.toString()); +} +// It's theoretically possible if _very_ unlikely to see some duplicates. +// Therefore, don't expect that the size of the set is exactly 10 but do +// assume it's > 1 because if you get 10 duplicates in a row you should +// go out real quick and buy some lottery tickets, you lucky devil you! +assert(results.size > 1); diff --git a/test/js/node/test/parallel/test-module-strip-types.js b/test/js/node/test/parallel/test-module-strip-types.js new file mode 100644 index 0000000000..0f90039b56 --- /dev/null +++ b/test/js/node/test/parallel/test-module-strip-types.js @@ -0,0 +1,105 @@ +'use strict'; + +const common = require('../common'); +if (!process.config.variables.node_use_amaro) common.skip('Requires Amaro'); +const assert = require('assert'); +const vm = require('node:vm'); +const { stripTypeScriptTypes } = require('node:module'); +const { test } = require('node:test'); + +common.expectWarning( + 'ExperimentalWarning', + 'stripTypeScriptTypes is an experimental feature and might change at any time', +); + +const sourceToBeTransformed = ` + namespace MathUtil { + export const add = (a: number, b: number) => a + b; + }`; +const sourceToBeTransformedMapping = 'UACY;aACK,MAAM,CAAC,GAAW,IAAc,IAAI;AACnD,GAFU,aAAA'; + +test('stripTypeScriptTypes', () => { + const source = 'const x: number = 1;'; + const result = stripTypeScriptTypes(source); + assert.strictEqual(result, 'const x = 1;'); +}); + +test('stripTypeScriptTypes explicit', () => { + const source = 'const x: number = 1;'; + const result = stripTypeScriptTypes(source, { mode: 'strip' }); + assert.strictEqual(result, 'const x = 1;'); +}); + +test('stripTypeScriptTypes code is not a string', () => { + assert.throws(() => stripTypeScriptTypes({}), + { code: 'ERR_INVALID_ARG_TYPE' }); +}); + +test('stripTypeScriptTypes invalid mode', () => { + const source = 'const x: number = 1;'; + assert.throws(() => stripTypeScriptTypes(source, { mode: 'invalid' }), { code: 'ERR_INVALID_ARG_VALUE' }); +}); + +test('stripTypeScriptTypes sourceMap throws when mode is strip', () => { + const source = 'const x: number = 1;'; + assert.throws(() => stripTypeScriptTypes(source, + { mode: 'strip', sourceMap: true }), + { code: 'ERR_INVALID_ARG_VALUE' }); +}); + +test('stripTypeScriptTypes sourceUrl throws when mode is strip', () => { + const source = 'const x: number = 1;'; + const result = stripTypeScriptTypes(source, { mode: 'strip', sourceUrl: 'foo.ts' }); + assert.strictEqual(result, 'const x = 1;\n\n//# sourceURL=foo.ts'); +}); + +test('stripTypeScriptTypes source map when mode is transform', () => { + const result = stripTypeScriptTypes(sourceToBeTransformed, { mode: 'transform', sourceMap: true }); + const script = new vm.Script(result); + const sourceMap = + { + version: 3, + sources: [''], + names: [], + mappings: sourceToBeTransformedMapping, + }; + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); +}); + +test('stripTypeScriptTypes source map when mode is transform and sourceUrl', () => { + const result = stripTypeScriptTypes(sourceToBeTransformed, { + mode: 'transform', + sourceMap: true, + sourceUrl: 'test.ts' + }); + const script = new vm.Script(result); + const sourceMap = + { + version: 3, + sources: ['test.ts'], + names: [], + mappings: sourceToBeTransformedMapping, + }; + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); +}); + +test('stripTypeScriptTypes source map when mode is transform and sourceUrl with non-latin-1 chars', () => { + const sourceUrl = 'dir%20with $unusual"chars?\'åß∂ƒ©∆¬…`.cts'; + const result = stripTypeScriptTypes(sourceToBeTransformed, { + mode: 'transform', + sourceMap: true, + sourceUrl, + }); + const script = new vm.Script(result); + const sourceMap = + { + version: 3, + sources: [sourceUrl], + names: [], + mappings: sourceToBeTransformedMapping, + }; + const inlinedSourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + assert.strictEqual(script.sourceMapURL, `data:application/json;base64,${inlinedSourceMap}`); +}); diff --git a/test/js/node/test/parallel/test-net-child-process-connect-reset.js b/test/js/node/test/parallel/test-net-child-process-connect-reset.js new file mode 100644 index 0000000000..228ba8ed57 --- /dev/null +++ b/test/js/node/test/parallel/test-net-child-process-connect-reset.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); +const net = require('net'); + +if (process.argv[2] === 'child') { + const server = net.createServer(common.mustCall()); + server.listen(0, common.mustCall(() => { + process.send({ type: 'ready', data: { port: server.address().port } }); + })); +} else { + const cp = spawn(process.execPath, + [__filename, 'child'], + { + stdio: ['ipc', 'inherit', 'inherit'] + }); + + cp.on('exit', common.mustCall((code, signal) => { + assert.strictEqual(code, null); + assert.strictEqual(signal, 'SIGKILL'); + })); + + cp.on('message', common.mustCall((msg) => { + const { type, data } = msg; + assert.strictEqual(type, 'ready'); + const port = data.port; + + const conn = net.createConnection({ + port, + onread: { + buffer: Buffer.alloc(65536), + callback: () => {}, + } + }); + + conn.on('error', (err) => { + // Error emitted on Windows. + assert.strictEqual(err.code, 'ECONNRESET'); + }); + + conn.on('connect', common.mustCall(() => { + cp.kill('SIGKILL'); + })); + })); +} diff --git a/test/js/node/test/parallel/test-net-sync-cork.js b/test/js/node/test/parallel/test-net-sync-cork.js new file mode 100644 index 0000000000..447f42ca91 --- /dev/null +++ b/test/js/node/test/parallel/test-net-sync-cork.js @@ -0,0 +1,33 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(handle); + +const N = 100; +const buf = Buffer.alloc(2, 'a'); + +server.listen(0, function() { + const conn = net.connect(this.address().port); + + conn.on('connect', () => { + let res = true; + let i = 0; + for (; i < N && res; i++) { + conn.cork(); + conn.write(buf); + res = conn.write(buf); + conn.uncork(); + } + assert.strictEqual(i, N); + conn.end(); + }); +}); + +function handle(socket) { + socket.resume(); + socket.on('error', common.mustNotCall()) + .on('close', common.mustCall(() => server.close())); +} diff --git a/test/js/node/test/parallel/test-net-write-fully-async-buffer.js b/test/js/node/test/parallel/test-net-write-fully-async-buffer.js new file mode 100644 index 0000000000..4dfb905d23 --- /dev/null +++ b/test/js/node/test/parallel/test-net-write-fully-async-buffer.js @@ -0,0 +1,34 @@ +'use strict'; +// Flags: --expose-gc + +// Note: This is a variant of test-net-write-fully-async-hex-string.js. +// This always worked, but it seemed appropriate to add a test that checks the +// behavior for Buffers, too. +const common = require('../common'); +const net = require('net'); + +const data = Buffer.alloc(1000000); + +const server = net.createServer(common.mustCall(function(conn) { + conn.resume(); + server.close(); +})).listen(0, common.mustCall(function() { + const conn = net.createConnection(this.address().port, common.mustCall(() => { + let count = 0; + + function writeLoop() { + if (count++ === 20) { + conn.end(); + return; + } + + while (conn.write(Buffer.from(data))); + globalThis.gc({ type: 'major' }); + // The buffer allocated above should still be alive. + } + + conn.on('drain', writeLoop); + + writeLoop(); + })); +})); diff --git a/test/js/node/test/parallel/test-net-write-fully-async-hex-string.js b/test/js/node/test/parallel/test-net-write-fully-async-hex-string.js new file mode 100644 index 0000000000..c1ebe7e68b --- /dev/null +++ b/test/js/node/test/parallel/test-net-write-fully-async-hex-string.js @@ -0,0 +1,32 @@ +'use strict'; +// Flags: --expose-gc + +// Regression test for https://github.com/nodejs/node/issues/8251. +const common = require('../common'); +const net = require('net'); + +const data = Buffer.alloc(1000000).toString('hex'); + +const server = net.createServer(common.mustCall(function(conn) { + conn.resume(); + server.close(); +})).listen(0, common.mustCall(function() { + const conn = net.createConnection(this.address().port, common.mustCall(() => { + let count = 0; + + function writeLoop() { + if (count++ === 20) { + conn.end(); + return; + } + + while (conn.write(data, 'hex')); + globalThis.gc({ type: 'major' }); + // The buffer allocated inside the .write() call should still be alive. + } + + conn.on('drain', writeLoop); + + writeLoop(); + })); +})); diff --git a/test/js/node/test/parallel/test-permission-fs-supported.js b/test/js/node/test/parallel/test-permission-fs-supported.js new file mode 100644 index 0000000000..5797e191cd --- /dev/null +++ b/test/js/node/test/parallel/test-permission-fs-supported.js @@ -0,0 +1,104 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Most of the times, the function called for async and Sync +// methods are the same on node_file.cc +function syncAndAsyncAPI(funcName) { + return [funcName, funcName + 'Sync']; +} + +// This tests guarantee whenever a new API under fs module is exposed +// it must contain a test to the permission model. +// Otherwise, a vulnerability might be exposed. If you are adding a new +// fs method, please, make sure to include a test for it on test-permission-fs-* +// and include to the supportedApis list. +// +// +// This list is synced with +// fixtures/permission/fs-read and +// fixtures/permission/fs-write +const supportedApis = [ + ...syncAndAsyncAPI('appendFile'), + ...syncAndAsyncAPI('access'), + ...syncAndAsyncAPI('chown'), + ...syncAndAsyncAPI('chmod'), + ...syncAndAsyncAPI('copyFile'), + ...syncAndAsyncAPI('cp'), + 'createReadStream', + 'createWriteStream', + ...syncAndAsyncAPI('exists'), + ...syncAndAsyncAPI('lchown'), + ...syncAndAsyncAPI('lchmod'), + ...syncAndAsyncAPI('link'), + ...syncAndAsyncAPI('lutimes'), + ...syncAndAsyncAPI('mkdir'), + ...syncAndAsyncAPI('mkdtemp'), + ...syncAndAsyncAPI('open'), + 'openAsBlob', + ...syncAndAsyncAPI('mkdtemp'), + ...syncAndAsyncAPI('readdir'), + ...syncAndAsyncAPI('readFile'), + ...syncAndAsyncAPI('readlink'), + ...syncAndAsyncAPI('rename'), + ...syncAndAsyncAPI('rm'), + ...syncAndAsyncAPI('rmdir'), + ...syncAndAsyncAPI('stat'), + ...syncAndAsyncAPI('statfs'), + ...syncAndAsyncAPI('statfs'), + ...syncAndAsyncAPI('symlink'), + ...syncAndAsyncAPI('truncate'), + ...syncAndAsyncAPI('unlink'), + ...syncAndAsyncAPI('utimes'), + 'watch', + 'watchFile', + ...syncAndAsyncAPI('writeFile'), + ...syncAndAsyncAPI('opendir'), +]; + +// Non functions +const ignoreList = [ + 'constants', + 'promises', + 'X_OK', + 'W_OK', + 'R_OK', + 'F_OK', + 'Dir', + 'FileReadStream', + 'FileWriteStream', + '_toUnixTimestamp', + 'Stats', + 'ReadStream', + 'WriteStream', + 'Dirent', + // fs.watch is already blocked + 'unwatchFile', + ...syncAndAsyncAPI('lstat'), + ...syncAndAsyncAPI('realpath'), + // fd required methods + ...syncAndAsyncAPI('close'), + ...syncAndAsyncAPI('fchown'), + ...syncAndAsyncAPI('fchmod'), + ...syncAndAsyncAPI('fdatasync'), + ...syncAndAsyncAPI('fstat'), + ...syncAndAsyncAPI('fsync'), + ...syncAndAsyncAPI('ftruncate'), + ...syncAndAsyncAPI('futimes'), + ...syncAndAsyncAPI('read'), + ...syncAndAsyncAPI('readv'), + ...syncAndAsyncAPI('write'), + ...syncAndAsyncAPI('writev'), + ...syncAndAsyncAPI('glob'), +]; + +{ + const fsList = Object.keys(require('fs')); + for (const k of fsList) { + if (!supportedApis.includes(k) && !ignoreList.includes(k)) { + assert.fail(`fs.${k} was exposed but is neither on the supported list ` + + 'of the permission model nor on the ignore list.'); + } + } +} diff --git a/test/js/node/test/parallel/test-primitive-timer-leak.js b/test/js/node/test/parallel/test-primitive-timer-leak.js new file mode 100644 index 0000000000..a0fe2765e1 --- /dev/null +++ b/test/js/node/test/parallel/test-primitive-timer-leak.js @@ -0,0 +1,23 @@ +'use strict'; +// Flags: --expose-gc +require('../common'); +const { onGC } = require('../common/gc'); + +// See https://github.com/nodejs/node/issues/53335 +const poller = setInterval(() => { + globalThis.gc(); +}, 100); + +let count = 0; + +for (let i = 0; i < 10; i++) { + const timer = setTimeout(() => {}, 0); + onGC(timer, { + ongc: () => { + if (++count === 10) { + clearInterval(poller); + } + } + }); + console.log(+timer); +} diff --git a/test/js/node/test/parallel/test-process-external-stdio-close-spawn.js b/test/js/node/test/parallel/test-process-external-stdio-close-spawn.js new file mode 100644 index 0000000000..f7ee37c446 --- /dev/null +++ b/test/js/node/test/parallel/test-process-external-stdio-close-spawn.js @@ -0,0 +1,31 @@ +'use strict'; +// Refs: https://github.com/nodejs/node/issues/947 +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[2] === 'child') { + process.on('message', common.mustCall((msg) => { + assert.strictEqual(msg, 'go'); + // The following console.log is an integral part + // of the test. If this regress, this call will + // cause the process to exit with 1 + console.log('logging should not cause a crash'); + process.disconnect(); + })); +} else { + // Passing '--inspect', '--inspect-brk' to child.spawn enables + // the debugger. This test was added to help debug the fork-based + // test with the same name. + const child = cp.spawn(process.execPath, [__filename, 'child'], { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'] + }); + + child.on('close', common.mustCall((exitCode, signal) => { + assert.strictEqual(exitCode, 0); + assert.strictEqual(signal, null); + })); + + child.stdout.destroy(); + child.send('go'); +} diff --git a/test/js/node/test/parallel/test-process-external-stdio-close.js b/test/js/node/test/parallel/test-process-external-stdio-close.js new file mode 100644 index 0000000000..9457161cf5 --- /dev/null +++ b/test/js/node/test/parallel/test-process-external-stdio-close.js @@ -0,0 +1,26 @@ +'use strict'; +// Refs: https://github.com/nodejs/node/issues/947 +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[2] === 'child') { + process.on('message', common.mustCall((msg) => { + assert.strictEqual(msg, 'go'); + // The following console.log is an integral part + // of the test. If this regress, this call will + // cause the process to exit with 1 + console.log('logging should not cause a crash'); + process.disconnect(); + })); +} else { + const child = cp.fork(__filename, ['child'], { silent: true }); + + child.on('close', common.mustCall((exitCode, signal) => { + assert.strictEqual(exitCode, 0); + assert.strictEqual(signal, null); + })); + + child.stdout.destroy(); + child.send('go'); +} diff --git a/test/js/node/test/parallel/test-readline-async-iterators-backpressure.js b/test/js/node/test/parallel/test-readline-async-iterators-backpressure.js new file mode 100644 index 0000000000..e8f443f438 --- /dev/null +++ b/test/js/node/test/parallel/test-readline-async-iterators-backpressure.js @@ -0,0 +1,58 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); +const readline = require('readline'); + +const CONTENT = 'content'; +const LINES_PER_PUSH = 2051; +const REPETITIONS = 3; + +(async () => { + const readable = new Readable({ read() {} }); + let salt = 0; + for (let i = 0; i < REPETITIONS; i++) { + readable.push(`${CONTENT}\n`.repeat(LINES_PER_PUSH + i)); + salt += i; + } + const TOTAL_LINES = LINES_PER_PUSH * REPETITIONS + salt; + + const rli = readline.createInterface({ + input: readable, + crlfDelay: Infinity + }); + + const it = rli[Symbol.asyncIterator](); + const watermarkData = it[Symbol.for('nodejs.watermarkData')]; + const highWaterMark = watermarkData.high; + + // For this test to work, we have to queue up more than the number of + // highWaterMark items in rli. Make sure that is the case. + assert(TOTAL_LINES > highWaterMark, `TOTAL_LINES (${TOTAL_LINES}) isn't greater than highWaterMark (${highWaterMark})`); + + let iterations = 0; + let readableEnded = false; + let notPaused = 0; + for await (const line of it) { + assert.strictEqual(readableEnded, false); + assert.strictEqual(line, CONTENT); + assert.ok(watermarkData.size <= TOTAL_LINES); + assert.strictEqual(readable.isPaused(), watermarkData.size >= 1); + if (!readable.isPaused()) { + notPaused++; + } + + iterations += 1; + + // We have to end the input stream asynchronously for back pressure to work. + // Only end when we have reached the final line. + if (iterations === TOTAL_LINES) { + readable.push(null); + readableEnded = true; + } + } + + assert.strictEqual(iterations, TOTAL_LINES); + assert.strictEqual(notPaused, REPETITIONS); +})().then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-shadow-realm-prepare-stack-trace.js b/test/js/node/test/parallel/test-shadow-realm-prepare-stack-trace.js new file mode 100644 index 0000000000..2460f017ee --- /dev/null +++ b/test/js/node/test/parallel/test-shadow-realm-prepare-stack-trace.js @@ -0,0 +1,53 @@ +// Flags: --experimental-shadow-realm +'use strict'; + +require('../common'); +const assert = require('assert'); + +let principalRealmPrepareStackTraceCalled = false; +Error.prepareStackTrace = (error, trace) => { + principalRealmPrepareStackTraceCalled = true; + return `${String(error)}\n at ${trace.join('\n at ')}`; +}; + +{ + // Validates inner Error.prepareStackTrace can not leak into the outer realm. + const shadowRealm = new ShadowRealm(); + + const stack = shadowRealm.evaluate(` +Error.prepareStackTrace = (error, trace) => { + globalThis.leaked = 'inner'; + return 'from shadow realm'; +}; + +try { + throw new Error('boom'); +} catch (e) { + e.stack; +} +`); + assert.ok(!principalRealmPrepareStackTraceCalled); + assert.strictEqual(stack, 'from shadow realm'); + assert.strictEqual('leaked' in globalThis, false); +} + +{ + // Validates stacks can be generated in the ShadowRealm. + const shadowRealm = new ShadowRealm(); + + const stack = shadowRealm.evaluate(` +function myFunc() { + throw new Error('boom'); +} + +try { + myFunc(); +} catch (e) { + e.stack; +} +`); + assert.ok(!principalRealmPrepareStackTraceCalled); + const lines = stack.split('\n'); + assert.strictEqual(lines[0], 'Error: boom'); + assert.match(lines[1], /^ {4}at myFunc \(.*\)/); +} diff --git a/test/js/node/test/parallel/test-socket-options-invalid.js b/test/js/node/test/parallel/test-socket-options-invalid.js new file mode 100644 index 0000000000..32bf0810c6 --- /dev/null +++ b/test/js/node/test/parallel/test-socket-options-invalid.js @@ -0,0 +1,27 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +{ + const invalidKeys = [ + 'objectMode', + 'readableObjectMode', + 'writableObjectMode', + ]; + invalidKeys.forEach((invalidKey) => { + const option = { + [invalidKey]: true + }; + const message = `The property 'options.${invalidKey}' is not supported. Received true`; + + assert.throws(() => { + const socket = new net.Socket(option); + socket.connect({ port: 8080 }); + }, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: new RegExp(message) + }); + }); +} diff --git a/test/js/node/test/parallel/test-socket-write-after-fin-error.js b/test/js/node/test/parallel/test-socket-write-after-fin-error.js new file mode 100644 index 0000000000..0e3b99abac --- /dev/null +++ b/test/js/node/test/parallel/test-socket-write-after-fin-error.js @@ -0,0 +1,62 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// This is similar to simple/test-socket-write-after-fin, except that +// we don't set allowHalfOpen. Then we write after the client has sent +// a FIN, and this is an error. However, the standard "write after end" +// message is too vague, and doesn't actually tell you what happens. + +const net = require('net'); +let serverData = ''; +let gotServerEnd = false; +let clientData = ''; +let gotClientEnd = false; +let gotServerError = false; + +const server = net.createServer(function(sock) { + sock.setEncoding('utf8'); + sock.on('error', function() {}); + + sock.on('data', function(c) { + serverData += c; + }); + sock.on('end', function() { + gotServerEnd = true; + setImmediate(() => { + sock.write(serverData, function(er) { + console.error(`${er.code}: ${er.message}`); + gotServerError = er; + }); + sock.end(); + }); + }); + server.close(); +}); +server.listen(0, function() { + const sock = net.connect(this.address().port); + sock.setEncoding('utf8'); + sock.on('data', function(c) { + clientData += c; + }); + + sock.on('end', function() { + gotClientEnd = true; + }); + + process.on('exit', function() { + assert.strictEqual(clientData, ''); + assert.strictEqual(serverData, 'hello1hello2hello3\nTHUNDERMUSCLE!'); + assert(gotClientEnd); + assert(gotServerEnd); + assert(gotServerError); + assert.strictEqual(gotServerError.code, 'EPIPE'); + assert.notStrictEqual(gotServerError.message, 'write after end'); + console.log('ok'); + }); + + sock.write('hello1'); + sock.write('hello2'); + sock.write('hello3\n'); + sock.end('THUNDERMUSCLE!'); +}); diff --git a/test/js/node/test/parallel/test-stream-finished.js b/test/js/node/test/parallel/test-stream-finished.js new file mode 100644 index 0000000000..9d66cbe59b --- /dev/null +++ b/test/js/node/test/parallel/test-stream-finished.js @@ -0,0 +1,702 @@ +'use strict'; + +const common = require('../common'); +const { + Writable, + Readable, + Transform, + finished, + Duplex, + PassThrough, + Stream, +} = require('stream'); +const assert = require('assert'); +const EE = require('events'); +const fs = require('fs'); +const { promisify } = require('util'); +const http = require('http'); + +{ + const rs = new Readable({ + read() {} + }); + + finished(rs, common.mustSucceed()); + + rs.push(null); + rs.resume(); +} + +{ + const ws = new Writable({ + write(data, enc, cb) { + cb(); + } + }); + + finished(ws, common.mustSucceed()); + + ws.end(); +} + +{ + const tr = new Transform({ + transform(data, enc, cb) { + cb(); + } + }); + + let finish = false; + let ended = false; + + tr.on('end', () => { + ended = true; + }); + + tr.on('finish', () => { + finish = true; + }); + + finished(tr, common.mustSucceed(() => { + assert(finish); + assert(ended); + })); + + tr.end(); + tr.resume(); +} + +{ + const rs = fs.createReadStream(__filename); + + rs.resume(); + finished(rs, common.mustCall()); +} + +{ + const finishedPromise = promisify(finished); + + async function run() { + const rs = fs.createReadStream(__filename); + const done = common.mustCall(); + + let ended = false; + rs.resume(); + rs.on('end', () => { + ended = true; + }); + await finishedPromise(rs); + assert(ended); + done(); + } + + run(); +} + +{ + // Check pre-cancelled + const signal = new EventTarget(); + signal.aborted = true; + + const rs = Readable.from((function* () {})()); + finished(rs, { signal }, common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); +} + +{ + // Check cancelled before the stream ends sync. + const ac = new AbortController(); + const { signal } = ac; + + const rs = Readable.from((function* () {})()); + finished(rs, { signal }, common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); + + ac.abort(); +} + +{ + // Check cancelled before the stream ends async. + const ac = new AbortController(); + const { signal } = ac; + + const rs = Readable.from((function* () {})()); + setTimeout(() => ac.abort(), 1); + finished(rs, { signal }, common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); +} + +{ + // Check cancelled after doesn't throw. + const ac = new AbortController(); + const { signal } = ac; + + const rs = Readable.from((function* () { + yield 5; + setImmediate(() => ac.abort()); + })()); + rs.resume(); + finished(rs, { signal }, common.mustSucceed()); +} + +{ + // Promisified abort works + const finishedPromise = promisify(finished); + async function run() { + const ac = new AbortController(); + const { signal } = ac; + const rs = Readable.from((function* () {})()); + setImmediate(() => ac.abort()); + await finishedPromise(rs, { signal }); + } + + assert.rejects(run, { name: 'AbortError' }).then(common.mustCall()); +} + +{ + // Promisified pre-aborted works + const finishedPromise = promisify(finished); + async function run() { + const signal = new EventTarget(); + signal.aborted = true; + const rs = Readable.from((function* () {})()); + await finishedPromise(rs, { signal }); + } + + assert.rejects(run, { name: 'AbortError' }).then(common.mustCall()); +} + + +{ + const rs = fs.createReadStream('file-does-not-exist'); + + finished(rs, common.expectsError({ + code: 'ENOENT' + })); +} + +{ + const rs = new Readable(); + + finished(rs, common.mustSucceed()); + + rs.push(null); + rs.emit('close'); // Should not trigger an error + rs.resume(); +} + +{ + const rs = new Readable(); + + finished(rs, common.mustCall((err) => { + assert(err, 'premature close error'); + })); + + rs.emit('close'); // Should trigger error + rs.push(null); + rs.resume(); +} + +// Test faulty input values and options. +{ + const rs = new Readable({ + read() {} + }); + + assert.throws( + () => finished(rs, 'foo'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /callback/ + } + ); + assert.throws( + () => finished(rs, 'foo', () => {}), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /options/ + } + ); + assert.throws( + () => finished(rs, {}, 'foo'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /callback/ + } + ); + + finished(rs, null, common.mustCall()); + + rs.push(null); + rs.resume(); +} + +// Test that calling returned function removes listeners +{ + const ws = new Writable({ + write(data, env, cb) { + cb(); + } + }); + const removeListener = finished(ws, common.mustNotCall()); + removeListener(); + ws.end(); +} + +{ + const rs = new Readable(); + const removeListeners = finished(rs, common.mustNotCall()); + removeListeners(); + + rs.emit('close'); + rs.push(null); + rs.resume(); +} + +{ + const streamLike = new EE(); + streamLike.readableEnded = true; + streamLike.readable = true; + assert.throws( + () => { + finished(streamLike, () => {}); + }, + { code: 'ERR_INVALID_ARG_TYPE' } + ); + streamLike.emit('close'); +} + +{ + const writable = new Writable({ write() {} }); + writable.writable = false; + writable.destroy(); + finished(writable, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); +} + +{ + const readable = new Readable(); + readable.readable = false; + readable.destroy(); + finished(readable, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); +} + +{ + const w = new Writable({ + write(chunk, encoding, callback) { + setImmediate(callback); + } + }); + finished(w, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); + w.end('asd'); + w.destroy(); +} + +function testClosed(factory) { + { + // If already destroyed but finished is cancelled in same tick + // don't invoke the callback, + + const s = factory(); + s.destroy(); + const dispose = finished(s, common.mustNotCall()); + dispose(); + } + + { + // If already destroyed invoked callback. + + const s = factory(); + s.destroy(); + finished(s, common.mustCall()); + } + + { + // Don't invoke until destroy has completed. + + let destroyed = false; + const s = factory({ + destroy(err, cb) { + setImmediate(() => { + destroyed = true; + cb(); + }); + } + }); + s.destroy(); + finished(s, common.mustCall(() => { + assert.strictEqual(destroyed, true); + })); + } + + { + // Invoke callback even if close is inhibited. + + const s = factory({ + emitClose: false, + destroy(err, cb) { + cb(); + finished(s, common.mustCall()); + } + }); + s.destroy(); + } + + { + // Invoke with deep async. + + const s = factory({ + destroy(err, cb) { + setImmediate(() => { + cb(); + setImmediate(() => { + finished(s, common.mustCall()); + }); + }); + } + }); + s.destroy(); + } +} + +testClosed((opts) => new Readable({ ...opts })); +testClosed((opts) => new Writable({ write() {}, ...opts })); + +{ + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + }, + autoDestroy: false + }); + w.end('asd'); + process.nextTick(() => { + finished(w, common.mustCall()); + }); +} + +{ + const w = new Writable({ + write(chunk, encoding, cb) { + cb(new Error()); + }, + autoDestroy: false + }); + w.write('asd'); + w.on('error', common.mustCall(() => { + finished(w, common.mustCall()); + })); +} + +{ + const r = new Readable({ + autoDestroy: false + }); + r.push(null); + r.resume(); + r.on('end', common.mustCall(() => { + finished(r, common.mustCall()); + })); +} + +{ + const rs = fs.createReadStream(__filename, { autoClose: false }); + rs.resume(); + rs.on('close', common.mustNotCall()); + rs.on('end', common.mustCall(() => { + finished(rs, common.mustCall()); + })); +} + +{ + const d = new EE(); + d._writableState = {}; + d._writableState.finished = true; + finished(d, { readable: false, writable: true }, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); + d._writableState.errored = true; + d.emit('close'); +} + +{ + const r = new Readable(); + finished(r, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); + r.push('asd'); + r.push(null); + r.destroy(); +} + +{ + const d = new Duplex({ + final(cb) { }, // Never close writable side for test purpose + read() { + this.push(null); + } + }); + + d.on('end', common.mustCall()); + + finished(d, { readable: true, writable: false }, common.mustCall()); + + d.end(); + d.resume(); +} + +{ + const d = new Duplex({ + final(cb) { }, // Never close writable side for test purpose + read() { + this.push(null); + } + }); + + d.on('end', common.mustCall()); + + d.end(); + finished(d, { readable: true, writable: false }, common.mustCall()); + + d.resume(); +} + +{ + // Test for compat for e.g. fd-slicer which implements + // non standard destroy behavior which might not emit + // 'close'. + const r = new Readable(); + finished(r, common.mustCall()); + r.resume(); + r.push('asd'); + r.destroyed = true; + r.push(null); +} + +{ + // Regression https://github.com/nodejs/node/issues/33130 + const response = new PassThrough(); + + class HelloWorld extends Duplex { + constructor(response) { + super({ + autoDestroy: false + }); + + this.response = response; + this.readMore = false; + + response.once('end', () => { + this.push(null); + }); + + response.on('readable', () => { + if (this.readMore) { + this._read(); + } + }); + } + + _read() { + const { response } = this; + + this.readMore = true; + + if (response.readableLength) { + this.readMore = false; + } + + let data; + while ((data = response.read()) !== null) { + this.push(data); + } + } + } + + const instance = new HelloWorld(response); + instance.setEncoding('utf8'); + instance.end(); + + (async () => { + await EE.once(instance, 'finish'); + + setImmediate(() => { + response.write('chunk 1'); + response.write('chunk 2'); + response.write('chunk 3'); + response.end(); + }); + + let res = ''; + for await (const data of instance) { + res += data; + } + + assert.strictEqual(res, 'chunk 1chunk 2chunk 3'); + })().then(common.mustCall()); +} + +{ + const p = new PassThrough(); + p.end(); + finished(p, common.mustNotCall()); +} + +{ + const p = new PassThrough(); + p.end(); + p.on('finish', common.mustCall(() => { + finished(p, common.mustNotCall()); + })); +} + +{ + const server = http.createServer(common.mustCall((req, res) => { + res.on('close', common.mustCall(() => { + finished(res, common.mustCall(() => { + server.close(); + })); + })); + res.end(); + })) + .listen(0, function() { + http.request({ + method: 'GET', + port: this.address().port + }).end() + .on('response', common.mustCall()); + }); +} + +{ + const server = http.createServer(common.mustCall((req, res) => { + req.on('close', common.mustCall(() => { + finished(req, common.mustCall(() => { + server.close(); + })); + })); + req.destroy(); + })).listen(0, function() { + http.request({ + method: 'GET', + port: this.address().port + }).end().on('error', common.mustCall()); + }); +} + +{ + const w = new Writable({ + write(chunk, encoding, callback) { + process.nextTick(callback); + } + }); + w.aborted = false; + w.end(); + let closed = false; + w.on('finish', () => { + assert.strictEqual(closed, false); + w.emit('aborted'); + }); + w.on('close', common.mustCall(() => { + closed = true; + })); + + finished(w, common.mustCall(() => { + assert.strictEqual(closed, true); + })); +} + +{ + const w = new Writable(); + const _err = new Error(); + w.destroy(_err); + assert.strictEqual(w.errored, _err); + finished(w, common.mustCall((err) => { + assert.strictEqual(_err, err); + assert.strictEqual(w.closed, true); + finished(w, common.mustCall((err) => { + assert.strictEqual(_err, err); + })); + })); +} + +{ + const w = new Writable(); + w.destroy(); + assert.strictEqual(w.errored, null); + finished(w, common.mustCall((err) => { + assert.strictEqual(w.closed, true); + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + finished(w, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); + })); + })); +} + +{ + // Legacy Streams do not inherit from Readable or Writable. + // We cannot really assume anything about them, so we cannot close them + // automatically. + const s = new Stream(); + finished(s, common.mustNotCall()); +} + +{ + const server = http.createServer(common.mustCall(function(req, res) { + fs.createReadStream(__filename).pipe(res); + finished(res, common.mustCall(function(err) { + assert.strictEqual(err, undefined); + })); + })).listen(0, function() { + http.request( + { method: 'GET', port: this.address().port }, + common.mustCall(function(res) { + res.resume(); + finished(res, common.mustCall(() => { + server.close(); + })); + }) + ).end(); + }); +} + +{ + let isCalled = false; + const stream = new Duplex({ + write(chunk, enc, cb) { + setImmediate(() => { + isCalled = true; + cb(); + }); + } + }); + + stream.end('foo'); + + finished(stream, { readable: false }, common.mustCall((err) => { + assert(!err); + assert.strictEqual(isCalled, true); + assert.strictEqual(stream._writableState.pendingcb, 0); + })); +} + +{ + const stream = new Duplex({ + write(chunk, enc, cb) {} + }); + + stream.end('foo'); + + // Simulate an old stream implementation that doesn't have pendingcb + delete stream._writableState.pendingcb; + + finished(stream, { readable: false }, common.mustCall()); +} diff --git a/test/js/node/test/parallel/test-timers-max-duration-warning.js b/test/js/node/test/parallel/test-timers-max-duration-warning.js new file mode 100644 index 0000000000..cc9595ff9c --- /dev/null +++ b/test/js/node/test/parallel/test-timers-max-duration-warning.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const OVERFLOW = Math.pow(2, 31); // TIMEOUT_MAX is 2^31-1 + +function timerNotCanceled() { + assert.fail('Timer should be canceled'); +} + +process.on('warning', common.mustCall((warning) => { + if (warning.name === 'DeprecationWarning') return; + + const lines = warning.message.split('\n'); + + assert.strictEqual(warning.name, 'TimeoutOverflowWarning'); + assert.strictEqual(lines[0], `${OVERFLOW} does not fit into a 32-bit signed` + + ' integer.'); + assert.strictEqual(lines.length, 2); +}, 2)); + + +{ + const timeout = setTimeout(timerNotCanceled, OVERFLOW); + clearTimeout(timeout); +} + +{ + const interval = setInterval(timerNotCanceled, OVERFLOW); + clearInterval(interval); +} diff --git a/test/js/node/test/parallel/test-tls-legacy-pfx.js b/test/js/node/test/parallel/test-tls-legacy-pfx.js new file mode 100644 index 0000000000..5106217718 --- /dev/null +++ b/test/js/node/test/parallel/test-tls-legacy-pfx.js @@ -0,0 +1,32 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const { hasOpenSSL3 } = require('../common/crypto'); + +if (!hasOpenSSL3) { + common.skip('OpenSSL legacy failures are only testable with OpenSSL 3+'); +} + +const fixtures = require('../common/fixtures'); + +const { + assert, connect, keys +} = require(fixtures.path('tls-connect')); + +const legacyPfx = fixtures.readKey('legacy.pfx'); + +connect({ + client: { + pfx: legacyPfx, + passphrase: 'legacy', + rejectUnauthorized: false + }, + server: keys.agent1 +}, common.mustCall((e, pair, cleanup) => { + assert.strictEqual(e.code, 'ERR_CRYPTO_UNSUPPORTED_OPERATION'); + assert.strictEqual(e.message, 'Unsupported PKCS12 PFX data'); + cleanup(); +})); diff --git a/test/js/node/test/parallel/test-tty-stdin-pipe.js b/test/js/node/test/parallel/test-tty-stdin-pipe.js new file mode 100644 index 0000000000..9e94153206 --- /dev/null +++ b/test/js/node/test/parallel/test-tty-stdin-pipe.js @@ -0,0 +1,48 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +// This test ensures piping from `stdin` isn't broken. +// https://github.com/nodejs/node/issues/5927 + +const assert = require('assert'); +const readline = require('readline'); + +const rl = readline.createInterface(process.stdin, process.stdout); +rl.resume(); + +let hasPaused = false; + +const origPause = rl.pause; +rl.pause = function() { + hasPaused = true; + origPause.apply(this, arguments); +}; + +const origSetRawMode = rl._setRawMode; +rl._setRawMode = function(mode) { + assert.ok(hasPaused); + origSetRawMode.apply(this, arguments); +}; + +rl.close(); diff --git a/test/js/node/test/parallel/test-v8-flag-pool-size-0.js b/test/js/node/test/parallel/test-v8-flag-pool-size-0.js new file mode 100644 index 0000000000..5f78e6006e --- /dev/null +++ b/test/js/node/test/parallel/test-v8-flag-pool-size-0.js @@ -0,0 +1,10 @@ +// Flags: --v8-pool-size=0 --expose-gc + +'use strict'; + +require('../common'); + +// This verifies that V8 tasks scheduled by GC are handled on worker threads if +// `--v8-pool-size=0` is given. The worker threads are managed by Node.js' +// `v8::Platform` implementation. +globalThis.gc(); diff --git a/test/js/node/test/parallel/test-v8-getheapsnapshot-twice.js b/test/js/node/test/parallel/test-v8-getheapsnapshot-twice.js new file mode 100644 index 0000000000..feffd97aa0 --- /dev/null +++ b/test/js/node/test/parallel/test-v8-getheapsnapshot-twice.js @@ -0,0 +1,9 @@ +'use strict'; +require('../common'); +const v8 = require('v8'); + +// Regression test for https://github.com/nodejs/node/issues/35559 +// It is important that the return value of the first call is not used, i.e. +// that the first snapshot is GC-able while the second one is being created. +v8.getHeapSnapshot(); +v8.getHeapSnapshot(); diff --git a/test/js/node/test/parallel/test-vm-new-script-new-context.js b/test/js/node/test/parallel/test-vm-new-script-new-context.js new file mode 100644 index 0000000000..b4221d81d9 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-new-script-new-context.js @@ -0,0 +1,107 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +const assert = require('assert'); + +const Script = require('vm').Script; + +{ + const script = new Script('\'passed\';'); + const result1 = script.runInNewContext(); + const result2 = script.runInNewContext(); + assert.strictEqual(result1, 'passed'); + assert.strictEqual(result2, 'passed'); +} + +{ + const script = new Script('throw new Error(\'test\');'); + assert.throws(() => { + script.runInNewContext(); + }, /^Error: test$/); +} + +{ + const script = new Script('foo.bar = 5;'); + assert.throws(() => { + script.runInNewContext(); + }, /^ReferenceError: foo is not defined$/); +} + +{ + globalThis.hello = 5; + const script = new Script('hello = 2'); + script.runInNewContext(); + assert.strictEqual(globalThis.hello, 5); + + // Cleanup + delete globalThis.hello; +} + +{ + globalThis.code = 'foo = 1;' + + 'bar = 2;' + + 'if (baz !== 3) throw new Error(\'test fail\');'; + globalThis.foo = 2; + globalThis.obj = { foo: 0, baz: 3 }; + const script = new Script(globalThis.code); + /* eslint-disable no-unused-vars */ + const baz = script.runInNewContext(globalThis.obj); + /* eslint-enable no-unused-vars */ + assert.strictEqual(globalThis.obj.foo, 1); + assert.strictEqual(globalThis.obj.bar, 2); + assert.strictEqual(globalThis.foo, 2); + + // cleanup + delete globalThis.code; + delete globalThis.foo; + delete globalThis.obj; +} + +{ + const script = new Script('f()'); + function changeFoo() { globalThis.foo = 100; } + script.runInNewContext({ f: changeFoo }); + assert.strictEqual(globalThis.foo, 100); + + // cleanup + delete globalThis.foo; +} + +{ + const script = new Script('f.a = 2'); + const f = { a: 1 }; + script.runInNewContext({ f }); + assert.strictEqual(f.a, 2); + + assert.throws(() => { + script.runInNewContext(); + }, /^ReferenceError: f is not defined$/); +} + +{ + const script = new Script(''); + assert.throws(() => { + script.runInNewContext.call('\'hello\';'); + }, /^TypeError: this\.runInContext is not a function$/); +} diff --git a/test/js/node/test/parallel/test-vm-util-lazy-properties.js b/test/js/node/test/parallel/test-vm-util-lazy-properties.js new file mode 100644 index 0000000000..8b0ca1b177 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-util-lazy-properties.js @@ -0,0 +1,26 @@ +'use strict'; +require('../common'); + +const vm = require('node:vm'); +const util = require('node:util'); +const assert = require('node:assert'); + +// This verifies that invoking property getters defined with +// `require('internal/util').defineLazyProperties` does not crash +// the process. + +const ctx = vm.createContext(); +const getter = vm.runInContext(` + function getter(object, property) { + return object[property]; + } + getter; +`, ctx); + +// `util.parseArgs` is a lazy property. +const parseArgs = getter(util, 'parseArgs'); +assert.strictEqual(parseArgs, util.parseArgs); + +// `globalThis.TextEncoder` is a lazy property. +const TextEncoder = getter(globalThis, 'TextEncoder'); +assert.strictEqual(TextEncoder, globalThis.TextEncoder); diff --git a/test/js/node/test/parallel/test-worker-dns-terminate-during-query.js b/test/js/node/test/parallel/test-worker-dns-terminate-during-query.js new file mode 100644 index 0000000000..77de9f484f --- /dev/null +++ b/test/js/node/test/parallel/test-worker-dns-terminate-during-query.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +const { Resolver } = require('dns'); +const dgram = require('dgram'); +const { Worker, isMainThread } = require('worker_threads'); + +// Test that Workers can terminate while DNS queries are outstanding. + +if (isMainThread) { + return new Worker(__filename); +} + +const socket = dgram.createSocket('udp4'); + +socket.bind(0, common.mustCall(() => { + const resolver = new Resolver(); + resolver.setServers([`127.0.0.1:${socket.address().port}`]); + resolver.resolve4('example.org', common.mustNotCall()); +})); + +socket.on('message', common.mustCall(() => { + process.exit(); +})); diff --git a/test/js/node/test/parallel/test-worker-exit-heapsnapshot.js b/test/js/node/test/parallel/test-worker-exit-heapsnapshot.js new file mode 100644 index 0000000000..a7b7b26eca --- /dev/null +++ b/test/js/node/test/parallel/test-worker-exit-heapsnapshot.js @@ -0,0 +1,17 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { getHeapSnapshot } = require('v8'); +const { isMainThread, Worker } = require('worker_threads'); + +// Checks taking heap snapshot at the exit event listener of Worker doesn't +// crash the process. +// Regression for https://github.com/nodejs/node/issues/43122. +if (isMainThread) { + const worker = new Worker(__filename); + + worker.once('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + getHeapSnapshot().pipe(process.stdout); + })); +} diff --git a/test/js/node/test/parallel/test-worker-fs-stat-watcher.js b/test/js/node/test/parallel/test-worker-fs-stat-watcher.js new file mode 100644 index 0000000000..c648792af7 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-fs-stat-watcher.js @@ -0,0 +1,17 @@ +'use strict'; +const common = require('../common'); +const { Worker, parentPort } = require('worker_threads'); +const fs = require('fs'); + +// Checks that terminating Workers does not crash the process if fs.watchFile() +// has active handles. + +// Do not use isMainThread so that this test itself can be run inside a Worker. +if (!process.env.HAS_STARTED_WORKER) { + process.env.HAS_STARTED_WORKER = 1; + const worker = new Worker(__filename); + worker.on('message', common.mustCall(() => worker.terminate())); +} else { + fs.watchFile(__filename, () => {}); + parentPort.postMessage('running'); +} diff --git a/test/js/node/test/parallel/test-worker-message-port-wasm-threads.js b/test/js/node/test/parallel/test-worker-message-port-wasm-threads.js new file mode 100644 index 0000000000..fe70261fd7 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-message-port-wasm-threads.js @@ -0,0 +1,53 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { MessageChannel, Worker } = require('worker_threads'); + +// Test that SharedArrayBuffer instances created from WASM are transferable +// through MessageChannels (without crashing). + +const fixtures = require('../common/fixtures'); +const wasmSource = fixtures.readSync('shared-memory.wasm'); +const wasmModule = new WebAssembly.Module(wasmSource); +const instance = new WebAssembly.Instance(wasmModule); + +const { buffer } = instance.exports.memory; +assert(buffer instanceof SharedArrayBuffer); + +{ + const { port1, port2 } = new MessageChannel(); + port1.postMessage(buffer); + port2.once('message', common.mustCall((buffer2) => { + // Make sure serialized + deserialized buffer refer to the same memory. + const expected = 'Hello, world!'; + const bytes = Buffer.from(buffer).write(expected); + const deserialized = Buffer.from(buffer2).toString('utf8', 0, bytes); + assert.deepStrictEqual(deserialized, expected); + })); +} + +{ + // Make sure we can free WASM memory originating from a thread that already + // stopped when we exit. + const worker = new Worker(` + const { parentPort } = require('worker_threads'); + + // Compile the same WASM module from its source bytes. + const wasmSource = new Uint8Array([${wasmSource.join(',')}]); + const wasmModule = new WebAssembly.Module(wasmSource); + const instance = new WebAssembly.Instance(wasmModule); + parentPort.postMessage(instance.exports.memory); + + // Do the same thing, except we receive the WASM module via transfer. + parentPort.once('message', ({ wasmModule }) => { + const instance = new WebAssembly.Instance(wasmModule); + parentPort.postMessage(instance.exports.memory); + }); + `, { eval: true }); + worker.on('message', common.mustCall(({ buffer }) => { + assert(buffer instanceof SharedArrayBuffer); + worker.buf = buffer; // Basically just keep the reference to buffer alive. + }, 2)); + worker.once('exit', common.mustCall()); + worker.postMessage({ wasmModule }); +} diff --git a/test/js/node/test/parallel/test-worker-terminate-http2-respond-with-file.js b/test/js/node/test/parallel/test-worker-terminate-http2-respond-with-file.js new file mode 100644 index 0000000000..9c80d68a46 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-terminate-http2-respond-with-file.js @@ -0,0 +1,39 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); +const { duplexPair } = require('stream'); +const { Worker, isMainThread } = require('worker_threads'); + +// This is a variant of test-http2-generic-streams-sendfile for checking +// that Workers can be terminated during a .respondWithFile() operation. + +if (isMainThread) { + return new Worker(__filename); +} + +{ + const server = http2.createServer(); + server.on('stream', common.mustCall((stream, headers) => { + stream.respondWithFile(process.execPath); // Use a large-ish file. + })); + + const [ clientSide, serverSide ] = duplexPair(); + server.emit('connection', serverSide); + + const client = http2.connect('http://localhost:80', { + createConnection: common.mustCall(() => clientSide) + }); + + const req = client.request(); + + req.on('response', common.mustCall((headers) => { + assert.strictEqual(headers[':status'], 200); + })); + + req.on('data', common.mustCall(() => process.exit())); + req.on('end', common.mustNotCall()); + req.end(); +}