From 1d3cff2dbce68c633c552f3bf5eaac521a304518 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Fri, 3 Jan 2025 22:46:24 -0800 Subject: [PATCH] node: add more passing tests --- .../test/parallel/test-buffer-bytelength.js | 132 ++++++++++++++++++ test/js/node/test/parallel/test-buffer-new.js | 11 ++ test/js/node/test/parallel/test-common-gc.js | 14 ++ .../test-finalization-registry-shutdown.js | 23 +++ .../node/test/parallel/test-fs-chmod-mask.js | 89 ++++++++++++ .../test/parallel/test-fs-readfile-flags.js | 50 +++++++ ...est-fs-watch-file-enoent-after-deletion.js | 47 +++++++ ...-watch-recursive-add-file-to-new-folder.js | 53 +++++++ .../test-fs-watch-recursive-symlink.js | 111 +++++++++++++++ .../test/parallel/test-http-full-response.js | 81 +++++++++++ .../parallel/test-http-no-content-length.js | 44 ++++++ ...t-http-server-connections-checking-leak.js | 24 ++++ ...est-http2-large-write-multiple-requests.js | 53 +++++++ .../test-http2-server-close-callback.js | 27 ++++ ...-https-server-connections-checking-leak.js | 29 ++++ .../test/parallel/test-net-server-close.js | 45 ++++++ .../node/test/parallel/test-net-sync-cork.js | 33 +++++ .../parallel/test-primitive-timer-leak.js | 23 +++ .../test/parallel/test-stdio-pipe-access.js | 38 +++++ .../parallel/test-timers-immediate-queue.js | 56 ++++++++ .../test-tls-connect-address-family.js | 49 +++++++ .../test-tls-wrap-econnreset-socket.js | 34 +++++ .../node/test/parallel/test-ttywrap-stack.js | 20 +++ .../test-util-inspect-long-running.js | 20 +++ .../test-worker-message-port-wasm-threads.js | 53 +++++++ .../test-worker-process-exit-async-module.js | 11 ++ 26 files changed, 1170 insertions(+) create mode 100644 test/js/node/test/parallel/test-buffer-bytelength.js create mode 100644 test/js/node/test/parallel/test-buffer-new.js create mode 100644 test/js/node/test/parallel/test-common-gc.js create mode 100644 test/js/node/test/parallel/test-finalization-registry-shutdown.js create mode 100644 test/js/node/test/parallel/test-fs-chmod-mask.js create mode 100644 test/js/node/test/parallel/test-fs-readfile-flags.js create mode 100644 test/js/node/test/parallel/test-fs-watch-file-enoent-after-deletion.js create mode 100644 test/js/node/test/parallel/test-fs-watch-recursive-add-file-to-new-folder.js create mode 100644 test/js/node/test/parallel/test-fs-watch-recursive-symlink.js create mode 100644 test/js/node/test/parallel/test-http-full-response.js create mode 100644 test/js/node/test/parallel/test-http-no-content-length.js create mode 100644 test/js/node/test/parallel/test-http-server-connections-checking-leak.js create mode 100644 test/js/node/test/parallel/test-http2-large-write-multiple-requests.js create mode 100644 test/js/node/test/parallel/test-http2-server-close-callback.js create mode 100644 test/js/node/test/parallel/test-https-server-connections-checking-leak.js create mode 100644 test/js/node/test/parallel/test-net-server-close.js create mode 100644 test/js/node/test/parallel/test-net-sync-cork.js create mode 100644 test/js/node/test/parallel/test-primitive-timer-leak.js create mode 100644 test/js/node/test/parallel/test-stdio-pipe-access.js create mode 100644 test/js/node/test/parallel/test-timers-immediate-queue.js create mode 100644 test/js/node/test/parallel/test-tls-connect-address-family.js create mode 100644 test/js/node/test/parallel/test-tls-wrap-econnreset-socket.js create mode 100644 test/js/node/test/parallel/test-ttywrap-stack.js create mode 100644 test/js/node/test/parallel/test-util-inspect-long-running.js create mode 100644 test/js/node/test/parallel/test-worker-message-port-wasm-threads.js create mode 100644 test/js/node/test/parallel/test-worker-process-exit-async-module.js diff --git a/test/js/node/test/parallel/test-buffer-bytelength.js b/test/js/node/test/parallel/test-buffer-bytelength.js new file mode 100644 index 00000000000000..95d54d425bc252 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-bytelength.js @@ -0,0 +1,132 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const SlowBuffer = require('buffer').SlowBuffer; +const vm = require('vm'); + +[ + [32, 'latin1'], + [NaN, 'utf8'], + [{}, 'latin1'], + [], +].forEach((args) => { + assert.throws( + () => Buffer.byteLength(...args), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string or an instance ' + + 'of Buffer or ArrayBuffer.' + + common.invalidArgTypeHelper(args[0]) + } + ); +}); + +assert(ArrayBuffer.isView(new Buffer(10))); +assert(ArrayBuffer.isView(new SlowBuffer(10))); +assert(ArrayBuffer.isView(Buffer.alloc(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); +assert(ArrayBuffer.isView(Buffer.from(''))); + +// buffer +const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); +assert.strictEqual(Buffer.byteLength(incomplete), 5); +const ascii = Buffer.from('abc'); +assert.strictEqual(Buffer.byteLength(ascii), 3); + +// ArrayBuffer +const buffer = new ArrayBuffer(8); +assert.strictEqual(Buffer.byteLength(buffer), 8); + +// TypedArray +const int8 = new Int8Array(8); +assert.strictEqual(Buffer.byteLength(int8), 8); +const uint8 = new Uint8Array(8); +assert.strictEqual(Buffer.byteLength(uint8), 8); +const uintc8 = new Uint8ClampedArray(2); +assert.strictEqual(Buffer.byteLength(uintc8), 2); +const int16 = new Int16Array(8); +assert.strictEqual(Buffer.byteLength(int16), 16); +const uint16 = new Uint16Array(8); +assert.strictEqual(Buffer.byteLength(uint16), 16); +const int32 = new Int32Array(8); +assert.strictEqual(Buffer.byteLength(int32), 32); +const uint32 = new Uint32Array(8); +assert.strictEqual(Buffer.byteLength(uint32), 32); +const float32 = new Float32Array(8); +assert.strictEqual(Buffer.byteLength(float32), 32); +const float64 = new Float64Array(8); +assert.strictEqual(Buffer.byteLength(float64), 64); + +// DataView +const dv = new DataView(new ArrayBuffer(2)); +assert.strictEqual(Buffer.byteLength(dv), 2); + +// Special case: zero length string +assert.strictEqual(Buffer.byteLength('', 'ascii'), 0); +assert.strictEqual(Buffer.byteLength('', 'HeX'), 0); + +// utf8 +assert.strictEqual(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19); +assert.strictEqual(Buffer.byteLength('κλμνξο', 'utf8'), 12); +assert.strictEqual(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15); +assert.strictEqual(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12); +// Without an encoding, utf8 should be assumed +assert.strictEqual(Buffer.byteLength('hey there'), 9); +assert.strictEqual(Buffer.byteLength('𠱸挶νξ#xx :)'), 17); +assert.strictEqual(Buffer.byteLength('hello world', ''), 11); +// It should also be assumed with unrecognized encoding +assert.strictEqual(Buffer.byteLength('hello world', 'abc'), 11); +assert.strictEqual(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10); + +// base64 +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'BASE64'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', 'base64'), 25 +); +// base64url +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'base64url'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'BASE64URL'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE', 'base64url'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64url'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw', 'base64url'), 25 +); +// special padding +assert.strictEqual(Buffer.byteLength('aaa=', 'base64'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64'), 3); +assert.strictEqual(Buffer.byteLength('aaa=', 'base64url'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64url'), 3); + +assert.strictEqual(Buffer.byteLength('Il était tué'), 14); +assert.strictEqual(Buffer.byteLength('Il était tué', 'utf8'), 14); + +['ascii', 'latin1', 'binary'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 12); + }); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 24); + }); + +// Test that ArrayBuffer from a different context is detected correctly +const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + +// Verify that invalid encodings are treated as utf8 +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.strictEqual(Buffer.byteLength('foo', encoding), + Buffer.byteLength('foo', 'utf8')); +} diff --git a/test/js/node/test/parallel/test-buffer-new.js b/test/js/node/test/parallel/test-buffer-new.js new file mode 100644 index 00000000000000..0f8fa485f29990 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-new.js @@ -0,0 +1,11 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.throws(() => new Buffer(42, 'utf8'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string. Received type ' + + 'number (42)' +}); diff --git a/test/js/node/test/parallel/test-common-gc.js b/test/js/node/test/parallel/test-common-gc.js new file mode 100644 index 00000000000000..f7d73ccd0423e3 --- /dev/null +++ b/test/js/node/test/parallel/test-common-gc.js @@ -0,0 +1,14 @@ +'use strict'; +// Flags: --expose-gc +const common = require('../common'); +const { onGC } = require('../common/gc'); + +{ + onGC({}, { ongc: common.mustCall() }); + global.gc(); +} + +{ + onGC(process, { ongc: common.mustNotCall() }); + global.gc(); +} 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 00000000000000..f896aa2f285c75 --- /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. + global.gc(); +}); diff --git a/test/js/node/test/parallel/test-fs-chmod-mask.js b/test/js/node/test/parallel/test-fs-chmod-mask.js new file mode 100644 index 00000000000000..53f1931be4cbef --- /dev/null +++ b/test/js/node/test/parallel/test-fs-chmod-mask.js @@ -0,0 +1,89 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs APIs. + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +let mode; +// On Windows chmod is only able to manipulate write permission +if (common.isWindows) { + mode = 0o444; // read-only +} else { + mode = 0o777; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = tmpdir.resolve(`chmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmod(file, input, common.mustSucceed(() => { + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } + + { + const file = tmpdir.resolve(`chmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmodSync(file, input); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = tmpdir.resolve(`fchmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.open(file, 'w', common.mustSucceed((fd) => { + fs.fchmod(fd, input, common.mustSucceed(() => { + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.close(fd, assert.ifError); + })); + })); + } + + { + const file = tmpdir.resolve(`fchmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + const fd = fs.openSync(file, 'w'); + + fs.fchmodSync(fd, input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + + fs.close(fd, assert.ifError); + } + + if (fs.lchmod) { + const link = tmpdir.resolve(`lchmod-src-${suffix}`); + const file = tmpdir.resolve(`lchmod-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmod(link, input, common.mustSucceed(() => { + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + })); + } + + if (fs.lchmodSync) { + const link = tmpdir.resolve(`lchmodSync-src-${suffix}`); + const file = tmpdir.resolve(`lchmodSync-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmodSync(link, input); + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/js/node/test/parallel/test-fs-readfile-flags.js b/test/js/node/test/parallel/test-fs-readfile-flags.js new file mode 100644 index 00000000000000..72b910aeeb48d6 --- /dev/null +++ b/test/js/node/test/parallel/test-fs-readfile-flags.js @@ -0,0 +1,50 @@ +'use strict'; + +// Test of fs.readFile with different flags. +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +{ + const emptyFile = tmpdir.resolve('empty.txt'); + fs.closeSync(fs.openSync(emptyFile, 'w')); + + fs.readFile( + emptyFile, + // With `a+` the file is created if it does not exist + common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'a+' }), + common.mustCall((err, data) => { assert.strictEqual(data, ''); }) + ); + + fs.readFile( + emptyFile, + // Like `a+` but fails if the path exists. + common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'ax+' }), + common.mustCall((err, data) => { assert.strictEqual(err.code, 'EEXIST'); }) + ); +} + +{ + const willBeCreated = tmpdir.resolve('will-be-created'); + + fs.readFile( + willBeCreated, + // With `a+` the file is created if it does not exist + common.mustNotMutateObjectDeep({ encoding: 'utf8', flag: 'a+' }), + common.mustCall((err, data) => { assert.strictEqual(data, ''); }) + ); +} + +{ + const willNotBeCreated = tmpdir.resolve('will-not-be-created'); + + fs.readFile( + willNotBeCreated, + // Default flag is `r`. An exception occurs if the file does not exist. + common.mustNotMutateObjectDeep({ encoding: 'utf8' }), + common.mustCall((err, data) => { assert.strictEqual(err.code, 'ENOENT'); }) + ); +} diff --git a/test/js/node/test/parallel/test-fs-watch-file-enoent-after-deletion.js b/test/js/node/test/parallel/test-fs-watch-file-enoent-after-deletion.js new file mode 100644 index 00000000000000..e4baf90fd17b94 --- /dev/null +++ b/test/js/node/test/parallel/test-fs-watch-file-enoent-after-deletion.js @@ -0,0 +1,47 @@ +// 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'); + +// Make sure the deletion event gets reported in the following scenario: +// 1. Watch a file. +// 2. The initial stat() goes okay. +// 3. Something deletes the watched file. +// 4. The second stat() fails with ENOENT. + +// The second stat() translates into the first 'change' event but a logic error +// stopped it from getting emitted. +// https://github.com/nodejs/node-v0.x-archive/issues/4027 + +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const filename = tmpdir.resolve('watched'); +fs.writeFileSync(filename, 'quis custodiet ipsos custodes'); + +fs.watchFile(filename, { interval: 50 }, common.mustCall(function(curr, prev) { + fs.unwatchFile(filename); +})); + +fs.unlinkSync(filename); diff --git a/test/js/node/test/parallel/test-fs-watch-recursive-add-file-to-new-folder.js b/test/js/node/test/parallel/test-fs-watch-recursive-add-file-to-new-folder.js new file mode 100644 index 00000000000000..fcc49bb7464937 --- /dev/null +++ b/test/js/node/test/parallel/test-fs-watch-recursive-add-file-to-new-folder.js @@ -0,0 +1,53 @@ +'use strict'; + +const common = require('../common'); + +if (common.isIBMi) + common.skip('IBMi does not support `fs.watch()`'); + +// fs-watch on folders have limited capability in AIX. +// The testcase makes use of folder watching, and causes +// hang. This behavior is documented. Skip this for AIX. + +if (common.isAIX) + common.skip('folder watch capability is limited in AIX.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +const testDir = tmpdir.path; +tmpdir.refresh(); + +// Add a file to newly created folder to already watching folder + +const rootDirectory = fs.mkdtempSync(testDir + path.sep); +const testDirectory = path.join(rootDirectory, 'test-3'); +fs.mkdirSync(testDirectory); + +const filePath = path.join(testDirectory, 'folder-3'); + +const childrenFile = 'file-4.txt'; +const childrenAbsolutePath = path.join(filePath, childrenFile); +const childrenRelativePath = path.join(path.basename(filePath), childrenFile); +let watcherClosed = false; + +const watcher = fs.watch(testDirectory, { recursive: true }); +watcher.on('change', function(event, filename) { + if (filename === childrenRelativePath) { + assert.strictEqual(event, 'rename'); + watcher.close(); + watcherClosed = true; + } +}); + +// Do the write with a delay to ensure that the OS is ready to notify us. +setTimeout(() => { + fs.mkdirSync(filePath); + fs.writeFileSync(childrenAbsolutePath, 'world'); +}, common.platformTimeout(200)); + +process.once('exit', function() { + assert(watcherClosed, 'watcher Object was not closed'); +}); diff --git a/test/js/node/test/parallel/test-fs-watch-recursive-symlink.js b/test/js/node/test/parallel/test-fs-watch-recursive-symlink.js new file mode 100644 index 00000000000000..37f71f56f8d019 --- /dev/null +++ b/test/js/node/test/parallel/test-fs-watch-recursive-symlink.js @@ -0,0 +1,111 @@ +'use strict'; + +const common = require('../common'); +const { setTimeout } = require('timers/promises'); + +if (common.isIBMi) + common.skip('IBMi does not support `fs.watch()`'); + +// fs-watch on folders have limited capability in AIX. +// The testcase makes use of folder watching, and causes +// hang. This behavior is documented. Skip this for AIX. + +if (common.isAIX) + common.skip('folder watch capability is limited in AIX.'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +const testDir = tmpdir.path; +tmpdir.refresh(); + +(async () => { + // Add a recursive symlink to the parent folder + + const testDirectory = fs.mkdtempSync(testDir + path.sep); + + // Do not use `testDirectory` as base. It will hang the tests. + const rootDirectory = path.join(testDirectory, 'test-1'); + fs.mkdirSync(rootDirectory); + + const filePath = path.join(rootDirectory, 'file.txt'); + + const symlinkFolder = path.join(rootDirectory, 'symlink-folder'); + fs.symlinkSync(rootDirectory, symlinkFolder); + + if (common.isMacOS) { + // On macOS delay watcher start to avoid leaking previous events. + // Refs: https://github.com/libuv/libuv/pull/4503 + await setTimeout(common.platformTimeout(100)); + } + + const watcher = fs.watch(rootDirectory, { recursive: true }); + let watcherClosed = false; + watcher.on('change', function(event, filename) { + assert.ok(event === 'rename', `Received ${event}`); + assert.ok(filename === path.basename(symlinkFolder) || filename === path.basename(filePath), `Received ${filename}`); + + if (filename === path.basename(filePath)) { + watcher.close(); + watcherClosed = true; + } + }); + + await setTimeout(common.platformTimeout(100)); + fs.writeFileSync(filePath, 'world'); + + process.once('exit', function() { + assert(watcherClosed, 'watcher Object was not closed'); + }); +})().then(common.mustCall()); + +(async () => { + // This test checks how a symlink to outside the tracking folder can trigger change + // tmp/sub-directory/tracking-folder/symlink-folder -> tmp/sub-directory + + const rootDirectory = fs.mkdtempSync(testDir + path.sep); + + const subDirectory = path.join(rootDirectory, 'sub-directory'); + fs.mkdirSync(subDirectory); + + const trackingSubDirectory = path.join(subDirectory, 'tracking-folder'); + fs.mkdirSync(trackingSubDirectory); + + const symlinkFolder = path.join(trackingSubDirectory, 'symlink-folder'); + fs.symlinkSync(subDirectory, symlinkFolder); + + const forbiddenFile = path.join(subDirectory, 'forbidden.txt'); + const acceptableFile = path.join(trackingSubDirectory, 'acceptable.txt'); + + if (common.isMacOS) { + // On macOS delay watcher start to avoid leaking previous events. + // Refs: https://github.com/libuv/libuv/pull/4503 + await setTimeout(common.platformTimeout(100)); + } + + const watcher = fs.watch(trackingSubDirectory, { recursive: true }); + let watcherClosed = false; + watcher.on('change', function(event, filename) { + // macOS will only change the following events: + // { event: 'rename', filename: 'symlink-folder' } + // { event: 'rename', filename: 'acceptable.txt' } + assert.ok(event === 'rename', `Received ${event}`); + assert.ok(filename === path.basename(symlinkFolder) || filename === path.basename(acceptableFile), `Received ${filename}`); + + if (filename === path.basename(acceptableFile)) { + watcher.close(); + watcherClosed = true; + } + }); + + await setTimeout(common.platformTimeout(100)); + fs.writeFileSync(forbiddenFile, 'world'); + await setTimeout(common.platformTimeout(100)); + fs.writeFileSync(acceptableFile, 'acceptable'); + + process.once('exit', function() { + assert(watcherClosed, 'watcher Object was not closed'); + }); +})().then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-http-full-response.js b/test/js/node/test/parallel/test-http-full-response.js new file mode 100644 index 00000000000000..d08e091ebd9808 --- /dev/null +++ b/test/js/node/test/parallel/test-http-full-response.js @@ -0,0 +1,81 @@ +// 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'); +// This test requires the program 'ab' +const http = require('http'); +const exec = require('child_process').exec; + +const bodyLength = 12345; + +const body = 'c'.repeat(bodyLength); + +const server = http.createServer(function(req, res) { + res.writeHead(200, { + 'Content-Length': bodyLength, + 'Content-Type': 'text/plain' + }); + res.end(body); +}); + +function runAb(opts, callback) { + const command = `ab ${opts} http://127.0.0.1:${server.address().port}/`; + exec(command, function(err, stdout, stderr) { + if (err) { + if (/ab|apr/i.test(stderr)) { + common.printSkipMessage(`problem spawning \`ab\`.\n${stderr}`); + process.reallyExit(0); + } + throw err; + } + + let m = /Document Length:\s*(\d+) bytes/i.exec(stdout); + const documentLength = parseInt(m[1]); + + m = /Complete requests:\s*(\d+)/i.exec(stdout); + const completeRequests = parseInt(m[1]); + + m = /HTML transferred:\s*(\d+) bytes/i.exec(stdout); + const htmlTransferred = parseInt(m[1]); + + assert.strictEqual(bodyLength, documentLength); + assert.strictEqual(completeRequests * documentLength, htmlTransferred); + + if (callback) callback(); + }); +} + +server.listen(0, common.mustCall(function() { + runAb('-c 1 -n 10', common.mustCall(function() { + console.log('-c 1 -n 10 okay'); + + runAb('-c 1 -n 100', common.mustCall(function() { + console.log('-c 1 -n 100 okay'); + + runAb('-c 1 -n 1000', common.mustCall(function() { + console.log('-c 1 -n 1000 okay'); + server.close(); + })); + })); + })); +})); diff --git a/test/js/node/test/parallel/test-http-no-content-length.js b/test/js/node/test/parallel/test-http-no-content-length.js new file mode 100644 index 00000000000000..a3a51c015ec86f --- /dev/null +++ b/test/js/node/test/parallel/test-http-no-content-length.js @@ -0,0 +1,44 @@ +// 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 net = require('net'); +const http = require('http'); + +const server = net.createServer(function(socket) { + // Neither Content-Length nor Connection + socket.end('HTTP/1.1 200 ok\r\n\r\nHello'); +}).listen(0, common.mustCall(function() { + http.get({ port: this.address().port }, common.mustCall(function(res) { + let body = ''; + + res.setEncoding('utf8'); + res.on('data', function(chunk) { + body += chunk; + }); + res.on('end', common.mustCall(function() { + assert.strictEqual(body, 'Hello'); + server.close(); + })); + })); +})); diff --git a/test/js/node/test/parallel/test-http-server-connections-checking-leak.js b/test/js/node/test/parallel/test-http-server-connections-checking-leak.js new file mode 100644 index 00000000000000..282c9a569fba7d --- /dev/null +++ b/test/js/node/test/parallel/test-http-server-connections-checking-leak.js @@ -0,0 +1,24 @@ +'use strict'; + +// Flags: --expose-gc + +// Check that creating a server without listening does not leak resources. + +require('../common'); +const { onGC } = require('../common/gc'); +const Countdown = require('../common/countdown'); + +const http = require('http'); +const max = 100; + +// Note that Countdown internally calls common.mustCall, that's why it's not done here. +const countdown = new Countdown(max, () => {}); + +for (let i = 0; i < max; i++) { + const server = http.createServer((req, res) => {}); + onGC(server, { ongc: countdown.dec.bind(countdown) }); +} + +setImmediate(() => { + global.gc(); +}); diff --git a/test/js/node/test/parallel/test-http2-large-write-multiple-requests.js b/test/js/node/test/parallel/test-http2-large-write-multiple-requests.js new file mode 100644 index 00000000000000..bcbb1434cbec91 --- /dev/null +++ b/test/js/node/test/parallel/test-http2-large-write-multiple-requests.js @@ -0,0 +1,53 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// This tests that the http2 server sends data early when it accumulates +// enough from ongoing requests to avoid DoS as mitigation for +// CVE-2019-9517 and CVE-2019-9511. +// Added by https://github.com/nodejs/node/commit/8a4a193 +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const http2 = require('http2'); + +const content = fixtures.readSync('person-large.jpg'); + +const server = http2.createServer({ + maxSessionMemory: 1000 +}); +let streamCount = 0; +server.on('stream', (stream, headers) => { + stream.respond({ + 'content-type': 'image/jpeg', + ':status': 200 + }); + stream.end(content); + console.log('server sends content', ++streamCount); +}); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}/`); + + let endCount = 0; + let finished = 0; + for (let i = 0; i < 100; i++) { + const req = client.request({ ':path': '/' }).end(); + const chunks = []; + req.on('data', (chunk) => { + chunks.push(chunk); + }); + req.on('end', common.mustCall(() => { + console.log('client receives content', ++endCount); + assert.deepStrictEqual(Buffer.concat(chunks), content); + + if (++finished === 100) { + client.close(); + server.close(); + } + })); + req.on('error', (e) => { + console.log('client error', e); + }); + } +})); diff --git a/test/js/node/test/parallel/test-http2-server-close-callback.js b/test/js/node/test/parallel/test-http2-server-close-callback.js new file mode 100644 index 00000000000000..e4cd24ce209782 --- /dev/null +++ b/test/js/node/test/parallel/test-http2-server-close-callback.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const Countdown = require('../common/countdown'); +const http2 = require('http2'); + +const server = http2.createServer(); + +let session; + +const countdown = new Countdown(2, () => { + server.close(common.mustSucceed()); + session.close(); +}); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}`); + client.on('connect', common.mustCall(() => countdown.dec())); +})); + +server.on('session', common.mustCall((s) => { + session = s; + countdown.dec(); +})); diff --git a/test/js/node/test/parallel/test-https-server-connections-checking-leak.js b/test/js/node/test/parallel/test-https-server-connections-checking-leak.js new file mode 100644 index 00000000000000..e920c8e403705f --- /dev/null +++ b/test/js/node/test/parallel/test-https-server-connections-checking-leak.js @@ -0,0 +1,29 @@ +'use strict'; + +// Flags: --expose-gc + +// Check that creating a server without listening does not leak resources. + +const common = require('../common'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const { onGC } = require('../common/gc'); +const Countdown = require('../common/countdown'); + +const https = require('https'); +const max = 100; + +// Note that Countdown internally calls common.mustCall, that's why it's not done here. +const countdown = new Countdown(max, () => {}); + +for (let i = 0; i < max; i++) { + const server = https.createServer((req, res) => {}); + onGC(server, { ongc: countdown.dec.bind(countdown) }); +} + +setImmediate(() => { + global.gc(); +}); diff --git a/test/js/node/test/parallel/test-net-server-close.js b/test/js/node/test/parallel/test-net-server-close.js new file mode 100644 index 00000000000000..8291f70432ec97 --- /dev/null +++ b/test/js/node/test/parallel/test-net-server-close.js @@ -0,0 +1,45 @@ +// 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 net = require('net'); + +const sockets = []; + +const server = net.createServer(function(c) { + c.on('close', common.mustCall()); + + sockets.push(c); + + if (sockets.length === 2) { + assert.strictEqual(server.close(), server); + sockets.forEach((c) => c.destroy()); + } +}); + +server.on('close', common.mustCall()); + +assert.strictEqual(server, server.listen(0, () => { + net.createConnection(server.address().port); + net.createConnection(server.address().port); +})); 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 00000000000000..447f42ca91e768 --- /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-primitive-timer-leak.js b/test/js/node/test/parallel/test-primitive-timer-leak.js new file mode 100644 index 00000000000000..d590a0347b9cac --- /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(() => { + global.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-stdio-pipe-access.js b/test/js/node/test/parallel/test-stdio-pipe-access.js new file mode 100644 index 00000000000000..ac0e22c399a1b9 --- /dev/null +++ b/test/js/node/test/parallel/test-stdio-pipe-access.js @@ -0,0 +1,38 @@ +'use strict'; +const common = require('../common'); +if (!common.isMainThread) + common.skip("Workers don't have process-like stdio"); + +// Test if Node handles accessing process.stdin if it is a redirected +// pipe without deadlocking +const { spawn, spawnSync } = require('child_process'); + +const numTries = 5; +const who = process.argv.length <= 2 ? 'runner' : process.argv[2]; + +switch (who) { + case 'runner': + for (let num = 0; num < numTries; ++num) { + spawnSync(process.argv0, + [process.argv[1], 'parent'], + { 'stdio': 'inherit' }); + } + break; + case 'parent': { + const middle = spawn(process.argv0, + [process.argv[1], 'middle'], + { 'stdio': 'pipe' }); + middle.stdout.on('data', () => {}); + break; + } + case 'middle': + spawn(process.argv0, + [process.argv[1], 'bottom'], + { 'stdio': [ process.stdin, + process.stdout, + process.stderr ] }); + break; + case 'bottom': + process.stdin; // eslint-disable-line no-unused-expressions + break; +} diff --git a/test/js/node/test/parallel/test-timers-immediate-queue.js b/test/js/node/test/parallel/test-timers-immediate-queue.js new file mode 100644 index 00000000000000..8b433ddedbf416 --- /dev/null +++ b/test/js/node/test/parallel/test-timers-immediate-queue.js @@ -0,0 +1,56 @@ +// 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'); + +// setImmediate should run clear its queued cbs once per event loop turn +// but immediates queued while processing the current queue should happen +// on the next turn of the event loop. + +// hit should be the exact same size of QUEUE, if we're letting things +// recursively add to the immediate QUEUE hit will be > QUEUE + +let ticked = false; + +let hit = 0; +const QUEUE = 10; + +function run() { + if (hit === 0) { + setTimeout(() => { ticked = true; }, 1); + const now = Date.now(); + while (Date.now() - now < 2); + } + + if (ticked) return; + + hit += 1; + setImmediate(run); +} + +for (let i = 0; i < QUEUE; i++) + setImmediate(run); + +process.on('exit', function() { + assert.strictEqual(hit, QUEUE); +}); diff --git a/test/js/node/test/parallel/test-tls-connect-address-family.js b/test/js/node/test/parallel/test-tls-connect-address-family.js new file mode 100644 index 00000000000000..083208cc1d3ab0 --- /dev/null +++ b/test/js/node/test/parallel/test-tls-connect-address-family.js @@ -0,0 +1,49 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (!common.hasIPv6) + common.skip('no IPv6 support'); + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const tls = require('tls'); +const dns = require('dns'); + +function runTest() { + tls.createServer({ + cert: fixtures.readKey('agent1-cert.pem'), + key: fixtures.readKey('agent1-key.pem'), + }).on('connection', common.mustCall(function() { + this.close(); + })).listen(0, '::1', common.mustCall(function() { + const options = { + host: 'localhost', + port: this.address().port, + family: 6, + rejectUnauthorized: false, + }; + // Will fail with ECONNREFUSED if the address family is not honored. + tls.connect(options).once('secureConnect', common.mustCall(function() { + assert.strictEqual(this.remoteAddress, '::1'); + this.destroy(); + })); + })); +} + +dns.lookup('localhost', { + family: 6, all: true +}, common.mustCall((err, addresses) => { + if (err) { + if (err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN') + common.skip('localhost does not resolve to ::1'); + + throw err; + } + + if (addresses.some((val) => val.address === '::1')) + runTest(); + else + common.skip('localhost does not resolve to ::1'); +})); diff --git a/test/js/node/test/parallel/test-tls-wrap-econnreset-socket.js b/test/js/node/test/parallel/test-tls-wrap-econnreset-socket.js new file mode 100644 index 00000000000000..ec305b785e0545 --- /dev/null +++ b/test/js/node/test/parallel/test-tls-wrap-econnreset-socket.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const net = require('net'); +const tls = require('tls'); + +const server = net.createServer((c) => { + c.end(); +}).listen(common.mustCall(() => { + const port = server.address().port; + + const socket = new net.Socket(); + + let errored = false; + tls.connect({ socket }) + .once('error', common.mustCall((e) => { + assert.strictEqual(e.code, 'ECONNRESET'); + assert.strictEqual(e.path, undefined); + assert.strictEqual(e.host, undefined); + assert.strictEqual(e.port, undefined); + assert.strictEqual(e.localAddress, undefined); + errored = true; + server.close(); + })) + .on('close', common.mustCall(() => { + assert.strictEqual(errored, true); + })); + + socket.connect(port); +})); diff --git a/test/js/node/test/parallel/test-ttywrap-stack.js b/test/js/node/test/parallel/test-ttywrap-stack.js new file mode 100644 index 00000000000000..6fa85a338f1129 --- /dev/null +++ b/test/js/node/test/parallel/test-ttywrap-stack.js @@ -0,0 +1,20 @@ +'use strict'; +const common = require('../common'); + +// This test ensures that console.log +// will not crash the process if there +// is not enough space on the V8 stack + +const done = common.mustCall(); + +async function test() { + await test(); +} + +(async () => { + try { + await test(); + } catch (err) { + console.log(err); + } +})().then(done, done); diff --git a/test/js/node/test/parallel/test-util-inspect-long-running.js b/test/js/node/test/parallel/test-util-inspect-long-running.js new file mode 100644 index 00000000000000..167f72ba64631a --- /dev/null +++ b/test/js/node/test/parallel/test-util-inspect-long-running.js @@ -0,0 +1,20 @@ +'use strict'; + +require('../common'); + +// Test that huge objects don't crash due to exceeding the maximum heap size. + +const util = require('util'); + +// Create a difficult to stringify object. Without the artificial limitation +// this would crash or throw an maximum string size error. +let last = {}; +const obj = last; + +for (let i = 0; i < 1000; i++) { + last.next = { circular: obj, last, obj: { a: 1, b: 2, c: true } }; + last = last.next; + obj[i] = last; +} + +util.inspect(obj, { depth: Infinity }); 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 00000000000000..fe70261fd7b2eb --- /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-process-exit-async-module.js b/test/js/node/test/parallel/test-worker-process-exit-async-module.js new file mode 100644 index 00000000000000..38d4ad74c7bd85 --- /dev/null +++ b/test/js/node/test/parallel/test-worker-process-exit-async-module.js @@ -0,0 +1,11 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Worker } = require('worker_threads'); + +// Regression for https://github.com/nodejs/node/issues/43182. +const w = new Worker(new URL('data:text/javascript,process.exit(1);await new Promise(()=>{ process.exit(2); })')); +w.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 1); +}));