ci: make sure we're running the sequential node tests too (#17928)

This commit is contained in:
Meghan Denny
2025-03-06 15:04:21 -08:00
committed by GitHub
parent 903d6d058c
commit 7161326baa
25 changed files with 2114 additions and 129 deletions

View File

@@ -877,12 +877,21 @@ function isNodeParallelTest(testPath) {
return testPath.replaceAll(sep, "/").includes("js/node/test/parallel/");
}
/**
* @param {string} testPath
* @returns {boolean}
*/
function isNodeSequentialTest(testPath) {
return testPath.replaceAll(sep, "/").includes("js/node/test/sequential/");
}
/**
* @param {string} path
* @returns {boolean}
*/
function isTest(path) {
if (isNodeParallelTest(path) && targetDoesRunNodeTests()) return true;
if (isNodeSequentialTest(path) && targetDoesRunNodeTests()) return true;
if (path.replaceAll(sep, "/").startsWith("js/node/cluster/test-") && path.endsWith(".ts")) return true;
return isTestStrict(path);
}

View File

@@ -0,0 +1,59 @@
// 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');
// Open a chain of five Node processes each a child of the next. The final
// process exits immediately. Each process in the chain is instructed to exit
// when its child exits.
// https://github.com/joyent/node/issues/1726
const assert = require('assert');
const ch = require('child_process');
const gen = +(process.argv[2] || 0);
const maxGen = 5;
if (gen === maxGen) {
console.error('hit maxGen, exiting', maxGen);
return;
}
const child = ch.spawn(process.execPath, [__filename, gen + 1], {
stdio: [ 'ignore', 'pipe', 'ignore' ]
});
assert.ok(!child.stdin);
assert.ok(child.stdout);
assert.ok(!child.stderr);
console.error('gen=%d, pid=%d', gen, process.pid);
child.on('exit', function(code) {
console.error('exit %d from gen %d', code, gen + 1);
});
child.stdout.pipe(process.stdout);
child.stdout.on('close', function() {
console.error('child.stdout close gen=%d', gen);
});

View File

@@ -0,0 +1,18 @@
'use strict';
const common = require('../common');
common.skipIfInspectorDisabled();
const spawn = require('child_process').spawn;
const proc = spawn(process.execPath, ['inspect', 'foo']);
proc.stdout.setEncoding('utf8');
let needToSendExit = true;
let output = '';
proc.stdout.on('data', (data) => {
output += data;
if (output.includes('debug> ') && needToSendExit) {
proc.stdin.write('.exit\n');
needToSendExit = false;
}
});

View File

@@ -1,113 +0,0 @@
// 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');
// This test asserts the semantics of dgram::socket.bind({ exclusive })
// when called from a cluster.Worker
const assert = require('assert');
const cluster = require('cluster');
const dgram = require('dgram');
const BYE = 'bye';
const WORKER2_NAME = 'wrker2';
if (cluster.isPrimary) {
const worker1 = cluster.fork();
if (common.isWindows) {
worker1.on('error', common.mustCall((err) => {
console.log(err);
assert.strictEqual(err.code, 'ENOTSUP');
worker1.kill();
}));
return;
}
worker1.on('message', common.mustCall((msg) => {
console.log(msg);
assert.strictEqual(msg, 'success');
const worker2 = cluster.fork({ WORKER2_NAME });
worker2.on('message', common.mustCall((msg) => {
console.log(msg);
assert.strictEqual(msg, 'socket3:EADDRINUSE');
// finish test
worker1.send(BYE);
worker2.send(BYE);
}));
worker2.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(signal, null);
assert.strictEqual(code, 0);
}));
}));
worker1.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(signal, null);
assert.strictEqual(code, 0);
}));
// end primary code
} else {
// worker code
process.on('message', common.mustCallAtLeast((msg) => {
if (msg === BYE) process.exit(0);
}), 1);
const isSecondWorker = process.env.WORKER2_NAME === WORKER2_NAME;
const socket1 = dgram.createSocket('udp4', common.mustNotCall());
const socket2 = dgram.createSocket('udp4', common.mustNotCall());
const socket3 = dgram.createSocket('udp4', common.mustNotCall());
socket1.on('error', (err) => assert.fail(err));
socket2.on('error', (err) => assert.fail(err));
// First worker should bind, second should err
const socket3OnBind =
isSecondWorker ?
common.mustNotCall() :
common.mustCall(() => {
const port3 = socket3.address().port;
assert.strictEqual(typeof port3, 'number');
process.send('success');
});
// An error is expected only in the second worker
const socket3OnError =
!isSecondWorker ?
common.mustNotCall() :
common.mustCall((err) => {
process.send(`socket3:${err.code}`);
});
const address = common.localhostIPv4;
const opt1 = { address, port: 0, exclusive: false };
const opt2 = { address, port: common.PORT, exclusive: false };
const opt3 = { address, port: common.PORT + 1, exclusive: true };
socket1.bind(opt1, common.mustCall(() => {
const port1 = socket1.address().port;
assert.strictEqual(typeof port1, 'number');
socket2.bind(opt2, common.mustCall(() => {
const port2 = socket2.address().port;
assert.strictEqual(typeof port2, 'number');
socket3.on('error', socket3OnError);
socket3.bind(opt3, socket3OnBind);
}));
}));
}

View File

@@ -0,0 +1,242 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const fsPromises = fs.promises;
const pathModule = require('path');
const tmpdir = require('../common/tmpdir');
const testDir = tmpdir.path;
const fileStructure = [
[ 'a', [ 'a', 'foo', 'bar' ] ],
[ 'b', [ 'foo', 'bar' ] ],
[ 'c', [ 'foo', 'bar' ] ],
[ 'd', [ 'foo', 'bar' ] ],
[ 'e', [ 'foo', 'bar' ] ],
[ 'f', [ 'foo', 'bar' ] ],
[ 'g', [ 'foo', 'bar' ] ],
[ 'h', [ 'foo', 'bar' ] ],
[ 'i', [ 'foo', 'bar' ] ],
[ 'j', [ 'foo', 'bar' ] ],
[ 'k', [ 'foo', 'bar' ] ],
[ 'l', [ 'foo', 'bar' ] ],
[ 'm', [ 'foo', 'bar' ] ],
[ 'n', [ 'foo', 'bar' ] ],
[ 'o', [ 'foo', 'bar' ] ],
[ 'p', [ 'foo', 'bar' ] ],
[ 'q', [ 'foo', 'bar' ] ],
[ 'r', [ 'foo', 'bar' ] ],
[ 's', [ 'foo', 'bar' ] ],
[ 't', [ 'foo', 'bar' ] ],
[ 'u', [ 'foo', 'bar' ] ],
[ 'v', [ 'foo', 'bar' ] ],
[ 'w', [ 'foo', 'bar' ] ],
[ 'x', [ 'foo', 'bar' ] ],
[ 'y', [ 'foo', 'bar' ] ],
[ 'z', [ 'foo', 'bar' ] ],
[ 'aa', [ 'foo', 'bar' ] ],
[ 'bb', [ 'foo', 'bar' ] ],
[ 'cc', [ 'foo', 'bar' ] ],
[ 'dd', [ 'foo', 'bar' ] ],
[ 'ee', [ 'foo', 'bar' ] ],
[ 'ff', [ 'foo', 'bar' ] ],
[ 'gg', [ 'foo', 'bar' ] ],
[ 'hh', [ 'foo', 'bar' ] ],
[ 'ii', [ 'foo', 'bar' ] ],
[ 'jj', [ 'foo', 'bar' ] ],
[ 'kk', [ 'foo', 'bar' ] ],
[ 'll', [ 'foo', 'bar' ] ],
[ 'mm', [ 'foo', 'bar' ] ],
[ 'nn', [ 'foo', 'bar' ] ],
[ 'oo', [ 'foo', 'bar' ] ],
[ 'pp', [ 'foo', 'bar' ] ],
[ 'qq', [ 'foo', 'bar' ] ],
[ 'rr', [ 'foo', 'bar' ] ],
[ 'ss', [ 'foo', 'bar' ] ],
[ 'tt', [ 'foo', 'bar' ] ],
[ 'uu', [ 'foo', 'bar' ] ],
[ 'vv', [ 'foo', 'bar' ] ],
[ 'ww', [ 'foo', 'bar' ] ],
[ 'xx', [ 'foo', 'bar' ] ],
[ 'yy', [ 'foo', 'bar' ] ],
[ 'zz', [ 'foo', 'bar' ] ],
[ 'abc', [ ['def', [ 'foo', 'bar' ] ], ['ghi', [ 'foo', 'bar' ] ] ] ],
];
function createFiles(path, fileStructure) {
for (const fileOrDir of fileStructure) {
if (typeof fileOrDir === 'string') {
fs.writeFileSync(pathModule.join(path, fileOrDir), '');
} else {
const dirPath = pathModule.join(path, fileOrDir[0]);
fs.mkdirSync(dirPath);
createFiles(dirPath, fileOrDir[1]);
}
}
}
// Make sure tmp directory is clean
tmpdir.refresh();
createFiles(testDir, fileStructure);
const symlinksRootPath = pathModule.join(testDir, 'symlinks');
const symlinkTargetFile = pathModule.join(symlinksRootPath, 'symlink-target-file');
const symlinkTargetDir = pathModule.join(symlinksRootPath, 'symlink-target-dir');
fs.mkdirSync(symlinksRootPath);
fs.writeFileSync(symlinkTargetFile, '');
fs.mkdirSync(symlinkTargetDir);
fs.symlinkSync(symlinkTargetFile, pathModule.join(symlinksRootPath, 'symlink-src-file'));
fs.symlinkSync(symlinkTargetDir, pathModule.join(symlinksRootPath, 'symlink-src-dir'));
const expected = [
'a', 'a/a', 'a/bar', 'a/foo', 'aa', 'aa/bar', 'aa/foo',
'abc', 'abc/def', 'abc/def/bar', 'abc/def/foo', 'abc/ghi', 'abc/ghi/bar', 'abc/ghi/foo',
'b', 'b/bar', 'b/foo', 'bb', 'bb/bar', 'bb/foo',
'c', 'c/bar', 'c/foo', 'cc', 'cc/bar', 'cc/foo',
'd', 'd/bar', 'd/foo', 'dd', 'dd/bar', 'dd/foo',
'e', 'e/bar', 'e/foo', 'ee', 'ee/bar', 'ee/foo',
'f', 'f/bar', 'f/foo', 'ff', 'ff/bar', 'ff/foo',
'g', 'g/bar', 'g/foo', 'gg', 'gg/bar', 'gg/foo',
'h', 'h/bar', 'h/foo', 'hh', 'hh/bar', 'hh/foo',
'i', 'i/bar', 'i/foo', 'ii', 'ii/bar', 'ii/foo',
'j', 'j/bar', 'j/foo', 'jj', 'jj/bar', 'jj/foo',
'k', 'k/bar', 'k/foo', 'kk', 'kk/bar', 'kk/foo',
'l', 'l/bar', 'l/foo', 'll', 'll/bar', 'll/foo',
'm', 'm/bar', 'm/foo', 'mm', 'mm/bar', 'mm/foo',
'n', 'n/bar', 'n/foo', 'nn', 'nn/bar', 'nn/foo',
'o', 'o/bar', 'o/foo', 'oo', 'oo/bar', 'oo/foo',
'p', 'p/bar', 'p/foo', 'pp', 'pp/bar', 'pp/foo',
'q', 'q/bar', 'q/foo', 'qq', 'qq/bar', 'qq/foo',
'r', 'r/bar', 'r/foo', 'rr', 'rr/bar', 'rr/foo',
's', 's/bar', 's/foo', 'ss', 'ss/bar', 'ss/foo',
'symlinks', 'symlinks/symlink-src-dir', 'symlinks/symlink-src-file',
'symlinks/symlink-target-dir', 'symlinks/symlink-target-file',
't', 't/bar', 't/foo', 'tt', 'tt/bar', 'tt/foo',
'u', 'u/bar', 'u/foo', 'uu', 'uu/bar', 'uu/foo',
'v', 'v/bar', 'v/foo', 'vv', 'vv/bar', 'vv/foo',
'w', 'w/bar', 'w/foo', 'ww', 'ww/bar', 'ww/foo',
'x', 'x/bar', 'x/foo', 'xx', 'xx/bar', 'xx/foo',
'y', 'y/bar', 'y/foo', 'yy', 'yy/bar', 'yy/foo',
'z', 'z/bar', 'z/foo', 'zz', 'zz/bar', 'zz/foo',
];
// Normalize paths once for non POSIX platforms
for (let i = 0; i < expected.length; i++) {
expected[i] = pathModule.normalize(expected[i]);
}
function getDirentPath(dirent) {
return pathModule.relative(testDir, pathModule.join(dirent.path, dirent.name));
}
function assertDirents(dirents) {
assert.strictEqual(dirents.length, expected.length);
dirents.sort((a, b) => (getDirentPath(a) < getDirentPath(b) ? -1 : 1));
assert.deepStrictEqual(
dirents.map((dirent) => {
assert(dirent instanceof fs.Dirent);
return getDirentPath(dirent);
}),
expected
);
}
function processDirSync(dir) {
const dirents = [];
let dirent = dir.readSync();
while (dirent !== null) {
dirents.push(dirent);
dirent = dir.readSync();
}
assertDirents(dirents);
}
// Opendir read results sync
{
const dir = fs.opendirSync(testDir, { recursive: true });
processDirSync(dir);
dir.closeSync();
}
{
fs.opendir(testDir, { recursive: true }, common.mustSucceed((dir) => {
processDirSync(dir);
dir.close(common.mustSucceed());
}));
}
// Opendir read result using callback
function processDirCb(dir, cb) {
const acc = [];
function _process(dir, acc, cb) {
dir.read((err, dirent) => {
if (err) {
return cb(err);
}
if (dirent !== null) {
acc.push(dirent);
_process(dir, acc, cb);
} else {
cb(null, acc);
}
});
}
_process(dir, acc, cb);
}
{
const dir = fs.opendirSync(testDir, { recursive: true });
processDirCb(dir, common.mustSucceed((dirents) => {
assertDirents(dirents);
dir.close(common.mustSucceed());
}));
}
{
fs.opendir(testDir, { recursive: true }, common.mustSucceed((dir) => {
processDirCb(dir, common.mustSucceed((dirents) => {
assertDirents(dirents);
dir.close(common.mustSucceed());
}));
}));
}
// Opendir read result using AsyncIterator
{
async function test() {
const dir = await fsPromises.opendir(testDir, { recursive: true });
const dirents = [];
for await (const dirent of dir) {
dirents.push(dirent);
}
assertDirents(dirents);
}
test().then(common.mustCall());
}
// Issue https://github.com/nodejs/node/issues/48820 highlights that
// opendir recursive does not properly handle the buffer size option.
// This test asserts that the buffer size option is respected.
{
const dir = fs.opendirSync(testDir, { bufferSize: 1, recursive: true });
processDirSync(dir);
dir.closeSync();
}
{
fs.opendir(testDir, { recursive: true, bufferSize: 1 }, common.mustSucceed((dir) => {
processDirCb(dir, common.mustSucceed((dirents) => {
assertDirents(dirents);
dir.close(common.mustSucceed());
}));
}));
}

View File

@@ -0,0 +1,198 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const pathModule = require('path');
const tmpdir = require('../common/tmpdir');
const readdirDir = tmpdir.path;
const fileStructure = [
[ 'a', [ 'a', 'foo', 'bar' ] ],
[ 'b', [ 'foo', 'bar' ] ],
[ 'c', [ 'foo', 'bar' ] ],
[ 'd', [ 'foo', 'bar' ] ],
[ 'e', [ 'foo', 'bar' ] ],
[ 'f', [ 'foo', 'bar' ] ],
[ 'g', [ 'foo', 'bar' ] ],
[ 'h', [ 'foo', 'bar' ] ],
[ 'i', [ 'foo', 'bar' ] ],
[ 'j', [ 'foo', 'bar' ] ],
[ 'k', [ 'foo', 'bar' ] ],
[ 'l', [ 'foo', 'bar' ] ],
[ 'm', [ 'foo', 'bar' ] ],
[ 'n', [ 'foo', 'bar' ] ],
[ 'o', [ 'foo', 'bar' ] ],
[ 'p', [ 'foo', 'bar' ] ],
[ 'q', [ 'foo', 'bar' ] ],
[ 'r', [ 'foo', 'bar' ] ],
[ 's', [ 'foo', 'bar' ] ],
[ 't', [ 'foo', 'bar' ] ],
[ 'u', [ 'foo', 'bar' ] ],
[ 'v', [ 'foo', 'bar' ] ],
[ 'w', [ 'foo', 'bar' ] ],
[ 'x', [ 'foo', 'bar' ] ],
[ 'y', [ 'foo', 'bar' ] ],
[ 'z', [ 'foo', 'bar' ] ],
[ 'aa', [ 'foo', 'bar' ] ],
[ 'bb', [ 'foo', 'bar' ] ],
[ 'cc', [ 'foo', 'bar' ] ],
[ 'dd', [ 'foo', 'bar' ] ],
[ 'ee', [ 'foo', 'bar' ] ],
[ 'ff', [ 'foo', 'bar' ] ],
[ 'gg', [ 'foo', 'bar' ] ],
[ 'hh', [ 'foo', 'bar' ] ],
[ 'ii', [ 'foo', 'bar' ] ],
[ 'jj', [ 'foo', 'bar' ] ],
[ 'kk', [ 'foo', 'bar' ] ],
[ 'll', [ 'foo', 'bar' ] ],
[ 'mm', [ 'foo', 'bar' ] ],
[ 'nn', [ 'foo', 'bar' ] ],
[ 'oo', [ 'foo', 'bar' ] ],
[ 'pp', [ 'foo', 'bar' ] ],
[ 'qq', [ 'foo', 'bar' ] ],
[ 'rr', [ 'foo', 'bar' ] ],
[ 'ss', [ 'foo', 'bar' ] ],
[ 'tt', [ 'foo', 'bar' ] ],
[ 'uu', [ 'foo', 'bar' ] ],
[ 'vv', [ 'foo', 'bar' ] ],
[ 'ww', [ 'foo', 'bar' ] ],
[ 'xx', [ 'foo', 'bar' ] ],
[ 'yy', [ 'foo', 'bar' ] ],
[ 'zz', [ 'foo', 'bar' ] ],
[ 'abc', [ ['def', [ 'foo', 'bar' ] ], ['ghi', [ 'foo', 'bar' ] ] ] ],
];
function createFiles(path, fileStructure) {
for (const fileOrDir of fileStructure) {
if (typeof fileOrDir === 'string') {
fs.writeFileSync(pathModule.join(path, fileOrDir), '');
} else {
const dirPath = pathModule.join(path, fileOrDir[0]);
fs.mkdirSync(dirPath);
createFiles(dirPath, fileOrDir[1]);
}
}
}
// Make sure tmp directory is clean
tmpdir.refresh();
createFiles(readdirDir, fileStructure);
const symlinksRootPath = pathModule.join(readdirDir, 'symlinks');
const symlinkTargetFile = pathModule.join(symlinksRootPath, 'symlink-target-file');
const symlinkTargetDir = pathModule.join(symlinksRootPath, 'symlink-target-dir');
fs.mkdirSync(symlinksRootPath);
fs.writeFileSync(symlinkTargetFile, '');
fs.mkdirSync(symlinkTargetDir);
fs.symlinkSync(symlinkTargetFile, pathModule.join(symlinksRootPath, 'symlink-src-file'));
fs.symlinkSync(symlinkTargetDir, pathModule.join(symlinksRootPath, 'symlink-src-dir'));
const expected = [
'a', 'a/a', 'a/bar', 'a/foo', 'aa', 'aa/bar', 'aa/foo',
'abc', 'abc/def', 'abc/def/bar', 'abc/def/foo', 'abc/ghi', 'abc/ghi/bar', 'abc/ghi/foo',
'b', 'b/bar', 'b/foo', 'bb', 'bb/bar', 'bb/foo',
'c', 'c/bar', 'c/foo', 'cc', 'cc/bar', 'cc/foo',
'd', 'd/bar', 'd/foo', 'dd', 'dd/bar', 'dd/foo',
'e', 'e/bar', 'e/foo', 'ee', 'ee/bar', 'ee/foo',
'f', 'f/bar', 'f/foo', 'ff', 'ff/bar', 'ff/foo',
'g', 'g/bar', 'g/foo', 'gg', 'gg/bar', 'gg/foo',
'h', 'h/bar', 'h/foo', 'hh', 'hh/bar', 'hh/foo',
'i', 'i/bar', 'i/foo', 'ii', 'ii/bar', 'ii/foo',
'j', 'j/bar', 'j/foo', 'jj', 'jj/bar', 'jj/foo',
'k', 'k/bar', 'k/foo', 'kk', 'kk/bar', 'kk/foo',
'l', 'l/bar', 'l/foo', 'll', 'll/bar', 'll/foo',
'm', 'm/bar', 'm/foo', 'mm', 'mm/bar', 'mm/foo',
'n', 'n/bar', 'n/foo', 'nn', 'nn/bar', 'nn/foo',
'o', 'o/bar', 'o/foo', 'oo', 'oo/bar', 'oo/foo',
'p', 'p/bar', 'p/foo', 'pp', 'pp/bar', 'pp/foo',
'q', 'q/bar', 'q/foo', 'qq', 'qq/bar', 'qq/foo',
'r', 'r/bar', 'r/foo', 'rr', 'rr/bar', 'rr/foo',
's', 's/bar', 's/foo', 'ss', 'ss/bar', 'ss/foo',
'symlinks', 'symlinks/symlink-src-dir', 'symlinks/symlink-src-file',
'symlinks/symlink-target-dir', 'symlinks/symlink-target-file',
't', 't/bar', 't/foo', 'tt', 'tt/bar', 'tt/foo',
'u', 'u/bar', 'u/foo', 'uu', 'uu/bar', 'uu/foo',
'v', 'v/bar', 'v/foo', 'vv', 'vv/bar', 'vv/foo',
'w', 'w/bar', 'w/foo', 'ww', 'ww/bar', 'ww/foo',
'x', 'x/bar', 'x/foo', 'xx', 'xx/bar', 'xx/foo',
'y', 'y/bar', 'y/foo', 'yy', 'yy/bar', 'yy/foo',
'z', 'z/bar', 'z/foo', 'zz', 'zz/bar', 'zz/foo',
];
// Normalize paths once for non POSIX platforms
for (let i = 0; i < expected.length; i++) {
expected[i] = pathModule.normalize(expected[i]);
}
function getDirentPath(dirent) {
return pathModule.relative(readdirDir, pathModule.join(dirent.path, dirent.name));
}
function assertDirents(dirents) {
assert.strictEqual(dirents.length, expected.length);
dirents.sort((a, b) => (getDirentPath(a) < getDirentPath(b) ? -1 : 1));
assert.deepStrictEqual(
dirents.map((dirent) => {
assert(dirent instanceof fs.Dirent);
assert.notStrictEqual(dirent.name, undefined);
return getDirentPath(dirent);
}),
expected
);
}
// readdirSync
// readdirSync { recursive }
{
const result = fs.readdirSync(readdirDir, { recursive: true });
assert.deepStrictEqual(result.sort(), expected);
}
// readdirSync { recursive, withFileTypes }
{
const result = fs.readdirSync(readdirDir, { recursive: true, withFileTypes: true });
assertDirents(result);
}
// readdir
// readdir { recursive } callback
{
fs.readdir(readdirDir, { recursive: true },
common.mustSucceed((result) => {
assert.deepStrictEqual(result.sort(), expected);
}));
}
// Readdir { recursive, withFileTypes } callback
{
fs.readdir(readdirDir, { recursive: true, withFileTypes: true },
common.mustSucceed((result) => {
assertDirents(result);
}));
}
// fs.promises.readdir
// fs.promises.readdir { recursive }
{
async function test() {
const result = await fs.promises.readdir(readdirDir, { recursive: true });
assert.deepStrictEqual(result.sort(), expected);
}
test().then(common.mustCall());
}
// fs.promises.readdir { recursive, withFileTypes }
{
async function test() {
const result = await fs.promises.readdir(readdirDir, { recursive: true, withFileTypes: true });
assertDirents(result);
}
test().then(common.mustCall());
}

View File

@@ -0,0 +1,50 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');
const server = http.createServer(common.mustCall((req, res) => {
if (req.url === '/first') {
res.end('ok');
return;
}
setTimeout(() => {
res.end('ok');
}, common.platformTimeout(500));
}, 2));
server.keepAliveTimeout = common.platformTimeout(200);
const agent = new http.Agent({
keepAlive: true,
maxSockets: 1
});
function request(path, callback) {
const port = server.address().port;
const req = http.request({ agent, path, port }, common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200);
res.setEncoding('utf8');
let result = '';
res.on('data', (chunk) => {
result += chunk;
});
res.on('end', common.mustCall(() => {
assert.strictEqual(result, 'ok');
callback();
}));
}));
req.end();
}
server.listen(0, common.mustCall(() => {
request('/first', () => {
request('/second', () => {
server.close();
});
});
}));

View File

@@ -0,0 +1,63 @@
// 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 child = require('child_process');
const fixtures = require('../common/fixtures');
if (!common.isMainThread)
common.skip('process.chdir is not available in Workers');
if (process.env.TEST_INIT) {
return process.stdout.write('Loaded successfully!');
}
process.env.TEST_INIT = 1;
function test(file, expected) {
child.exec(...common.escapePOSIXShell`"${process.execPath}" "${file}"`, common.mustSucceed((out) => {
assert.strictEqual(out, expected, `'node ${file}' failed!`);
}));
}
{
// Change CWD as we do this test so it's not dependent on current CWD
// being in the test folder
process.chdir(__dirname);
test('test-init', 'Loaded successfully!');
test('test-init.js', 'Loaded successfully!');
}
{
// test-init-index is in fixtures dir as requested by ry, so go there
process.chdir(fixtures.path());
test('test-init-index', 'Loaded successfully!');
}
{
// Ensures that `node fs` does not mistakenly load the native 'fs' module
// instead of the desired file and that the fs module loads as
// expected in node
process.chdir(fixtures.path('test-init-native'));
test('fs', 'fs loaded successfully');
}

View File

@@ -0,0 +1,99 @@
// 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');
// Ref: https://github.com/nodejs/node-v0.x-archive/issues/5504
//
// This test only fails with CentOS 6.3 using kernel version 2.6.32.
// On other Linuxes and macOS, the `read` call gets an ECONNRESET in
// that case. On sunos, the `write` call fails with EPIPE.
//
// However, old CentOS will occasionally send an EOF instead of an
// ECONNRESET or EPIPE when the client has been destroyed abruptly.
//
// Make sure we don't keep trying to write or read more in that case.
switch (process.argv[2]) {
case 'server': return server();
case 'client': return client();
case undefined: return parent();
default: throw new Error('invalid');
}
function server() {
const net = require('net');
const content = Buffer.alloc(64 * 1024 * 1024, '#');
net.createServer(function(socket) {
this.close();
socket.on('end', function() {
console.error('end');
});
socket.on('_socketEnd', function() {
console.error('_socketEnd');
});
socket.write(content);
}).listen(common.PORT, common.localhostIPv4, function() {
console.log('listening');
});
}
function client() {
const net = require('net');
const client = net.connect({
host: common.localhostIPv4,
port: common.PORT
}, function() {
client.destroy();
});
}
function parent() {
const spawn = require('child_process').spawn;
const node = process.execPath;
const s = spawn(node, [__filename, 'server'], {
env: Object.assign(process.env, {
NODE_DEBUG: 'net'
})
});
wrap(s.stderr, process.stderr, 'SERVER 2>');
wrap(s.stdout, process.stdout, 'SERVER 1>');
s.on('exit', common.mustCall());
s.stdout.once('data', common.mustCall(function() {
const c = spawn(node, [__filename, 'client']);
wrap(c.stderr, process.stderr, 'CLIENT 2>');
wrap(c.stdout, process.stdout, 'CLIENT 1>');
c.on('exit', common.mustCall());
}));
function wrap(inp, out, w) {
inp.setEncoding('utf8');
inp.on('data', function(chunk) {
chunk = chunk.trim();
if (!chunk) return;
out.write(`${w}${chunk.split('\n').join(`\n${w}`)}\n`);
});
}
}

View File

@@ -0,0 +1,68 @@
// 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';
// Verify that connect reqs are properly cleaned up.
const common = require('../common');
const assert = require('assert');
const net = require('net');
const ROUNDS = 5;
const ATTEMPTS_PER_ROUND = 50;
let rounds = 1;
let reqs = 0;
let port;
const server = net.createServer().listen(0, common.mustCall(() => {
port = server.address().port;
server.close(common.mustCall(pummel));
}));
function pummel() {
let pending;
for (pending = 0; pending < ATTEMPTS_PER_ROUND; pending++) {
net.createConnection({ port, autoSelectFamily: false }).on('error', function(error) {
// Family autoselection might be skipped if only a single address is returned by DNS.
const actualError = Array.isArray(error.errors) ? error.errors[0] : error;
console.log('pending', pending, 'rounds', rounds);
assert.strictEqual(actualError.code, 'ECONNREFUSED');
if (--pending > 0) return;
if (rounds === ROUNDS) return check();
rounds++;
pummel();
});
reqs++;
}
}
function check() {
setTimeout(common.mustCall(function() {
assert.strictEqual(process.getActiveResourcesInfo().filter(
(type) => type === 'TCPWRAP').length, 0);
}), 0);
}
process.on('exit', function() {
assert.strictEqual(rounds, ROUNDS);
assert.strictEqual(reqs, ROUNDS * ATTEMPTS_PER_ROUND);
});

View File

@@ -21,23 +21,23 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// Check that the calls to Integer::New() and Date::New() succeed and bail out
// if they don't.
// V8 returns an empty handle on stack overflow. Trying to set the empty handle
// as a property on an object results in a NULL pointer dereference in release
// builds and an assert in debug builds.
// https://github.com/nodejs/node-v0.x-archive/issues/4015
const net = require('net');
const assert = require('assert');
const { spawn } = require('child_process');
const N = 20;
let disconnectCount = 0;
const cp = spawn(process.execPath, [fixtures.path('test-fs-stat-sync-overflow.js')]);
const c = net.createConnection(common.PORT);
const stderr = [];
cp.stderr.on('data', (chunk) => stderr.push(chunk));
c.on('connect', common.mustNotCall('client should not have connected'));
cp.on('exit', common.mustCall(() => {
assert.match(Buffer.concat(stderr).toString('utf8'), /RangeError: Maximum call stack size exceeded/);
}));
c.on('error', common.mustCall((error) => {
// Family autoselection might be skipped if only a single address is returned by DNS.
const actualError = Array.isArray(error.errors) ? error.errors[0] : error;
assert.strictEqual(actualError.code, 'ECONNREFUSED');
}, N + 1));
c.on('close', common.mustCall(() => {
if (disconnectCount++ < N)
c.connect(common.PORT); // reconnect
}, N + 1));

View File

@@ -0,0 +1,75 @@
// 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 net module's server doesn't throw an error when handling
// responses that are either too long or too small (especially on Windows)
// https://github.com/nodejs/node-v0.x-archive/issues/1697
const net = require('net');
const cp = require('child_process');
if (process.argv[2] === 'server') {
// Server
const server = net.createServer(function(conn) {
conn.on('data', function(data) {
console.log(`server received ${data.length} bytes`);
});
conn.on('close', function() {
server.close();
});
});
server.listen(common.PORT, '127.0.0.1', function() {
console.log('Server running.');
});
} else {
// Client
const serverProcess = cp.spawn(process.execPath, [process.argv[1], 'server']);
serverProcess.stdout.pipe(process.stdout);
serverProcess.stderr.pipe(process.stdout);
serverProcess.stdout.once('data', function() {
const client = net.createConnection(common.PORT, '127.0.0.1');
client.on('connect', function() {
const alot = Buffer.allocUnsafe(1024);
const alittle = Buffer.allocUnsafe(1);
for (let i = 0; i < 100; i++) {
client.write(alot);
}
// Block the event loop for 1 second
const start = (new Date()).getTime();
while ((new Date()).getTime() < start + 1000);
client.write(alittle);
client.destroySoon();
});
});
}

View File

@@ -0,0 +1,113 @@
// 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');
// Test on IPv4 Server
{
const family = 'IPv4';
const server = net.createServer();
server.on('error', common.mustNotCall());
server
.listen(common.PORT + 1, common.localhostIPv4, common.mustCall(() => {
const address4 = server.address();
assert.strictEqual(address4.address, common.localhostIPv4);
assert.strictEqual(address4.port, common.PORT + 1);
assert.strictEqual(address4.family, family);
server.close();
}));
}
if (!common.hasIPv6) {
common.printSkipMessage('ipv6 part of test, no IPv6 support');
return;
}
const family6 = 'IPv6';
const anycast6 = '::';
// Test on IPv6 Server
{
const localhost = '::1';
const server = net.createServer();
server.on('error', common.mustNotCall());
server.listen(common.PORT + 2, localhost, common.mustCall(() => {
const address = server.address();
assert.strictEqual(address.address, localhost);
assert.strictEqual(address.port, common.PORT + 2);
assert.strictEqual(address.family, family6);
server.close();
}));
}
// Test without hostname or ip
{
const server = net.createServer();
server.on('error', common.mustNotCall());
// Specify the port number
server.listen(common.PORT + 3, common.mustCall(() => {
const address = server.address();
assert.strictEqual(address.address, anycast6);
assert.strictEqual(address.port, common.PORT + 3);
assert.strictEqual(address.family, family6);
server.close();
}));
}
// Test without hostname or port
{
const server = net.createServer();
server.on('error', common.mustNotCall());
// Don't specify the port number
server.listen(common.mustCall(() => {
const address = server.address();
assert.strictEqual(address.address, anycast6);
assert.strictEqual(address.family, family6);
server.close();
}));
}
// Test without hostname, but with a false-y port
{
const server = net.createServer();
server.on('error', common.mustNotCall());
// Specify a false-y port number
server.listen(0, common.mustCall(() => {
const address = server.address();
assert.strictEqual(address.address, anycast6);
assert.strictEqual(address.family, family6);
server.close();
}));
}

View File

@@ -0,0 +1,74 @@
// 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';
// We've experienced a regression where the module loader stats a bunch of
// directories on require() even if it's been called before. The require()
// should caching the request.
require('../common');
const fs = require('fs');
const assert = require('assert');
const { fixturesDir } = require('../common/fixtures');
let counter = 0;
// Switch out the two stat implementations so that they increase a counter
// each time they are called.
const _statSync = fs.statSync;
const _stat = fs.stat;
fs.statSync = function() {
counter++;
return _statSync.apply(this, arguments);
};
fs.stat = function() {
counter++;
return _stat.apply(this, arguments);
};
// Load the module 'a' and 'http' once. It should become cached.
require(`${fixturesDir}/a`);
require('../fixtures/a.js');
require('./../fixtures/a.js');
require('http');
console.log(`counterBefore = ${counter}`);
const counterBefore = counter;
// Now load the module a bunch of times with equivalent paths.
// stat should not be called.
for (let i = 0; i < 100; i++) {
require(`${fixturesDir}/a`);
require('../fixtures/a.js');
require('./../fixtures/a.js');
}
// Do the same with a built-in module
for (let i = 0; i < 100; i++) {
require('http');
}
console.log(`counterAfter = ${counter}`);
const counterAfter = counter;
assert.strictEqual(counterBefore, counterAfter);

View File

@@ -0,0 +1,70 @@
'use strict';
const common = require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the snapshot support in single executable applications.
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndExitWithoutError,
} = require('../common/child_process');
const assert = require('assert');
const fixtures = require('../common/fixtures');
tmpdir.refresh();
if (!tmpdir.hasEnoughSpace(120 * 1024 * 1024)) {
common.skip('Not enough disk space');
}
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
{
tmpdir.refresh();
copyFileSync(fixtures.path('sea', 'get-asset-raw.js'), tmpdir.resolve('sea.js'));
copyFileSync(fixtures.path('person.jpg'), tmpdir.resolve('person.jpg'));
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"assets": {
"person.jpg": "person.jpg"
}
}
`, 'utf8');
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
cwd: tmpdir.path
});
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndExitWithoutError(
outputFile,
{
env: {
...process.env,
NODE_DEBUG_NATIVE: 'SEA',
__TEST_PERSON_JPG: fixtures.path('person.jpg'),
}
},
);
}

View File

@@ -0,0 +1,129 @@
'use strict';
const common = require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the snapshot support in single executable applications.
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndAssert,
spawnSyncAndExit,
spawnSyncAndExitWithoutError,
} = require('../common/child_process');
const assert = require('assert');
const fixtures = require('../common/fixtures');
tmpdir.refresh();
if (!tmpdir.hasEnoughSpace(120 * 1024 * 1024)) {
common.skip('Not enough disk space');
}
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
{
tmpdir.refresh();
copyFileSync(fixtures.path('sea', 'get-asset.js'), tmpdir.resolve('sea.js'));
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"assets": "invalid"
}
`);
spawnSyncAndExit(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path
},
{
status: 1,
signal: null,
stderr: /"assets" field of sea-config\.json is not a map of strings/
});
}
{
tmpdir.refresh();
copyFileSync(fixtures.path('sea', 'get-asset.js'), tmpdir.resolve('sea.js'));
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"assets": {
"nonexistent": "nonexistent.txt"
}
}
`);
spawnSyncAndExit(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path
},
{
status: 1,
signal: null,
stderr: /Cannot read asset nonexistent\.txt: no such file or directory/
});
}
{
tmpdir.refresh();
copyFileSync(fixtures.path('sea', 'get-asset.js'), tmpdir.resolve('sea.js'));
copyFileSync(fixtures.utf8TestTextPath, tmpdir.resolve('utf8_test_text.txt'));
copyFileSync(fixtures.path('person.jpg'), tmpdir.resolve('person.jpg'));
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"assets": {
"utf8_test_text.txt": "utf8_test_text.txt",
"person.jpg": "person.jpg"
}
}
`, 'utf8');
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
cwd: tmpdir.path
});
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
{
env: {
...process.env,
NODE_DEBUG_NATIVE: 'SEA',
__TEST_PERSON_JPG: fixtures.path('person.jpg'),
__TEST_UTF8_TEXT_PATH: fixtures.path('utf8_test_text.txt'),
}
},
{
trim: true,
stdout: fixtures.utf8TestText,
}
);
}

View File

@@ -0,0 +1,67 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the creation of a single executable application which has the
// experimental SEA warning disabled.
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process');
const { join } = require('path');
const assert = require('assert');
const inputFile = fixtures.path('sea.js');
const requirableFile = tmpdir.resolve('requirable.js');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
tmpdir.refresh();
writeFileSync(requirableFile, `
module.exports = {
hello: 'world',
};
`);
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"disableExperimentalSEAWarning": true
}
`);
// Copy input to working directory
copyFileSync(inputFile, tmpdir.resolve('sea.js'));
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{ cwd: tmpdir.path });
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
[ '-a', '--b=c', 'd' ],
{
env: {
COMMON_DIRECTORY: join(__dirname, '..', 'common'),
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
}
},
{
stdout: 'Hello, world! 😊\n'
});

View File

@@ -0,0 +1,63 @@
'use strict';
const common = require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the creation of a single executable application with an empty
// script.
const tmpdir = require('../common/tmpdir');
const { writeFileSync, existsSync } = require('fs');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
const assert = require('assert');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
tmpdir.refresh();
writeFileSync(tmpdir.resolve('empty.js'), '', 'utf-8');
writeFileSync(configFile, `
{
"main": "empty.js",
"output": "sea-prep.blob"
}
`);
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{ cwd: tmpdir.path });
assert(existsSync(seaPrepBlob));
// Verify the workflow.
try {
generateSEA(outputFile, process.execPath, seaPrepBlob, true);
} catch (e) {
if (/Cannot copy/.test(e.message)) {
common.skip(e.message);
} else if (common.isWindows) {
if (/Cannot sign/.test(e.message) || /Cannot find signtool/.test(e.message)) {
common.skip(e.message);
}
}
throw e;
}
spawnSyncAndExitWithoutError(
outputFile,
{
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
}
});

View File

@@ -0,0 +1,78 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests "useCodeCache" is ignored when "useSnapshot" is true.
const tmpdir = require('../common/tmpdir');
const { writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndAssert,
} = require('../common/child_process');
const { join } = require('path');
const assert = require('assert');
const configFile = join(tmpdir.path, 'sea-config.json');
const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob');
const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea');
{
tmpdir.refresh();
const code = `
const {
setDeserializeMainFunction,
} = require('v8').startupSnapshot;
setDeserializeMainFunction(() => {
console.log('Hello from snapshot');
});
`;
writeFileSync(join(tmpdir.path, 'snapshot.js'), code, 'utf-8');
writeFileSync(configFile, `
{
"main": "snapshot.js",
"output": "sea-prep.blob",
"useSnapshot": true,
"useCodeCache": true
}
`);
spawnSyncAndAssert(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path,
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
},
{
stderr: /"useCodeCache" is redundant when "useSnapshot" is true/
}
);
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
{
env: {
NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT',
...process.env,
}
}, {
stdout: 'Hello from snapshot',
trim: true,
});
}

View File

@@ -0,0 +1,80 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the snapshot support in single executable applications.
const tmpdir = require('../common/tmpdir');
const { writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndAssert, spawnSyncAndExitWithoutError,
} = require('../common/child_process');
const assert = require('assert');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
{
tmpdir.refresh();
// FIXME(joyeecheung): currently `worker_threads` cannot be loaded during the
// snapshot building process because internal/worker.js is accessing isMainThread at
// the top level (and there are maybe more code that access these at the top-level),
// and have to be loaded in the deserialized snapshot main function.
// Change these states to be accessed on-demand.
const code = `
const {
setDeserializeMainFunction,
} = require('v8').startupSnapshot;
setDeserializeMainFunction(() => {
const { Worker } = require('worker_threads');
new Worker("console.log('Hello from Worker')", { eval: true });
});
`;
writeFileSync(tmpdir.resolve('snapshot.js'), code, 'utf-8');
writeFileSync(configFile, `
{
"main": "snapshot.js",
"output": "sea-prep.blob",
"useSnapshot": true
}
`);
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path,
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
});
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
{
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
}
},
{
trim: true,
stdout: 'Hello from Worker'
}
);
}

View File

@@ -0,0 +1,108 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the snapshot support in single executable applications.
const tmpdir = require('../common/tmpdir');
const { writeFileSync, existsSync } = require('fs');
const {
spawnSyncAndAssert,
spawnSyncAndExit,
} = require('../common/child_process');
const assert = require('assert');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
{
tmpdir.refresh();
writeFileSync(tmpdir.resolve('snapshot.js'), '', 'utf-8');
writeFileSync(configFile, `
{
"main": "snapshot.js",
"output": "sea-prep.blob",
"useSnapshot": true
}
`);
spawnSyncAndExit(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path
},
{
status: 1,
signal: null,
stderr: /snapshot\.js does not invoke v8\.startupSnapshot\.setDeserializeMainFunction\(\)/
});
}
{
tmpdir.refresh();
const code = `
const {
setDeserializeMainFunction,
} = require('v8').startupSnapshot;
setDeserializeMainFunction(() => {
console.log('Hello from snapshot');
});
`;
writeFileSync(tmpdir.resolve('snapshot.js'), code, 'utf-8');
writeFileSync(configFile, `
{
"main": "snapshot.js",
"output": "sea-prep.blob",
"useSnapshot": true
}
`);
spawnSyncAndAssert(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path,
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
},
{
stderr: /Single executable application is an experimental feature/
});
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
{
env: {
NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT',
...process.env,
}
},
{
trim: true,
stdout: 'Hello from snapshot',
stderr(output) {
assert.doesNotMatch(
output,
/Single executable application is an experimental feature/);
}
}
);
}

View File

@@ -0,0 +1,73 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the creation of a single executable application which uses the
// V8 code cache.
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process');
const { join } = require('path');
const assert = require('assert');
const inputFile = fixtures.path('sea.js');
const requirableFile = tmpdir.resolve('requirable.js');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
tmpdir.refresh();
writeFileSync(requirableFile, `
module.exports = {
hello: 'world',
};
`);
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"useCodeCache": true
}
`);
// Copy input to working directory
copyFileSync(inputFile, tmpdir.resolve('sea.js'));
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{
cwd: tmpdir.path,
env: {
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
},
});
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
[ '-a', '--b=c', 'd' ],
{
env: {
COMMON_DIRECTORY: join(__dirname, '..', 'common'),
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
}
},
{
stdout: 'Hello, world! 😊\n'
});

View File

@@ -0,0 +1,66 @@
'use strict';
require('../common');
const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the creation of a single executable application.
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process');
const { join } = require('path');
const assert = require('assert');
const inputFile = fixtures.path('sea.js');
const requirableFile = tmpdir.resolve('requirable.js');
const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
tmpdir.refresh();
writeFileSync(requirableFile, `
module.exports = {
hello: 'world',
};
`);
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"disableExperimentalSEAWarning": false
}
`);
// Copy input to working directory
copyFileSync(inputFile, tmpdir.resolve('sea.js'));
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{ cwd: tmpdir.path });
assert(existsSync(seaPrepBlob));
generateSEA(outputFile, process.execPath, seaPrepBlob);
spawnSyncAndAssert(
outputFile,
[ '-a', '--b=c', 'd' ],
{
env: {
COMMON_DIRECTORY: join(__dirname, '..', 'common'),
NODE_DEBUG_NATIVE: 'SEA',
...process.env,
}
},
{
stdout: 'Hello, world! 😊\n'
});

View File

@@ -0,0 +1,110 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
if (!common.opensslCli)
common.skip('missing openssl cli');
const assert = require('assert');
const tls = require('tls');
const net = require('net');
const { spawn } = require('child_process');
const CIPHERS = 'PSK+HIGH';
const KEY = 'd731ef57be09e5204f0b205b60627028';
const IDENTITY = 'Client_identity'; // Hardcoded by `openssl s_server`
const useIPv4 = !common.hasIPv6;
const server = spawn(common.opensslCli, [
's_server',
'-accept', common.PORT,
'-cipher', CIPHERS,
'-psk', KEY,
'-psk_hint', IDENTITY,
'-nocert',
'-rev',
...(useIPv4 ? ['-4'] : []),
], { encoding: 'utf8' });
let serverErr = '';
let serverOut = '';
server.stderr.on('data', (data) => serverErr += data);
server.stdout.on('data', (data) => serverOut += data);
server.on('error', common.mustNotCall());
server.on('exit', (code, signal) => {
// Server is expected to be terminated by cleanUp().
assert.strictEqual(code, null,
`'${server.spawnfile} ${server.spawnargs.join(' ')}' unexpected exited with output:\n${serverOut}\n${serverErr}`);
assert.strictEqual(signal, 'SIGTERM');
});
const cleanUp = (err) => {
clearTimeout(timeout);
if (err)
console.log('Failed:', err);
server.kill();
process.exitCode = err ? 1 : 0;
};
const timeout = setTimeout(() => cleanUp('Timed out'), 5000);
function waitForPort(port, cb) {
const socket = net.connect(common.PORT, () => {
socket.on('data', () => {});
socket.end();
socket.on('end', cb);
});
socket.on('error', (e) => {
if (e.code === 'ENOENT' || e.code === 'ECONNREFUSED') {
setTimeout(() => waitForPort(port, cb), 1000);
} else {
cb(e);
}
});
}
waitForPort(common.PORT, common.mustCall((err) => {
if (err) {
cleanUp(err);
return;
}
const message = 'hello';
const reverse = message.split('').reverse().join('');
runClient(message, common.mustCall((err, data) => {
try {
if (!err) assert.strictEqual(data.trim(), reverse);
} finally {
cleanUp(err);
}
}));
}));
function runClient(message, cb) {
const s = tls.connect(common.PORT, {
ciphers: CIPHERS,
checkServerIdentity: () => {},
pskCallback(hint) {
// 'hint' will be null in TLS1.3.
if (hint === null || hint === IDENTITY) {
return {
identity: IDENTITY,
psk: Buffer.from(KEY, 'hex')
};
}
}
});
s.on('secureConnect', common.mustCall(() => {
let data = '';
s.on('data', common.mustCallAtLeast((d) => {
data += d;
}));
s.on('end', common.mustCall(() => {
cb(null, data);
}));
s.end(message);
}));
s.on('error', (e) => {
cb(e);
});
}

View File

@@ -0,0 +1,186 @@
// 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');
if (!common.opensslCli)
common.skip('node compiled without OpenSSL CLI.');
if (!common.hasCrypto)
common.skip('missing crypto');
if (common.isWindows)
common.skip('test does not work on Windows'); // ...but it should!
const net = require('net');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const tls = require('tls');
const spawn = require('child_process').spawn;
const useIPv4 = !common.hasIPv6;
test1();
// simple/test-tls-securepair-client
function test1() {
test('keys/rsa_private.pem', 'keys/rsa_cert.crt', null, test2);
}
// simple/test-tls-ext-key-usage
function test2() {
function check(pair) {
// "TLS Web Client Authentication"
assert.strictEqual(pair.cleartext.getPeerCertificate().ext_key_usage.length,
1);
assert.strictEqual(pair.cleartext.getPeerCertificate().ext_key_usage[0],
'1.3.6.1.5.5.7.3.2');
}
test('keys/agent4-key.pem', 'keys/agent4-cert.pem', check);
}
function test(keyPath, certPath, check, next) {
const key = fixtures.readSync(keyPath).toString();
const cert = fixtures.readSync(certPath).toString();
const server = spawn(common.opensslCli, ['s_server',
'-accept', 0,
'-cert', fixtures.path(certPath),
'-key', fixtures.path(keyPath),
...(useIPv4 ? ['-4'] : []),
]);
server.stdout.pipe(process.stdout);
server.stderr.pipe(process.stdout);
let state = 'WAIT-ACCEPT';
let serverStdoutBuffer = '';
server.stdout.setEncoding('utf8');
server.stdout.on('data', function(s) {
serverStdoutBuffer += s;
console.log(state);
switch (state) {
case 'WAIT-ACCEPT': {
const matches = serverStdoutBuffer.match(/ACCEPT .*?:(\d+)/);
if (matches) {
const port = matches[1];
state = 'WAIT-HELLO';
startClient(port);
}
break;
}
case 'WAIT-HELLO':
if (/hello/.test(serverStdoutBuffer)) {
// End the current SSL connection and exit.
// See s_server(1ssl).
server.stdin.write('Q');
state = 'WAIT-SERVER-CLOSE';
}
break;
default:
break;
}
});
const timeout = setTimeout(function() {
server.kill();
process.exit(1);
}, 5000);
let gotWriteCallback = false;
let serverExitCode = -1;
server.on('exit', function(code) {
serverExitCode = code;
clearTimeout(timeout);
if (next) next();
});
function startClient(port) {
const s = new net.Stream();
const sslcontext = tls.createSecureContext({ key, cert });
sslcontext.context.setCiphers('RC4-SHA:AES128-SHA:AES256-SHA');
const pair = tls.createSecurePair(sslcontext, false);
assert.ok(pair.encrypted.writable);
assert.ok(pair.cleartext.writable);
pair.encrypted.pipe(s);
s.pipe(pair.encrypted);
s.connect(port);
s.on('connect', function() {
console.log('client connected');
setTimeout(function() {
pair.cleartext.write('hello\r\n', function() {
gotWriteCallback = true;
});
}, 500);
});
pair.on('secure', function() {
console.log('client: connected+secure!');
console.log('client pair.cleartext.getPeerCertificate(): %j',
pair.cleartext.getPeerCertificate());
console.log('client pair.cleartext.getCipher(): %j',
pair.cleartext.getCipher());
if (check) check(pair);
});
pair.cleartext.on('data', function(d) {
console.log('cleartext: %s', d.toString());
});
s.on('close', function() {
console.log('client close');
});
pair.encrypted.on('error', function(err) {
console.log(`encrypted error: ${err}`);
});
s.on('error', function(err) {
console.log(`socket error: ${err}`);
});
pair.on('error', function(err) {
console.log(`secure error: ${err}`);
});
}
process.on('exit', function() {
assert.strictEqual(serverExitCode, 0);
assert.strictEqual(state, 'WAIT-SERVER-CLOSE');
assert.ok(gotWriteCallback);
});
}