diff --git a/README.md b/README.md index 7dbe826..5fd5831 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # string_decoder -***Node-core v7.0.0 string_decoder for userland*** +***Node-core v8.9.4 string_decoder for userland*** [![NPM](https://nodei.co/npm/string_decoder.png?downloads=true&downloadRank=true)](https://nodei.co/npm/string_decoder/) @@ -15,7 +15,7 @@ npm install --save string_decoder This package is a mirror of the string_decoder implementation in Node-core. -Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v7.8.0/docs/api/). +Full documentation may be found on the [Node.js website](https://nodejs.org/dist/v8.9.4/docs/api/). As of version 1.0.0 **string_decoder** uses semantic versioning. diff --git a/build/build.js b/build/build.js index f52fd28..52dd130 100755 --- a/build/build.js +++ b/build/build.js @@ -16,7 +16,7 @@ const hyperquest = require('hyperquest') , nodeVersionRegexString = '\\d+\\.\\d+\\.\\d+' , usageVersionRegex = RegExp('^' + nodeVersionRegexString + '$') , readmeVersionRegex = - RegExp('((?:Node-core )|(?:https\:\/\/nodejs\.org\/dist\/)v)' + nodeVersionRegexString, 'g') + RegExp('((?:(?:Node-core )|(?:https\:\/\/nodejs\.org\/dist\/))v)' + nodeVersionRegexString, 'g') , readmePath = path.join(__dirname, '..', 'README.md') , files = require('./files') @@ -49,21 +49,37 @@ function processFile (inputLoc, out, replacements) { var arg2 = replacement[1] if (typeof arg2 === 'function') arg2 = arg2.bind(data) + if (arg2 === undefined) { + console.error('missing second arg for file', inputLoc, replacement) + throw new Error('missing second arg in replacement') + } data = data.replace(regexp, arg2) }) if (inputLoc.slice(-3) === '.js') { - const transformed = babel.transform(data, { - plugins: [ - 'transform-es2015-parameters', - 'transform-es2015-arrow-functions', - 'transform-es2015-block-scoping', - 'transform-es2015-template-literals', - 'transform-es2015-shorthand-properties', - 'transform-es2015-for-of', - 'transform-es2015-destructuring' - ] - }) - data = transformed.code + try { + const transformed = babel.transform(data, { + plugins: [ + 'transform-es2015-parameters', + 'transform-es2015-arrow-functions', + 'transform-es2015-block-scoping', + 'transform-es2015-template-literals', + 'transform-es2015-shorthand-properties', + 'transform-es2015-for-of', + ['transform-es2015-classes', { loose: true }], + 'transform-es2015-destructuring', + 'transform-es2015-computed-properties', + 'transform-es2015-spread' + ] + }) + data = transformed.code + } catch (err) { + fs.writeFile(out + '.errored.js', data, encoding, function () { + console.log('Wrote errored', out) + + throw err + }) + return + } } fs.writeFile(out, data, encoding, function (err) { if (err) throw err @@ -112,7 +128,6 @@ pump( throw err } - //-------------------------------------------------------------------- // Grab & process files in ../lib/ @@ -137,15 +152,23 @@ pump( //-------------------------------------------------------------------- // Grab the nodejs/node test/common.js - processFile( - testsrcurl.replace(/parallel\/$/, 'common.js') - , path.join(testourroot, '../common.js') - , testReplace['common.js'] - ) + glob(path.join(src, 'test/common/*'), function (err, list) { + if (err) { + throw err + } + + list.forEach(function (file) { + file = path.basename(file) + processFile( + path.join(testsrcurl.replace(/parallel\/$/, 'common/'), file) + , path.join(testourroot.replace('parallel', 'common'), file) + , testReplace['common.js'] + ) + }) + }) //-------------------------------------------------------------------- // Update Node version in README - processFile(readmePath, readmePath, [ [readmeVersionRegex, "$1" + nodeVersion] ]) diff --git a/build/common-replacements.js b/build/common-replacements.js new file mode 100644 index 0000000..e17f5d7 --- /dev/null +++ b/build/common-replacements.js @@ -0,0 +1,59 @@ +module.exports.altForEachImplReplacement = [ + /$/ + , '\nfunction forEach (xs, f) {\n' + + ' for (var i = 0, l = xs.length; i < l; i++) {\n' + + ' f(xs[i], i);\n' + + ' }\n' + + '}\n' +] + +module.exports.altForEachUseReplacement = [ + /(\W)([\w\.\(\),\[\] ']+)(\.forEach\()/gm + , '$1forEach($2, ' +] + +module.exports.specialForEachReplacment = [ + /(\W)(\[(?:\d\,\s)+\d\])(\.forEach\()/gm + , '$1forEach($2, ' +] + +module.exports.altIndexOfImplReplacement = [ + /$/ + , '\nfunction indexOf (xs, x) {\n' + + ' for (var i = 0, l = xs.length; i < l; i++) {\n' + + ' if (xs[i] === x) return i;\n' + + ' }\n' + + ' return -1;\n' + + '}\n' +] + +module.exports.altIndexOfUseReplacement = [ + /(\W)([\w\.\(\),\[\]]+)(\.indexOf\()/gm + , '$1indexOf($2, ' +] +module.exports.objectKeysDefine = [ + /^('use strict';)$/m + , '$1\n\n/**/\nvar objectKeys = Object.keys || function (obj) {\n' + + ' var keys = [];\n' + + ' for (var key in obj) keys.push(key);\n' + + ' return keys;\n' + + '}\n/**/\n' +] + +module.exports.objectKeysReplacement = [ + /Object\.keys/g + , 'objectKeys' + ] + + +module.exports.bufferShimFix = [ + /^('use strict';)$/m, + `/**/ + const bufferShim = require('safe-buffer').Buffer; + /**/` +] + +module.exports.bufferStaticMethods = [ + /Buffer\.((?:alloc)|(?:allocUnsafe)|(?:from))/g, + `bufferShim.$1` +] diff --git a/build/files.js b/build/files.js index 66f195d..ff6b7c6 100644 --- a/build/files.js +++ b/build/files.js @@ -15,8 +15,11 @@ module.exports['string_decoder.js'] = [ ] , [ - /const Buffer = require\('buffer'\).Buffer;/ - , 'var Buffer = require(\'safe-buffer\').Buffer;\n' + /(?:var|const) (?:{ )Buffer(?: }) = require\('buffer'\)(?:\.Buffer)?;/, + `/**/ + var Buffer = require('safe-buffer').Buffer; +/**/ +` ] // add Buffer.isEncoding where missing diff --git a/build/package.json b/build/package.json index 29dc1c2..cf2a769 100644 --- a/build/package.json +++ b/build/package.json @@ -1,23 +1,26 @@ { - "name": "string_decoder-build", + "name": "readable-stream-build", "version": "0.0.0", "description": "", "main": "build.js", "dependencies": { - "babel-core": "^6.5.2", + "babel-core": "^6.26.0", "babel-plugin-transform-es2015-arrow-functions": "^6.5.2", - "babel-plugin-transform-es2015-block-scoping": "^6.5.0", + "babel-plugin-transform-es2015-block-scoping": "^6.26.0", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", "babel-plugin-transform-es2015-destructuring": "^6.18.0", "babel-plugin-transform-es2015-for-of": "^6.8.0", - "babel-plugin-transform-es2015-parameters": "^6.11.4", - "babel-plugin-transform-es2015-shorthand-properties": "^6.8.0", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", "babel-plugin-transform-es2015-template-literals": "^6.8.0", - "bl": "^1.2.0", - "glob": "^7.1.1", - "gunzip-maybe": "^1.4.0", - "hyperquest": "^2.1.2", - "pump": "^1.0.2", - "rimraf": "^2.6.1", - "tar-fs": "^1.15.1" + "bl": "^1.2.1", + "glob": "^7.1.2", + "gunzip-maybe": "^1.4.1", + "hyperquest": "^2.1.3", + "pump": "^3.0.0", + "rimraf": "^2.6.2", + "tar-fs": "^1.16.0" } } diff --git a/build/test-replacements.js b/build/test-replacements.js index 11b1bb0..830dbca 100644 --- a/build/test-replacements.js +++ b/build/test-replacements.js @@ -1,3 +1,8 @@ +const altForEachImplReplacement = require('./common-replacements').altForEachImplReplacement + , altForEachUseReplacement = require('./common-replacements').altForEachUseReplacement + , objectKeysDefine = require('./common-replacements').objectKeysDefine + , objectKeysReplacement = require('./common-replacements').objectKeysReplacement + module.exports.all = [ [ /require\(['"]string_decoder['"]\)/g @@ -16,27 +21,57 @@ module.exports.all = [ ] -module.exports['common.js'] = [ + +module.exports['test-string-decoder.js'] = [ + // test removed because it is V8-version dependant. [ - /^ setImmediate,$/m - , ' typeof setImmediate == \'undefined\' ? null : setImmediate,' + /test\('utf-8', bufferShim\.from\('EDA0B5EDB08D'.*\n.*\n/ + , '' + ], + , [ + /test\('utf-8', bufferShim\.from\('F0B841', 'hex'.*\n/ + , '' ] - , [ - /^ clearImmediate,$/m - , ' typeof clearImmediate == \'undefined\' ? null : clearImmediate,' + /test\('utf-8', bufferShim\.from\('CCE2B8B8', 'hex'.*\n/ + , '' + ] + , [ + /test\('utf-8', bufferShim\.from\('E2B8CCB8', 'hex'.*\n/ + , '' ] - , [ - /^ global];$/m - , ' global].filter(Boolean);' + /assert\.strictEqual\(decoder\.end(), '\ufffd'\);\n/ + , '' ] +] + +module.exports['common.js'] = [ + objectKeysDefine + , objectKeysReplacement + , altForEachImplReplacement + , altForEachUseReplacement , [ - /^/ - , 'require(\'babel-polyfill\');' + /(exports.mustCall[\s\S]*)/m + , '$1\n' + + 'if (!util._errnoException) {\n' + + ' var uv;\n' + + ' util._errnoException = function(err, syscall) {\n' + + ' if (util.isUndefined(uv)) try { uv = process.binding(\'uv\'); } catch (e) {}\n' + + ' var errname = uv ? uv.errname(err) : \'\';\n' + + ' var e = new Error(syscall + \' \' + errname);\n' + + ' e.code = errname;\n' + + ' e.errno = errname;\n' + + ' e.syscall = syscall;\n' + + ' return e;\n' + + ' };\n' + + '}\n' ] + // for streams2 on node 0.11 + // and dtrace in 0.10 + // and coverage in all , [ /^( for \(var x in global\) \{|function leakedGlobals\(\) \{)$/m , ' /**/\n' @@ -51,12 +86,89 @@ module.exports['common.js'] = [ + '\'core,__core-js_shared__,Promise,Map,Set,WeakMap,WeakSet,Reflect,System,asap,Observable,regeneratorRuntime,_babelPolyfill\'.split(\',\').filter(function (item) { return typeof global[item] !== undefined}).forEach(function (item) {knownGlobals.push(global[item])})' + ' /**/\n\n$1' ] -] -module.exports['test-string-decoder.js'] = [ - // test removed because it is V8-version dependant. - [ - /test\('utf-8', bufferShim.from\('EDA0B5EDB08D'.*\n.*\n/ - , '' + // for node 0.8 + , [ + /^/ + , '/**/' + + '\nif (!global.setImmediate) {\n' + + ' global.setImmediate = function setImmediate(fn) {\n' + + + ' return setTimeout(fn.bind.apply(fn, arguments), 4);\n' + + ' };\n' + + '}\n' + + 'if (!global.clearImmediate) {\n' + + ' global.clearImmediate = function clearImmediate(i) {\n' + + ' return clearTimeout(i);\n' + + ' };\n' + + '}\n' + + '/**/\n' + ] + , [ + /^if \(global\.ArrayBuffer\) \{([^\}]+)\}$/m + , '/**/if (!process.browser) {' + + '\nif \(global\.ArrayBuffer\) {$1}\n' + + '}/**/\n' + ] + , [ + /^Object\.defineProperty\(([\w\W]+?)\}\)\;/mg + , '/**/if (!process.browser) {' + + '\nObject\.defineProperty($1});\n' + + '}/**/\n' + ] + , [ + /if \(!process\.send\)/ + , 'if (!process.send && !process.browser)' ] + , [ + /^/, + `/**/ + require('babel-polyfill'); + var util = require('util'); + for (var i in util) exports[i] = util[i]; + /**/` + ], + [ + /var regexp = `\^\(\\\\w\+\)\\\\s\+\\\\s\$\{port\}\/\$\{protocol\}\\\\s`;/, + `var regexp = '^(\\w+)\\s+\\s' + port + '/' + protocol + '\\s';` + ], + [ + /^var util = require\('util'\);/m + , '\n/**/\nvar util = require(\'core-util-is\');\n' + + 'util.inherits = require(\'inherits\');\n/**/\n' + ], + [ + /^const util = require\('util'\);/m +, '\n/**/\nvar util = require(\'core-util-is\');\n' + + 'util.inherits = require(\'inherits\');\n/**/\n' +] +, [ + /process\.binding\('timer_wrap'\)\.Timer;/, + '{now: function (){}}' +], +[ + /(exports\.enoughTestCpu[^;]+;)/, + '/*$1*/' +], +[ + /exports\.buildType/, + '//exports.buildType' +], +[ + /require\('async_hooks'\)/, + '/*require(\'async_hooks\')' +], +[ + /\}\).enable\(\);/, + '}).enable();*/' +], +[ + /(?:var|const) async_wrap = process\.binding\('async_wrap'\);\n.*(?:var|const) (?:{ )?kCheck(?: })? = async_wrap\.constants(?:\.kCheck)?;/gm, + '// const async_wrap = process.binding(\'async_wrap\');\n' + + ' // const kCheck = async_wrap.constants.kCheck;' +], +[ + /async_wrap\.async_hook_fields\[kCheck\] \+= 1;/, + '// async_wrap.async_hook_fields[kCheck] += 1;' +] ] diff --git a/lib/string_decoder.js b/lib/string_decoder.js index 26fb94c..2e89e63 100644 --- a/lib/string_decoder.js +++ b/lib/string_decoder.js @@ -1,6 +1,30 @@ +// 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'; +/**/ + var Buffer = require('safe-buffer').Buffer; +/**/ var isEncoding = Buffer.isEncoding || function (encoding) { encoding = '' + encoding; @@ -112,10 +136,10 @@ StringDecoder.prototype.fillLast = function (buf) { }; // Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a -// continuation byte. +// continuation byte. If an invalid byte is detected, -2 is returned. function utf8CheckByte(byte) { if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; - return -1; + return byte >> 6 === 0x02 ? -1 : -2; } // Checks at most 3 bytes at the end of a Buffer in order to detect an @@ -129,13 +153,13 @@ function utf8CheckIncomplete(self, buf, i) { if (nb > 0) self.lastNeed = nb - 1; return nb; } - if (--j < i) return 0; + if (--j < i || nb === -2) return 0; nb = utf8CheckByte(buf[j]); if (nb >= 0) { if (nb > 0) self.lastNeed = nb - 2; return nb; } - if (--j < i) return 0; + if (--j < i || nb === -2) return 0; nb = utf8CheckByte(buf[j]); if (nb >= 0) { if (nb > 0) { @@ -149,7 +173,7 @@ function utf8CheckIncomplete(self, buf, i) { // Validates as many continuation bytes for a multi-byte UTF-8 character as // needed or are available. If we see a non-continuation byte where we expect // one, we "replace" the validated continuation bytes we've seen so far with -// UTF-8 replacement characters ('\ufffd'), to match v8's UTF-8 decoding +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding // behavior. The continuation byte check is included three times in the case // where all of the continuation bytes for a character exist in the same buffer. // It is also done this way as a slight performance increase instead of using a @@ -157,17 +181,17 @@ function utf8CheckIncomplete(self, buf, i) { function utf8CheckExtraBytes(self, buf, p) { if ((buf[0] & 0xC0) !== 0x80) { self.lastNeed = 0; - return '\ufffd'.repeat(p); + return '\ufffd'; } if (self.lastNeed > 1 && buf.length > 1) { if ((buf[1] & 0xC0) !== 0x80) { self.lastNeed = 1; - return '\ufffd'.repeat(p + 1); + return '\ufffd'; } if (self.lastNeed > 2 && buf.length > 2) { if ((buf[2] & 0xC0) !== 0x80) { self.lastNeed = 2; - return '\ufffd'.repeat(p + 2); + return '\ufffd'; } } } @@ -198,11 +222,11 @@ function utf8Text(buf, i) { return buf.toString('utf8', i, end); } -// For UTF-8, a replacement character for each buffered byte of a (partial) -// character needs to be added to the output. +// For UTF-8, a replacement character is added when ending on a partial +// character. function utf8End(buf) { var r = buf && buf.length ? this.write(buf) : ''; - if (this.lastNeed) return r + '\ufffd'.repeat(this.lastTotal - this.lastNeed); + if (this.lastNeed) return r + '\ufffd'; return r; } diff --git a/package.json b/package.json index 49408e8..c359e28 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,13 @@ }, "devDependencies": { "babel-polyfill": "^6.23.0", + "core-util-is": "^1.0.2", + "inherits": "^2.0.3", "tap": "~0.4.8" }, "scripts": { - "test": "tap test/parallel/*.js && node test/verify-dependencies" + "test": "tap test/parallel/*.js && node test/verify-dependencies", + "ci": "tap test/parallel/*.js test/ours/*.js --tap | tee test.tap && node test/verify-dependencies.js" }, "repository": { "type": "git", diff --git a/test/common.js b/test/common.js deleted file mode 100644 index e879f8e..0000000 --- a/test/common.js +++ /dev/null @@ -1,583 +0,0 @@ -require('babel-polyfill'); /* eslint-disable required-modules */ -'use strict'; -var path = require('path'); -var fs = require('fs'); -var assert = require('assert'); -var os = require('os'); -var child_process = require('child_process'); -var stream = require('stream'); -var util = require('util'); -var Timer = process.binding('timer_wrap').Timer; -var execSync = require('child_process').execSync; - -var testRoot = process.env.NODE_TEST_DIR ? fs.realpathSync(process.env.NODE_TEST_DIR) : __dirname; - -exports.fixturesDir = path.join(__dirname, 'fixtures'); -exports.tmpDirName = 'tmp'; -// PORT should match the definition in test/testpy/__init__.py. -exports.PORT = +process.env.NODE_COMMON_PORT || 12346; -exports.isWindows = process.platform === 'win32'; -exports.isWOW64 = exports.isWindows && process.env.PROCESSOR_ARCHITEW6432 !== undefined; -exports.isAix = process.platform === 'aix'; -exports.isLinuxPPCBE = process.platform === 'linux' && process.arch === 'ppc64' && os.endianness() === 'BE'; -exports.isSunOS = process.platform === 'sunos'; -exports.isFreeBSD = process.platform === 'freebsd'; -exports.isLinux = process.platform === 'linux'; -exports.isOSX = process.platform === 'darwin'; - -exports.enoughTestMem = os.totalmem() > 0x40000000; /* 1 Gb */ - -var cpus = os.cpus(); -exports.enoughTestCpu = Array.isArray(cpus) && (cpus.length > 1 || cpus[0].speed > 999); - -exports.rootDir = exports.isWindows ? 'c:\\' : '/'; -exports.buildType = process.config.target_defaults.default_configuration; - -function rimrafSync(p) { - var st = void 0; - try { - st = fs.lstatSync(p); - } catch (e) { - if (e.code === 'ENOENT') return; - } - - try { - if (st && st.isDirectory()) rmdirSync(p, null);else fs.unlinkSync(p); - } catch (e) { - if (e.code === 'ENOENT') return; - if (e.code === 'EPERM') return rmdirSync(p, e); - if (e.code !== 'EISDIR') throw e; - rmdirSync(p, e); - } -} - -function rmdirSync(p, originalEr) { - try { - fs.rmdirSync(p); - } catch (e) { - if (e.code === 'ENOTDIR') throw originalEr; - if (e.code === 'ENOTEMPTY' || e.code === 'EEXIST' || e.code === 'EPERM') { - var enc = exports.isLinux ? 'buffer' : 'utf8'; - fs.readdirSync(p, enc).forEach(function (f) { - if (f instanceof Buffer) { - var buf = Buffer.concat([Buffer.from(p), Buffer.from(path.sep), f]); - rimrafSync(buf); - } else { - rimrafSync(path.join(p, f)); - } - }); - fs.rmdirSync(p); - } - } -} - -exports.refreshTmpDir = function () { - rimrafSync(exports.tmpDir); - fs.mkdirSync(exports.tmpDir); -}; - -if (process.env.TEST_THREAD_ID) { - exports.PORT += process.env.TEST_THREAD_ID * 100; - exports.tmpDirName += '.' + process.env.TEST_THREAD_ID; -} -exports.tmpDir = path.join(testRoot, exports.tmpDirName); - -var opensslCli = null; -var inFreeBSDJail = null; -var localhostIPv4 = null; - -exports.localIPv6Hosts = ['localhost']; -if (exports.isLinux) { - exports.localIPv6Hosts = [ - // Debian/Ubuntu - 'ip6-localhost', 'ip6-loopback', - - // SUSE - 'ipv6-localhost', 'ipv6-loopback', - - // Typically universal - 'localhost']; -} - -Object.defineProperty(exports, 'inFreeBSDJail', { - get: function () { - if (inFreeBSDJail !== null) return inFreeBSDJail; - - if (exports.isFreeBSD && child_process.execSync('sysctl -n security.jail.jailed').toString() === '1\n') { - inFreeBSDJail = true; - } else { - inFreeBSDJail = false; - } - return inFreeBSDJail; - } -}); - -Object.defineProperty(exports, 'localhostIPv4', { - get: function () { - if (localhostIPv4 !== null) return localhostIPv4; - - if (exports.inFreeBSDJail) { - // Jailed network interfaces are a bit special - since we need to jump - // through loops, as well as this being an exception case, assume the - // user will provide this instead. - if (process.env.LOCALHOST) { - localhostIPv4 = process.env.LOCALHOST; - } else { - console.error('Looks like we\'re in a FreeBSD Jail. ' + 'Please provide your default interface address ' + 'as LOCALHOST or expect some tests to fail.'); - } - } - - if (localhostIPv4 === null) localhostIPv4 = '127.0.0.1'; - - return localhostIPv4; - } -}); - -// opensslCli defined lazily to reduce overhead of spawnSync -Object.defineProperty(exports, 'opensslCli', { get: function () { - if (opensslCli !== null) return opensslCli; - - if (process.config.variables.node_shared_openssl) { - // use external command - opensslCli = 'openssl'; - } else { - // use command built from sources included in Node.js repository - opensslCli = path.join(path.dirname(process.execPath), 'openssl-cli'); - } - - if (exports.isWindows) opensslCli += '.exe'; - - var opensslCmd = child_process.spawnSync(opensslCli, ['version']); - if (opensslCmd.status !== 0 || opensslCmd.error !== undefined) { - // openssl command cannot be executed - opensslCli = false; - } - return opensslCli; - }, enumerable: true }); - -Object.defineProperty(exports, 'hasCrypto', { - get: function () { - return process.versions.openssl ? true : false; - } -}); - -Object.defineProperty(exports, 'hasFipsCrypto', { - get: function () { - return exports.hasCrypto && require('crypto').fips; - } -}); - -if (exports.isWindows) { - exports.PIPE = '\\\\.\\pipe\\libuv-test'; - if (process.env.TEST_THREAD_ID) { - exports.PIPE += '.' + process.env.TEST_THREAD_ID; - } -} else { - exports.PIPE = exports.tmpDir + '/test.sock'; -} - -var ifaces = os.networkInterfaces(); -exports.hasIPv6 = Object.keys(ifaces).some(function (name) { - return (/lo/.test(name) && ifaces[name].some(function (info) { - return info.family === 'IPv6'; - }) - ); -}); - -/* - * Check that when running a test with - * `$node --abort-on-uncaught-exception $file child` - * the process aborts. - */ -exports.childShouldThrowAndAbort = function () { - var testCmd = ''; - if (!exports.isWindows) { - // Do not create core files, as it can take a lot of disk space on - // continuous testing and developers' machines - testCmd += 'ulimit -c 0 && '; - } - testCmd += process.argv[0] + ' --abort-on-uncaught-exception '; - testCmd += process.argv[1] + ' child'; - var child = child_process.exec(testCmd); - child.on('exit', function onExit(exitCode, signal) { - var errMsg = 'Test should have aborted ' + ('but instead exited with exit code ' + exitCode) + (' and signal ' + signal); - assert(exports.nodeProcessAborted(exitCode, signal), errMsg); - }); -}; - -exports.ddCommand = function (filename, kilobytes) { - if (exports.isWindows) { - var p = path.resolve(exports.fixturesDir, 'create-file.js'); - return '"' + process.argv[0] + '" "' + p + '" "' + filename + '" ' + kilobytes * 1024; - } else { - return 'dd if=/dev/zero of="' + filename + '" bs=1024 count=' + kilobytes; - } -}; - -exports.spawnPwd = function (options) { - var spawn = require('child_process').spawn; - - if (exports.isWindows) { - return spawn('cmd.exe', ['/d', '/c', 'cd'], options); - } else { - return spawn('pwd', [], options); - } -}; - -exports.spawnSyncPwd = function (options) { - var spawnSync = require('child_process').spawnSync; - - if (exports.isWindows) { - return spawnSync('cmd.exe', ['/d', '/c', 'cd'], options); - } else { - return spawnSync('pwd', [], options); - } -}; - -exports.platformTimeout = function (ms) { - if (process.config.target_defaults.default_configuration === 'Debug') ms = 2 * ms; - - if (global.__coverage__) ms = 4 * ms; - - if (exports.isAix) return 2 * ms; // default localhost speed is slower on AIX - - if (process.arch !== 'arm') return ms; - - var armv = process.config.variables.arm_version; - - if (armv === '6') return 7 * ms; // ARMv6 - - if (armv === '7') return 2 * ms; // ARMv7 - - return ms; // ARMv8+ -}; - -var knownGlobals = [Buffer, clearImmediate, clearInterval, clearTimeout, console, constructor, // Enumerable in V8 3.21. -global, process, setImmediate, setInterval, setTimeout]; - -if (global.gc) { - knownGlobals.push(global.gc); -} - -if (global.DTRACE_HTTP_SERVER_RESPONSE) { - knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE); - knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST); - knownGlobals.push(DTRACE_HTTP_CLIENT_RESPONSE); - knownGlobals.push(DTRACE_HTTP_CLIENT_REQUEST); - knownGlobals.push(DTRACE_NET_STREAM_END); - knownGlobals.push(DTRACE_NET_SERVER_CONNECTION); -} - -if (global.COUNTER_NET_SERVER_CONNECTION) { - knownGlobals.push(COUNTER_NET_SERVER_CONNECTION); - knownGlobals.push(COUNTER_NET_SERVER_CONNECTION_CLOSE); - knownGlobals.push(COUNTER_HTTP_SERVER_REQUEST); - knownGlobals.push(COUNTER_HTTP_SERVER_RESPONSE); - knownGlobals.push(COUNTER_HTTP_CLIENT_REQUEST); - knownGlobals.push(COUNTER_HTTP_CLIENT_RESPONSE); -} - -if (global.LTTNG_HTTP_SERVER_RESPONSE) { - knownGlobals.push(LTTNG_HTTP_SERVER_RESPONSE); - knownGlobals.push(LTTNG_HTTP_SERVER_REQUEST); - knownGlobals.push(LTTNG_HTTP_CLIENT_RESPONSE); - knownGlobals.push(LTTNG_HTTP_CLIENT_REQUEST); - knownGlobals.push(LTTNG_NET_STREAM_END); - knownGlobals.push(LTTNG_NET_SERVER_CONNECTION); -} - -if (global.ArrayBuffer) { - knownGlobals.push(ArrayBuffer); - knownGlobals.push(Int8Array); - knownGlobals.push(Uint8Array); - knownGlobals.push(Uint8ClampedArray); - knownGlobals.push(Int16Array); - knownGlobals.push(Uint16Array); - knownGlobals.push(Int32Array); - knownGlobals.push(Uint32Array); - knownGlobals.push(Float32Array); - knownGlobals.push(Float64Array); - knownGlobals.push(DataView); -} - -// Harmony features. -if (global.Proxy) { - knownGlobals.push(Proxy); -} - -if (global.Symbol) { - knownGlobals.push(Symbol); -} - -function allowGlobals() { - for (var _len = arguments.length, whitelist = Array(_len), _key = 0; _key < _len; _key++) { - whitelist[_key] = arguments[_key]; - } - - knownGlobals = knownGlobals.concat(whitelist); -} -exports.allowGlobals = allowGlobals; - -/**/ -if (typeof constructor == 'function') knownGlobals.push(constructor); -if (typeof DTRACE_NET_SOCKET_READ == 'function') knownGlobals.push(DTRACE_NET_SOCKET_READ); -if (typeof DTRACE_NET_SOCKET_WRITE == 'function') knownGlobals.push(DTRACE_NET_SOCKET_WRITE); -if (global.__coverage__) knownGlobals.push(__coverage__); -'core,__core-js_shared__,Promise,Map,Set,WeakMap,WeakSet,Reflect,System,asap,Observable,regeneratorRuntime,_babelPolyfill'.split(',').filter(function (item) { - return typeof global[item] !== undefined; -}).forEach(function (item) { - knownGlobals.push(global[item]); -}); /**/ - -function leakedGlobals() { - var leaked = []; - - for (var val in global) { - if (!knownGlobals.includes(global[val])) leaked.push(val); - }if (global.__coverage__) { - return leaked.filter(function (varname) { - return !/^(cov_|__cov)/.test(varname); - }); - } else { - return leaked; - } -} -exports.leakedGlobals = leakedGlobals; - -// Turn this off if the test should not check for global leaks. -exports.globalCheck = true; - -process.on('exit', function () { - if (!exports.globalCheck) return; - var leaked = leakedGlobals(); - if (leaked.length > 0) { - fail('Unexpected global(s) found: ' + leaked.join(', ')); - } -}); - -var mustCallChecks = []; - -function runCallChecks(exitCode) { - if (exitCode !== 0) return; - - var failed = mustCallChecks.filter(function (context) { - return context.actual !== context.expected; - }); - - failed.forEach(function (context) { - console.log('Mismatched %s function calls. Expected %d, actual %d.', context.name, context.expected, context.actual); - console.log(context.stack.split('\n').slice(2).join('\n')); - }); - - if (failed.length) process.exit(1); -} - -exports.mustCall = function (fn, expected) { - if (expected === undefined) expected = 1;else if (typeof expected !== 'number') throw new TypeError('Invalid expected value: ' + expected); - - var context = { - expected: expected, - actual: 0, - stack: new Error().stack, - name: fn.name || '' - }; - - // add the exit listener only once to avoid listener leak warnings - if (mustCallChecks.length === 0) process.on('exit', runCallChecks); - - mustCallChecks.push(context); - - return function () { - context.actual++; - return fn.apply(this, arguments); - }; -}; - -exports.hasMultiLocalhost = function hasMultiLocalhost() { - var TCP = process.binding('tcp_wrap').TCP; - var t = new TCP(); - var ret = t.bind('127.0.0.2', exports.PORT); - t.close(); - return ret === 0; -}; - -exports.fileExists = function (pathname) { - try { - fs.accessSync(pathname); - return true; - } catch (err) { - return false; - } -}; - -exports.canCreateSymLink = function () { - // On Windows, creating symlinks requires admin privileges. - // We'll only try to run symlink test if we have enough privileges. - // On other platforms, creating symlinks shouldn't need admin privileges - if (exports.isWindows) { - // whoami.exe needs to be the one from System32 - // If unix tools are in the path, they can shadow the one we want, - // so use the full path while executing whoami - var whoamiPath = path.join(process.env['SystemRoot'], 'System32', 'whoami.exe'); - - var err = false; - var output = ''; - - try { - output = execSync(whoamiPath + ' /priv', { timout: 1000 }); - } catch (e) { - err = true; - } finally { - if (err || !output.includes('SeCreateSymbolicLinkPrivilege')) { - return false; - } - } - } - - return true; -}; - -function fail(msg) { - assert.fail(null, null, msg); -} -exports.fail = fail; - -exports.mustNotCall = function (msg) { - return function mustNotCall() { - fail(msg || 'function should not have been called'); - }; -}; - -exports.skip = function (msg) { - console.log('1..0 # Skipped: ' + msg); -}; - -// A stream to push an array into a REPL -function ArrayStream() { - this.run = function (data) { - var _this = this; - - data.forEach(function (line) { - _this.emit('data', line + '\n'); - }); - }; -} - -util.inherits(ArrayStream, stream.Stream); -exports.ArrayStream = ArrayStream; -ArrayStream.prototype.readable = true; -ArrayStream.prototype.writable = true; -ArrayStream.prototype.pause = function () {}; -ArrayStream.prototype.resume = function () {}; -ArrayStream.prototype.write = function () {}; - -// Returns true if the exit code "exitCode" and/or signal name "signal" -// represent the exit code and/or signal name of a node process that aborted, -// false otherwise. -exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) { - // Depending on the compiler used, node will exit with either - // exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT). - var expectedExitCodes = [132, 133, 134]; - - // On platforms using KSH as the default shell (like SmartOS), - // when a process aborts, KSH exits with an exit code that is - // greater than 256, and thus the exit code emitted with the 'exit' - // event is null and the signal is set to either SIGILL, SIGTRAP, - // or SIGABRT (depending on the compiler). - var expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT']; - - // On Windows, v8's base::OS::Abort triggers an access violation, - // which corresponds to exit code 3221225477 (0xC0000005) - if (exports.isWindows) expectedExitCodes = [3221225477]; - - // When using --abort-on-uncaught-exception, V8 will use - // base::OS::Abort to terminate the process. - // Depending on the compiler used, the shell or other aspects of - // the platform used to build the node binary, this will actually - // make V8 exit by aborting or by raising a signal. In any case, - // one of them (exit code or signal) needs to be set to one of - // the expected exit codes or signals. - if (signal !== null) { - return expectedSignals.includes(signal); - } else { - return expectedExitCodes.includes(exitCode); - } -}; - -exports.busyLoop = function busyLoop(time) { - var startTime = Timer.now(); - var stopTime = startTime + time; - while (Timer.now() < stopTime) {} -}; - -exports.isAlive = function isAlive(pid) { - try { - process.kill(pid, 'SIGCONT'); - return true; - } catch (e) { - return false; - } -}; - -exports.expectWarning = function (name, expected) { - if (typeof expected === 'string') expected = [expected]; - process.on('warning', exports.mustCall(function (warning) { - assert.strictEqual(warning.name, name); - assert.ok(expected.includes(warning.message), 'unexpected error message: "' + warning.message + '"'); - // Remove a warning message after it is seen so that we guarantee that we - // get each message only once. - expected.splice(expected.indexOf(warning.message), 1); - }, expected.length)); -}; - -Object.defineProperty(exports, 'hasIntl', { - get: function () { - return process.binding('config').hasIntl; - } -}); - -// https://github.com/w3c/testharness.js/blob/master/testharness.js -exports.WPT = { - test: function (fn, desc) { - try { - fn(); - } catch (err) { - if (err instanceof Error) err.message = 'In ' + desc + ':\n ' + err.message; - throw err; - } - }, - assert_equals: assert.strictEqual, - assert_true: function (value, message) { - return assert.strictEqual(value, true, message); - }, - assert_false: function (value, message) { - return assert.strictEqual(value, false, message); - }, - assert_throws: function (code, func, desc) { - assert.throws(func, function (err) { - return typeof err === 'object' && 'name' in err && err.name === code.name; - }, desc); - }, - assert_array_equals: assert.deepStrictEqual, - assert_unreached: function (desc) { - assert.fail(undefined, undefined, 'Reached unreachable code: ' + desc); - } -}; - -// Useful for testing expected internal/error objects -exports.expectsError = function expectsError(_ref) { - var code = _ref.code, - type = _ref.type, - message = _ref.message; - - return function (error) { - assert.strictEqual(error.code, code); - if (type !== undefined) assert(error instanceof type, error + ' is not the expected type ' + type); - if (message instanceof RegExp) { - assert(message.test(error.message), error.message + ' does not match ' + message); - } else if (typeof message === 'string') { - assert.strictEqual(error.message, message); - } - return true; - }; -}; \ No newline at end of file diff --git a/test/common/README.md b/test/common/README.md new file mode 100644 index 0000000..0e25d98 --- /dev/null +++ b/test/common/README.md @@ -0,0 +1,541 @@ +/**/ + require('babel-polyfill'); + var util = require('util'); + for (var i in util) exports[i] = util[i]; + /**//**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +# Node.js Core Test Common Modules + +This directory contains modules used to test the Node.js implementation. + +## Table of Contents + +* [Benchmark module](#benchmark-module) +* [Common module API](#common-module-api) +* [Countdown module](#countdown-module) +* [DNS module](#dns-module) +* [Duplex pair helper](#duplex-pair-helper) +* [Fixtures module](#fixtures-module) +* [WPT module](#wpt-module) + +## Benchmark Module + +The `benchmark` module is used by tests to run benchmarks. + +### runBenchmark(name, args, env) + +* `name` [<String>] Name of benchmark suite to be run. +* `args` [<Array>] Array of environment variable key/value pairs (ex: + `n=1`) to be applied via `--set`. +* `env` [<Object>] Environment variables to be applied during the run. + +## Common Module API + +The `common` module is used by tests for consistency across repeated +tasks. + +### allowGlobals(...whitelist) +* `whitelist` [<Array>] Array of Globals +* return [<Array>] + +Takes `whitelist` and concats that with predefined `knownGlobals`. + +### arrayStream +A stream to push an array into a REPL + +### busyLoop(time) +* `time` [<Number>] + +Blocks for `time` amount of time. + +### canCreateSymLink() +* return [<Boolean>] + +Checks whether the current running process can create symlinks. On Windows, this +returns `false` if the process running doesn't have privileges to create +symlinks +([SeCreateSymbolicLinkPrivilege](https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx)). +On non-Windows platforms, this always returns `true`. + +### crashOnUnhandledRejection() + +Installs a `process.on('unhandledRejection')` handler that crashes the process +after a tick. This is useful for tests that use Promises and need to make sure +no unexpected rejections occur, because currently they result in silent +failures. + +### ddCommand(filename, kilobytes) +* return [<Object>] + +Platform normalizes the `dd` command + +### enoughTestMem +* [<Boolean>] + +Indicates if there is more than 1gb of total memory. + +### expectsError([fn, ]settings[, exact]) +* `fn` [<Function>] a function that should throw. +* `settings` [<Object>] + that must contain the `code` property plus any of the other following + properties (some properties only apply for `AssertionError`): + * `code` [<String>] + expected error must have this value for its `code` property. + * `type` [<Function>] + expected error must be an instance of `type` and must be an Error subclass. + * `message` [<String>] or [<RegExp>] + if a string is provided for `message`, expected error must have it for its + `message` property; if a regular expression is provided for `message`, the + regular expression must match the `message` property of the expected error. + * `name` [<String>] + expected error must have this value for its `name` property. + * `generatedMessage` [<String>] + (`AssertionError` only) expected error must have this value for its + `generatedMessage` property. + * `actual` <any> + (`AssertionError` only) expected error must have this value for its + `actual` property. + * `expected` <any> + (`AssertionError` only) expected error must have this value for its + `expected` property. + * `operator` <any> + (`AssertionError` only) expected error must have this value for its + `operator` property. +* `exact` [<Number>] default = 1 +* return [<Function>] + + If `fn` is provided, it will be passed to `assert.throws` as first argument + and `undefined` will be returned. + Otherwise a function suitable as callback or for use as a validation function + passed as the second argument to `assert.throws()` will be returned. If the + returned function has not been called exactly `exact` number of times when the + test is complete, then the test will fail. + +### expectWarning(name, expected) +* `name` [<String>] +* `expected` [<String>] | [<Array>] + +Tests whether `name` and `expected` are part of a raised warning. + +### fileExists(pathname) +* pathname [<String>] +* return [<Boolean>] + +Checks if `pathname` exists + +### fires(promise, [error], [timeoutMs]) +* promise [<Promise] +* error [<String] default = 'timeout' +* timeoutMs [<Number] default = 100 + +Returns a new promise that will propagate `promise` resolution or rejection if +that happens within the `timeoutMs` timespan, or rejects with `error` as +a reason otherwise. + +### getArrayBufferViews(buf) +* `buf` [<Buffer>] +* return [<ArrayBufferView[]>] + +Returns an instance of all possible `ArrayBufferView`s of the provided Buffer. + +### getCallSite(func) +* `func` [<Function>] +* return [<String>] + +Returns the file name and line number for the provided Function. + +### globalCheck +* [<Boolean>] + +Set to `false` if the test should not check for global leaks. + +### hasCrypto +* [<Boolean>] + +Indicates whether OpenSSL is available. + +### hasFipsCrypto +* [<Boolean>] + +Indicates `hasCrypto` and `crypto` with fips. + +### hasIntl +* [<Boolean>] + +Indicates if [internationalization] is supported. + +### hasSmallICU +* [<Boolean>] + +Indicates `hasIntl` and `small-icu` are supported. + +### hasIPv6 +* [<Boolean>] + +Indicates whether `IPv6` is supported on this platform. + +### hasMultiLocalhost +* [<Boolean>] + +Indicates if there are multiple localhosts available. + +### hijackStderr(listener) +* `listener` [<Function>]: a listener with a single parameter + called `data`. + +Eavesdrop to `process.stderr.write` calls. Once `process.stderr.write` is +called, `listener` will also be called and the `data` of `write` function will +be passed to `listener`. What's more, `process.stderr.writeTimes` is a count of +the number of calls. + +### hijackStdout(listener) +* `listener` [<Function>]: a listener with a single parameter + called `data`. + +Eavesdrop to `process.stdout.write` calls. Once `process.stdout.write` is +called, `listener` will also be called and the `data` of `write` function will +be passed to `listener`. What's more, `process.stdout.writeTimes` is a count of +the number of calls. + +### inFreeBSDJail +* [<Boolean>] + +Checks whether free BSD Jail is true or false. + +### isAIX +* [<Boolean>] + +Platform check for Advanced Interactive eXecutive (AIX). + +### isAlive(pid) +* `pid` [<Number>] +* return [<Boolean>] + +Attempts to 'kill' `pid` + +### isFreeBSD +* [<Boolean>] + +Platform check for Free BSD. + +### isLinux +* [<Boolean>] + +Platform check for Linux. + +### isLinuxPPCBE +* [<Boolean>] + +Platform check for Linux on PowerPC. + +### isOSX +* [<Boolean>] + +Platform check for macOS. + +### isSunOS +* [<Boolean>] + +Platform check for SunOS. + +### isWindows +* [<Boolean>] + +Platform check for Windows. + +### isWOW64 +* [<Boolean>] + +Platform check for Windows 32-bit on Windows 64-bit. + +### leakedGlobals() +* return [<Array>] + +Indicates whether any globals are not on the `knownGlobals` list. + +### localhostIPv4 +* [<String>] + +IP of `localhost`. + +### localIPv6Hosts +* [<Array>] + +Array of IPV6 representations for `localhost`. + +### mustCall([fn][, exact]) +* `fn` [<Function>] default = () => {} +* `exact` [<Number>] default = 1 +* return [<Function>] + +Returns a function that calls `fn`. If the returned function has not been called +exactly `exact` number of times when the test is complete, then the test will +fail. + +If `fn` is not provided, an empty function will be used. + +### mustCallAtLeast([fn][, minimum]) +* `fn` [<Function>] default = () => {} +* `minimum` [<Number>] default = 1 +* return [<Function>] + +Returns a function that calls `fn`. If the returned function has not been called +at least `minimum` number of times when the test is complete, then the test will +fail. + +If `fn` is not provided, an empty function will be used. + +### mustNotCall([msg]) +* `msg` [<String>] default = 'function should not have been called' +* return [<Function>] + +Returns a function that triggers an `AssertionError` if it is invoked. `msg` is +used as the error message for the `AssertionError`. + +### nodeProcessAborted(exitCode, signal) +* `exitCode` [<Number>] +* `signal` [<String>] +* return [<Boolean>] + +Returns `true` if the exit code `exitCode` and/or signal name `signal` represent +the exit code and/or signal name of a node process that aborted, `false` +otherwise. + +### opensslCli +* [<Boolean>] + +Indicates whether 'opensslCli' is supported. + +### platformTimeout(ms) +* `ms` [<Number>] +* return [<Number>] + +Platform normalizes timeout. + +### PIPE +* [<String>] + +Path to the test socket. + +### PORT +* [<Number>] + +A port number for tests to use if one is needed. + +### printSkipMessage(msg) +* `msg` [<String>] + +Logs '1..0 # Skipped: ' + `msg` + +### refreshTmpDir() +* return [<String>] + +Deletes the testing 'tmp' directory and recreates it. + +### restoreStderr() + +Restore the original `process.stderr.write`. Used to restore `stderr` to its +original state after calling [`common.hijackStdErr()`][]. + +### restoreStdout() + +Restore the original `process.stdout.write`. Used to restore `stdout` to its +original state after calling [`common.hijackStdOut()`][]. + +### rootDir +* [<String>] + +Path to the 'root' directory. either `/` or `c:\\` (windows) + +### projectDir +* [<String>] + +Path to the project directory. + +### skip(msg) +* `msg` [<String>] + +Logs '1..0 # Skipped: ' + `msg` and exits with exit code `0`. + +### skipIfInspectorDisabled() + +Skip the rest of the tests in the current file when the Inspector +was disabled at compile time. + +### skipIf32Bits() + +Skip the rest of the tests in the current file when the Node.js executable +was compiled with a pointer size smaller than 64 bits. + +### spawnPwd(options) +* `options` [<Object>] +* return [<Object>] + +Platform normalizes the `pwd` command. + +### spawnSyncPwd(options) +* `options` [<Object>] +* return [<Object>] + +Synchronous version of `spawnPwd`. + +### tmpDir +* [<String>] + +The realpath of the 'tmp' directory. + +## Countdown Module + +The `Countdown` module provides a simple countdown mechanism for tests that +require a particular action to be taken after a given number of completed +tasks (for instance, shutting down an HTTP server after a specific number of +requests). + + +```js +const Countdown = require('../common/countdown'); + +function doSomething() { + console.log('.'); +} + +const countdown = new Countdown(2, doSomething); +countdown.dec(); +countdown.dec(); +``` + +### new Countdown(limit, callback) + +* `limit` {number} +* `callback` {function} + +Creates a new `Countdown` instance. + +### Countdown.prototype.dec() + +Decrements the `Countdown` counter. + +### Countdown.prototype.remaining + +Specifies the remaining number of times `Countdown.prototype.dec()` must be +called before the callback is invoked. + +## DNS Module + +The `DNS` module provides a naïve DNS parser/serializer. + +### readDomainFromPacket(buffer, offset) + +* `buffer` [<Buffer>] +* `offset` [<Number>] +* return [<Object>] + +Reads the domain string from a packet and returns an object containing the +number of bytes read and the domain. + +### parseDNSPacket(buffer) + +* `buffer` [<Buffer>] +* return [<Object>] + +Parses a DNS packet. Returns an object with the values of the various flags of +the packet depending on the type of packet. + +### writeIPv6(ip) + +* `ip` [<String>] +* return [<Buffer>] + +Reads an IPv6 String and returns a Buffer containing the parts. + +### writeDomainName(domain) + +* `domain` [<String>] +* return [<Buffer>] + +Reads a Domain String and returns a Buffer containing the domain. + +### writeDNSPacket(parsed) + +* `parsed` [<Object>] +* return [<Buffer>] + +Takes in a parsed Object and writes its fields to a DNS packet as a Buffer +object. + +## Duplex pair helper + +The `common/duplexpair` module exports a single function `makeDuplexPair`, +which returns an object `{ clientSide, serverSide }` where each side is a +`Duplex` stream connected to the other side. + +There is no difference between client or server side beyond their names. + +## Fixtures Module + +The `common/fixtures` module provides convenience methods for working with +files in the `test/fixtures` directory. + +### fixtures.fixturesDir + +* [<String>] + +The absolute path to the `test/fixtures/` directory. + +### fixtures.path(...args) + +* `...args` [<String>] + +Returns the result of `path.join(fixtures.fixturesDir, ...args)`. + +### fixtures.readSync(args[, enc]) + +* `args` [<String>] | [<Array>] + +Returns the result of +`fs.readFileSync(path.join(fixtures.fixturesDir, ...args), 'enc')`. + +### fixtures.readKey(arg[, enc]) + +* `arg` [<String>] + +Returns the result of +`fs.readFileSync(path.join(fixtures.fixturesDir, 'keys', arg), 'enc')`. + +## WPT Module + +The wpt.js module is a port of parts of +[W3C testharness.js](https://github.com/w3c/testharness.js) for testing the +Node.js +[WHATWG URL API](https://nodejs.org/api/url.html#url_the_whatwg_url_api) +implementation with tests from +[W3C Web Platform Tests](https://github.com/w3c/web-platform-tests). + +[<Array>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[<ArrayBufferView[]>]: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView +[<Boolean>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type +[<Buffer>]: https://nodejs.org/api/buffer.html#buffer_class_buffer +[<Function>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function +[<Number>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type +[<Object>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object +[<RegExp>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp +[<String>]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type +[`common.hijackStdErr()`]: #hijackstderrlistener +[`common.hijackStdOut()`]: #hijackstdoutlistener +[internationalization]: https://github.com/nodejs/node/wiki/Intl + +function forEach (xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} diff --git a/test/common/benchmark.js b/test/common/benchmark.js new file mode 100644 index 0000000..555dc62 --- /dev/null +++ b/test/common/benchmark.js @@ -0,0 +1,62 @@ +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ + +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var assert = require('assert'); +var fork = require('child_process').fork; +var path = require('path'); + +var runjs = path.join(__dirname, '..', '..', 'benchmark', 'run.js'); + +function runBenchmark(name, args, env) { + var argv = []; + + for (var _i = 0; _i < args.length; _i++) { + argv.push('--set'); + argv.push(args[_i]); + } + + argv.push(name); + + var mergedEnv = Object.assign({}, process.env, env); + + var child = fork(runjs, argv, { env: mergedEnv }); + child.on('exit', function (code, signal) { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); +} + +module.exports = runBenchmark; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/countdown.js b/test/common/countdown.js new file mode 100644 index 0000000..411fb69 --- /dev/null +++ b/test/common/countdown.js @@ -0,0 +1,70 @@ +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var assert = require('assert'); +var kLimit = Symbol('limit'); +var kCallback = Symbol('callback'); + +var Countdown = function () { + function Countdown(limit, cb) { + _classCallCheck(this, Countdown); + + assert.strictEqual(typeof limit, 'number'); + assert.strictEqual(typeof cb, 'function'); + this[kLimit] = limit; + this[kCallback] = cb; + } + + Countdown.prototype.dec = function dec() { + assert(this[kLimit] > 0, 'Countdown expired'); + if (--this[kLimit] === 0) this[kCallback](); + return this[kLimit]; + }; + + _createClass(Countdown, [{ + key: 'remaining', + get: function () { + return this[kLimit]; + } + }]); + + return Countdown; +}(); + +module.exports = Countdown; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/dns.js b/test/common/dns.js new file mode 100644 index 0000000..388c8df --- /dev/null +++ b/test/common/dns.js @@ -0,0 +1,418 @@ +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +// Naïve DNS parser/serializer. + +var assert = require('assert'); +var os = require('os'); + +var types = { + A: 1, + AAAA: 28, + NS: 2, + CNAME: 5, + SOA: 6, + PTR: 12, + MX: 15, + TXT: 16, + ANY: 255 +}; + +var classes = { + IN: 1 +}; + +function readDomainFromPacket(buffer, offset) { + assert.ok(offset < buffer.length); + var length = buffer[offset]; + if (length === 0) { + return { nread: 1, domain: '' }; + } else if ((length & 0xC0) === 0) { + offset += 1; + var chunk = buffer.toString('ascii', offset, offset + length); + // Read the rest of the domain. + + var _readDomainFromPacket = readDomainFromPacket(buffer, offset + length), + nread = _readDomainFromPacket.nread, + domain = _readDomainFromPacket.domain; + + return { + nread: 1 + length + nread, + domain: domain ? chunk + '.' + domain : chunk + }; + } else { + // Pointer to another part of the packet. + assert.strictEqual(length & 0xC0, 0xC0); + // eslint-disable-next-line + var pointeeOffset = buffer.readUInt16BE(offset) & ~0xC000; + return { + nread: 2, + domain: readDomainFromPacket(buffer, pointeeOffset) + }; + } +} + +function parseDNSPacket(buffer) { + assert.ok(buffer.length > 12); + + var parsed = { + id: buffer.readUInt16BE(0), + flags: buffer.readUInt16BE(2) + }; + + var counts = [['questions', buffer.readUInt16BE(4)], ['answers', buffer.readUInt16BE(6)], ['authorityAnswers', buffer.readUInt16BE(8)], ['additionalRecords', buffer.readUInt16BE(10)]]; + + var offset = 12; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = counts[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var _step$value = _slicedToArray(_step.value, 2), + sectionName = _step$value[0], + count = _step$value[1]; + + parsed[sectionName] = []; + for (var _i = 0; _i < count; ++_i) { + var _readDomainFromPacket2 = readDomainFromPacket(buffer, offset), + nread = _readDomainFromPacket2.nread, + domain = _readDomainFromPacket2.domain; + + offset += nread; + + var type = buffer.readUInt16BE(offset); + + var rr = { + domain: domain, + cls: buffer.readUInt16BE(offset + 2) + }; + offset += 4; + + for (var name in types) { + if (types[name] === type) rr.type = name; + } + + if (sectionName !== 'questions') { + rr.ttl = buffer.readInt32BE(offset); + var dataLength = buffer.readUInt16BE(offset); + offset += 6; + + switch (type) { + case types.A: + assert.strictEqual(dataLength, 4); + rr.address = buffer[offset + 0] + '.' + buffer[offset + 1] + '.' + (buffer[offset + 2] + '.' + buffer[offset + 3]); + break; + case types.AAAA: + assert.strictEqual(dataLength, 16); + rr.address = buffer.toString('hex', offset, offset + 16).replace(/(.{4}(?!$))/g, '$1:'); + break; + case types.TXT: + { + var position = offset; + rr.entries = []; + while (position < offset + dataLength) { + var txtLength = buffer[offset]; + rr.entries.push(buffer.toString('utf8', position + 1, position + 1 + txtLength)); + position += 1 + txtLength; + } + assert.strictEqual(position, offset + dataLength); + break; + } + case types.MX: + { + rr.priority = buffer.readInt16BE(buffer, offset); + offset += 2; + + var _readDomainFromPacket3 = readDomainFromPacket(buffer, offset), + _nread = _readDomainFromPacket3.nread, + _domain = _readDomainFromPacket3.domain; + + rr.exchange = _domain; + assert.strictEqual(_nread, dataLength); + break; + } + case types.NS: + case types.CNAME: + case types.PTR: + { + var _readDomainFromPacket4 = readDomainFromPacket(buffer, offset), + _nread2 = _readDomainFromPacket4.nread, + _domain2 = _readDomainFromPacket4.domain; + + rr.value = _domain2; + assert.strictEqual(_nread2, dataLength); + break; + } + case types.SOA: + { + var mname = readDomainFromPacket(buffer, offset); + var rname = readDomainFromPacket(buffer, offset + mname.nread); + rr.nsname = mname.domain; + rr.hostmaster = rname.domain; + var trailerOffset = offset + mname.nread + rname.nread; + rr.serial = buffer.readUInt32BE(trailerOffset); + rr.refresh = buffer.readUInt32BE(trailerOffset + 4); + rr.retry = buffer.readUInt32BE(trailerOffset + 8); + rr.expire = buffer.readUInt32BE(trailerOffset + 12); + rr.minttl = buffer.readUInt32BE(trailerOffset + 16); + + assert.strictEqual(trailerOffset + 20, dataLength); + break; + } + default: + throw new Error('Unknown RR type ' + rr.type); + } + offset += dataLength; + } + + parsed[sectionName].push(rr); + + assert.ok(offset <= buffer.length); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + assert.strictEqual(offset, buffer.length); + return parsed; +} + +function writeIPv6(ip) { + var parts = ip.replace(/^:|:$/g, '').split(':'); + var buf = Buffer.alloc(16); + + var offset = 0; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = parts[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var part = _step2.value; + + if (part === '') { + offset += 16 - 2 * (parts.length - 1); + } else { + buf.writeUInt16BE(parseInt(part, 16), offset); + offset += 2; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + return buf; +} + +function writeDomainName(domain) { + return Buffer.concat(domain.split('.').map(function (label) { + assert(label.length < 64); + return Buffer.concat([Buffer.from([label.length]), Buffer.from(label, 'ascii')]); + }).concat([Buffer.alloc(1)])); +} + +function writeDNSPacket(parsed) { + var buffers = []; + var kStandardResponseFlags = 0x8180; + + buffers.push(new Uint16Array([parsed.id, parsed.flags === undefined ? kStandardResponseFlags : parsed.flags, parsed.questions && parsed.questions.length, parsed.answers && parsed.answers.length, parsed.authorityAnswers && parsed.authorityAnswers.length, parsed.additionalRecords && parsed.additionalRecords.length])); + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = parsed.questions[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var q = _step3.value; + + assert(types[q.type]); + buffers.push(writeDomainName(q.domain)); + buffers.push(new Uint16Array([types[q.type], q.cls === undefined ? classes.IN : q.cls])); + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = [].concat(parsed.answers, parsed.authorityAnswers, parsed.additionalRecords)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var rr = _step4.value; + + if (!rr) continue; + + assert(types[rr.type]); + buffers.push(writeDomainName(rr.domain)); + buffers.push(new Uint16Array([types[rr.type], rr.cls === undefined ? classes.IN : rr.cls])); + buffers.push(new Int32Array([rr.ttl])); + + var rdLengthBuf = new Uint16Array(1); + buffers.push(rdLengthBuf); + + switch (rr.type) { + case 'A': + rdLengthBuf[0] = 4; + buffers.push(new Uint8Array(rr.address.split('.'))); + break; + case 'AAAA': + rdLengthBuf[0] = 16; + buffers.push(writeIPv6(rr.address)); + break; + case 'TXT': + var total = rr.entries.map(function (s) { + return s.length; + }).reduce(function (a, b) { + return a + b; + }); + // Total length of all strings + 1 byte each for their lengths. + rdLengthBuf[0] = rr.entries.length + total; + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = rr.entries[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var txt = _step5.value; + + buffers.push(new Uint8Array([Buffer.byteLength(txt)])); + buffers.push(Buffer.from(txt)); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + + break; + case 'MX': + rdLengthBuf[0] = 2; + buffers.push(new Uint16Array([rr.priority])); + // fall through + case 'NS': + case 'CNAME': + case 'PTR': + { + var domain = writeDomainName(rr.exchange || rr.value); + rdLengthBuf[0] += domain.length; + buffers.push(domain); + break; + } + case 'SOA': + { + var mname = writeDomainName(rr.nsname); + var rname = writeDomainName(rr.hostmaster); + rdLengthBuf[0] = mname.length + rname.length + 20; + buffers.push(mname, rname); + buffers.push(new Uint32Array([rr.serial, rr.refresh, rr.retry, rr.expire, rr.minttl])); + break; + } + default: + throw new Error('Unknown RR type ' + rr.type); + } + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + return Buffer.concat(buffers.map(function (typedArray) { + var buf = Buffer.from(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + if (os.endianness() === 'LE') { + if (typedArray.BYTES_PER_ELEMENT === 2) buf.swap16(); + if (typedArray.BYTES_PER_ELEMENT === 4) buf.swap32(); + } + return buf; + })); +} + +module.exports = { types: types, classes: classes, writeDNSPacket: writeDNSPacket, parseDNSPacket: parseDNSPacket }; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/duplexpair.js b/test/common/duplexpair.js new file mode 100644 index 0000000..821983d --- /dev/null +++ b/test/common/duplexpair.js @@ -0,0 +1,94 @@ +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var _require = require('stream'), + Duplex = _require.Duplex; + +var assert = require('assert'); + +var kCallback = Symbol('Callback'); +var kOtherSide = Symbol('Other'); + +var DuplexSocket = function (_Duplex) { + _inherits(DuplexSocket, _Duplex); + + function DuplexSocket() { + _classCallCheck(this, DuplexSocket); + + var _this = _possibleConstructorReturn(this, _Duplex.call(this)); + + _this[kCallback] = null; + _this[kOtherSide] = null; + return _this; + } + + DuplexSocket.prototype._read = function _read() { + var callback = this[kCallback]; + if (callback) { + this[kCallback] = null; + callback(); + } + }; + + DuplexSocket.prototype._write = function _write(chunk, encoding, callback) { + assert.notStrictEqual(this[kOtherSide], null); + assert.strictEqual(this[kOtherSide][kCallback], null); + this[kOtherSide][kCallback] = callback; + this[kOtherSide].push(chunk); + }; + + DuplexSocket.prototype._final = function _final(callback) { + this[kOtherSide].on('end', callback); + this[kOtherSide].push(null); + }; + + return DuplexSocket; +}(Duplex); + +function makeDuplexPair() { + var clientSide = new DuplexSocket(); + var serverSide = new DuplexSocket(); + clientSide[kOtherSide] = serverSide; + serverSide[kOtherSide] = clientSide; + return { clientSide: clientSide, serverSide: serverSide }; +} + +module.exports = makeDuplexPair; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/fixtures.js b/test/common/fixtures.js new file mode 100644 index 0000000..75f170d --- /dev/null +++ b/test/common/fixtures.js @@ -0,0 +1,65 @@ +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var path = require('path'); +var fs = require('fs'); + +var fixturesDir = path.join(__dirname, '..', 'fixtures'); + +function fixturesPath() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return path.join.apply(path, [fixturesDir].concat(args)); +} + +function readFixtureSync(args, enc) { + if (Array.isArray(args)) return fs.readFileSync(fixturesPath.apply(undefined, _toConsumableArray(args)), enc); + return fs.readFileSync(fixturesPath(args), enc); +} + +function readFixtureKey(name, enc) { + return fs.readFileSync(fixturesPath('keys', name), enc); +} + +module.exports = { + fixturesDir: fixturesDir, + path: fixturesPath, + readSync: readFixtureSync, + readKey: readFixtureKey +}; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/index.js b/test/common/index.js new file mode 100644 index 0000000..ce627d1 --- /dev/null +++ b/test/common/index.js @@ -0,0 +1,987 @@ +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +// 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. + +/* eslint-disable required-modules, crypto-check */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var path = require('path'); +var fs = require('fs'); +var assert = require('assert'); +var os = require('os'); + +var _require = require('child_process'), + exec = _require.exec, + execSync = _require.execSync, + spawn = _require.spawn, + spawnSync = _require.spawnSync; + +var stream = require('stream'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Timer = { now: function () {} }; + +var _require2 = require('./fixtures'), + fixturesDir = _require2.fixturesDir; + +var testRoot = process.env.NODE_TEST_DIR ? fs.realpathSync(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..'); + +var noop = function () {}; + +// Using a `.` prefixed name, which is the convention for "hidden" on POSIX, +// gets tools to ignore it by default or by simple rules, especially eslint. +var tmpDirName = '.tmp'; +// PORT should match the definition in test/testpy/__init__.py. +exports.PORT = +process.env.NODE_COMMON_PORT || 12346; +exports.isWindows = process.platform === 'win32'; +exports.isWOW64 = exports.isWindows && process.env.PROCESSOR_ARCHITEW6432 !== undefined; +exports.isAIX = process.platform === 'aix'; +exports.isLinuxPPCBE = process.platform === 'linux' && process.arch === 'ppc64' && os.endianness() === 'BE'; +exports.isSunOS = process.platform === 'sunos'; +exports.isFreeBSD = process.platform === 'freebsd'; +exports.isLinux = process.platform === 'linux'; +exports.isOSX = process.platform === 'darwin'; + +exports.enoughTestMem = os.totalmem() > 0x70000000; /* 1.75 Gb */ +var cpus = os.cpus(); +/*exports.enoughTestCpu = Array.isArray(cpus) && + (cpus.length > 1 || cpus[0].speed > 999);*/ + +exports.rootDir = exports.isWindows ? 'c:\\' : '/'; +exports.projectDir = path.resolve(__dirname, '..', '..'); + +//exports.buildType = process.config.target_defaults.default_configuration; + +// Always enable async_hooks checks in tests +{ + // const async_wrap = process.binding('async_wrap'); + // const kCheck = async_wrap.constants.kCheck; + // async_wrap.async_hook_fields[kCheck] += 1; + + exports.revert_force_async_hooks_checks = function () { + async_wrap.async_hook_fields[kCheck] -= 1; + }; +} + +// If env var is set then enable async_hook hooks for all tests. +if (process.env.NODE_TEST_WITH_ASYNC_HOOKS) { + var destroydIdsList = {}; + var destroyListList = {}; + var initHandles = {}; + var _async_wrap = process.binding('async_wrap'); + + process.on('exit', function () { + // itterate through handles to make sure nothing crashes + for (var k in initHandles) { + util.inspect(initHandles[k]); + } + }); + + var _queueDestroyAsyncId = _async_wrap.queueDestroyAsyncId; + _async_wrap.queueDestroyAsyncId = function queueDestroyAsyncId(id) { + if (destroyListList[id] !== undefined) { + process._rawDebug(destroyListList[id]); + process._rawDebug(); + throw new Error('same id added to destroy list twice (' + id + ')'); + } + destroyListList[id] = new Error().stack; + _queueDestroyAsyncId(id); + }; + + /*require('async_hooks').createHook({ + init(id, ty, tr, r) { + if (initHandles[id]) { + process._rawDebug( + `Is same resource: ${r === initHandles[id].resource}`); + process._rawDebug(`Previous stack:\n${initHandles[id].stack}\n`); + throw new Error(`init called twice for same id (${id})`); + } + initHandles[id] = { resource: r, stack: new Error().stack.substr(6) }; + }, + before() { }, + after() { }, + destroy(id) { + if (destroydIdsList[id] !== undefined) { + process._rawDebug(destroydIdsList[id]); + process._rawDebug(); + throw new Error(`destroy called for same id (${id})`); + } + destroydIdsList[id] = new Error().stack; + }, + }).enable();*/ +} + +function rimrafSync(p) { + var st = void 0; + try { + st = fs.lstatSync(p); + } catch (e) { + if (e.code === 'ENOENT') return; + } + + try { + if (st && st.isDirectory()) rmdirSync(p, null);else fs.unlinkSync(p); + } catch (e) { + if (e.code === 'ENOENT') return; + if (e.code === 'EPERM') return rmdirSync(p, e); + if (e.code !== 'EISDIR') throw e; + rmdirSync(p, e); + } +} + +function rmdirSync(p, originalEr) { + try { + fs.rmdirSync(p); + } catch (e) { + if (e.code === 'ENOTDIR') throw originalEr; + if (e.code === 'ENOTEMPTY' || e.code === 'EEXIST' || e.code === 'EPERM') { + var enc = exports.isLinux ? 'buffer' : 'utf8'; + forEach(fs.readdirSync(p, enc), function (f) { + if (f instanceof Buffer) { + var buf = Buffer.concat([Buffer.from(p), Buffer.from(path.sep), f]); + rimrafSync(buf); + } else { + rimrafSync(path.join(p, f)); + } + }); + fs.rmdirSync(p); + } + } +} + +exports.refreshTmpDir = function () { + rimrafSync(exports.tmpDir); + fs.mkdirSync(exports.tmpDir); +}; + +if (process.env.TEST_THREAD_ID) { + exports.PORT += process.env.TEST_THREAD_ID * 100; + tmpDirName += '.' + process.env.TEST_THREAD_ID; +} +exports.tmpDir = path.join(testRoot, tmpDirName); + +var opensslCli = null; +var inFreeBSDJail = null; +var localhostIPv4 = null; + +exports.localIPv6Hosts = ['localhost']; +if (exports.isLinux) { + exports.localIPv6Hosts = [ + // Debian/Ubuntu + 'ip6-localhost', 'ip6-loopback', + + // SUSE + 'ipv6-localhost', 'ipv6-loopback', + + // Typically universal + 'localhost']; +} + +/**/if (!process.browser) { + Object.defineProperty(exports, 'inFreeBSDJail', { + get: function () { + if (inFreeBSDJail !== null) return inFreeBSDJail; + + if (exports.isFreeBSD && execSync('sysctl -n security.jail.jailed').toString() === '1\n') { + inFreeBSDJail = true; + } else { + inFreeBSDJail = false; + } + return inFreeBSDJail; + } + }); +} /**/ + +/**/if (!process.browser) { + Object.defineProperty(exports, 'localhostIPv4', { + get: function () { + if (localhostIPv4 !== null) return localhostIPv4; + + if (exports.inFreeBSDJail) { + // Jailed network interfaces are a bit special - since we need to jump + // through loops, as well as this being an exception case, assume the + // user will provide this instead. + if (process.env.LOCALHOST) { + localhostIPv4 = process.env.LOCALHOST; + } else { + console.error('Looks like we\'re in a FreeBSD Jail. ' + 'Please provide your default interface address ' + 'as LOCALHOST or expect some tests to fail.'); + } + } + + if (localhostIPv4 === null) localhostIPv4 = '127.0.0.1'; + + return localhostIPv4; + } + }); +} /**/ + +// opensslCli defined lazily to reduce overhead of spawnSync +/**/if (!process.browser) { + Object.defineProperty(exports, 'opensslCli', { get: function () { + if (opensslCli !== null) return opensslCli; + + if (process.config.variables.node_shared_openssl) { + // use external command + opensslCli = 'openssl'; + } else { + // use command built from sources included in Node.js repository + opensslCli = path.join(path.dirname(process.execPath), 'openssl-cli'); + } + + if (exports.isWindows) opensslCli += '.exe'; + + var opensslCmd = spawnSync(opensslCli, ['version']); + if (opensslCmd.status !== 0 || opensslCmd.error !== undefined) { + // openssl command cannot be executed + opensslCli = false; + } + return opensslCli; + }, enumerable: true }); +} /**/ + +/**/if (!process.browser) { + Object.defineProperty(exports, 'hasCrypto', { + get: function () { + return Boolean(process.versions.openssl); + } + }); +} /**/ + +/**/if (!process.browser) { + Object.defineProperty(exports, 'hasFipsCrypto', { + get: function () { + return exports.hasCrypto && require('crypto').fips; + } + }); +} /**/ + +{ + var localRelative = path.relative(process.cwd(), exports.tmpDir + '/'); + var pipePrefix = exports.isWindows ? '\\\\.\\pipe\\' : localRelative; + var pipeName = 'node-test.' + process.pid + '.sock'; + exports.PIPE = path.join(pipePrefix, pipeName); +} + +{ + var iFaces = os.networkInterfaces(); + var re = exports.isWindows ? /Loopback Pseudo-Interface/ : /lo/; + exports.hasIPv6 = objectKeys(iFaces).some(function (name) { + return re.test(name) && iFaces[name].some(function (info) { + return info.family === 'IPv6'; + }); + }); +} + +/* + * Check that when running a test with + * `$node --abort-on-uncaught-exception $file child` + * the process aborts. + */ +exports.childShouldThrowAndAbort = function () { + var testCmd = ''; + if (!exports.isWindows) { + // Do not create core files, as it can take a lot of disk space on + // continuous testing and developers' machines + testCmd += 'ulimit -c 0 && '; + } + testCmd += '"' + process.argv[0] + '" --abort-on-uncaught-exception '; + testCmd += '"' + process.argv[1] + '" child'; + var child = exec(testCmd); + child.on('exit', function onExit(exitCode, signal) { + var errMsg = 'Test should have aborted ' + ('but instead exited with exit code ' + exitCode) + (' and signal ' + signal); + assert(exports.nodeProcessAborted(exitCode, signal), errMsg); + }); +}; + +exports.ddCommand = function (filename, kilobytes) { + if (exports.isWindows) { + var p = path.resolve(fixturesDir, 'create-file.js'); + return '"' + process.argv[0] + '" "' + p + '" "' + filename + '" ' + kilobytes * 1024; + } else { + return 'dd if=/dev/zero of="' + filename + '" bs=1024 count=' + kilobytes; + } +}; + +exports.spawnPwd = function (options) { + if (exports.isWindows) { + return spawn('cmd.exe', ['/d', '/c', 'cd'], options); + } else { + return spawn('pwd', [], options); + } +}; + +exports.spawnSyncPwd = function (options) { + if (exports.isWindows) { + return spawnSync('cmd.exe', ['/d', '/c', 'cd'], options); + } else { + return spawnSync('pwd', [], options); + } +}; + +exports.platformTimeout = function (ms) { + if (process.features.debug) ms = 2 * ms; + + if (global.__coverage__) ms = 4 * ms; + + if (exports.isAIX) return 2 * ms; // default localhost speed is slower on AIX + + if (process.arch !== 'arm') return ms; + + var armv = process.config.variables.arm_version; + + if (armv === '6') return 7 * ms; // ARMv6 + + if (armv === '7') return 2 * ms; // ARMv7 + + return ms; // ARMv8+ +}; + +var knownGlobals = [Buffer, clearImmediate, clearInterval, clearTimeout, console, constructor, // Enumerable in V8 3.21. +global, process, setImmediate, setInterval, setTimeout]; + +if (global.gc) { + knownGlobals.push(global.gc); +} + +if (global.DTRACE_HTTP_SERVER_RESPONSE) { + knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE); + knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST); + knownGlobals.push(DTRACE_HTTP_CLIENT_RESPONSE); + knownGlobals.push(DTRACE_HTTP_CLIENT_REQUEST); + knownGlobals.push(DTRACE_NET_STREAM_END); + knownGlobals.push(DTRACE_NET_SERVER_CONNECTION); +} + +if (global.COUNTER_NET_SERVER_CONNECTION) { + knownGlobals.push(COUNTER_NET_SERVER_CONNECTION); + knownGlobals.push(COUNTER_NET_SERVER_CONNECTION_CLOSE); + knownGlobals.push(COUNTER_HTTP_SERVER_REQUEST); + knownGlobals.push(COUNTER_HTTP_SERVER_RESPONSE); + knownGlobals.push(COUNTER_HTTP_CLIENT_REQUEST); + knownGlobals.push(COUNTER_HTTP_CLIENT_RESPONSE); +} + +if (global.LTTNG_HTTP_SERVER_RESPONSE) { + knownGlobals.push(LTTNG_HTTP_SERVER_RESPONSE); + knownGlobals.push(LTTNG_HTTP_SERVER_REQUEST); + knownGlobals.push(LTTNG_HTTP_CLIENT_RESPONSE); + knownGlobals.push(LTTNG_HTTP_CLIENT_REQUEST); + knownGlobals.push(LTTNG_NET_STREAM_END); + knownGlobals.push(LTTNG_NET_SERVER_CONNECTION); +} + +/**/if (!process.browser) { + if (global.ArrayBuffer) { + knownGlobals.push(ArrayBuffer); + knownGlobals.push(Int8Array); + knownGlobals.push(Uint8Array); + knownGlobals.push(Uint8ClampedArray); + knownGlobals.push(Int16Array); + knownGlobals.push(Uint16Array); + knownGlobals.push(Int32Array); + knownGlobals.push(Uint32Array); + knownGlobals.push(Float32Array); + knownGlobals.push(Float64Array); + knownGlobals.push(DataView); + } +} /**/ + +// Harmony features. +if (global.Proxy) { + knownGlobals.push(Proxy); +} + +if (global.Symbol) { + knownGlobals.push(Symbol); +} + +if (process.env.NODE_TEST_KNOWN_GLOBALS) { + var knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(','); + allowGlobals.apply(undefined, _toConsumableArray(knownFromEnv)); +} + +function allowGlobals() { + for (var _len = arguments.length, whitelist = Array(_len), _key = 0; _key < _len; _key++) { + whitelist[_key] = arguments[_key]; + } + + knownGlobals = knownGlobals.concat(whitelist); +} +exports.allowGlobals = allowGlobals; + +/**/ +if (typeof constructor == 'function') knownGlobals.push(constructor); +if (typeof DTRACE_NET_SOCKET_READ == 'function') knownGlobals.push(DTRACE_NET_SOCKET_READ); +if (typeof DTRACE_NET_SOCKET_WRITE == 'function') knownGlobals.push(DTRACE_NET_SOCKET_WRITE); +if (global.__coverage__) knownGlobals.push(__coverage__); +'core,__core-js_shared__,Promise,Map,Set,WeakMap,WeakSet,Reflect,System,asap,Observable,regeneratorRuntime,_babelPolyfill'.split(',').filter(function (item) { + return typeof global[item] !== undefined; +}).forEach(function (item) { + knownGlobals.push(global[item]); +}); /**/ + +function leakedGlobals() { + var leaked = []; + + for (var val in global) { + if (!knownGlobals.includes(global[val])) { + leaked.push(val); + } + } + + if (global.__coverage__) { + return leaked.filter(function (varname) { + return !/^(?:cov_|__cov)/.test(varname); + }); + } else { + return leaked; + } +} +exports.leakedGlobals = leakedGlobals; + +// Turn this off if the test should not check for global leaks. +exports.globalCheck = true; + +process.on('exit', function () { + if (!exports.globalCheck) return; + var leaked = leakedGlobals(); + if (leaked.length > 0) { + assert.fail('Unexpected global(s) found: ' + leaked.join(', ')); + } +}); + +var mustCallChecks = []; + +function runCallChecks(exitCode) { + if (exitCode !== 0) return; + + var failed = mustCallChecks.filter(function (context) { + if ('minimum' in context) { + context.messageSegment = 'at least ' + context.minimum; + return context.actual < context.minimum; + } else { + context.messageSegment = 'exactly ' + context.exact; + return context.actual !== context.exact; + } + }); + + forEach(failed, function (context) { + console.log('Mismatched %s function calls. Expected %s, actual %d.', context.name, context.messageSegment, context.actual); + console.log(context.stack.split('\n').slice(2).join('\n')); + }); + + if (failed.length) process.exit(1); +} + +exports.mustCall = function (fn, exact) { + return _mustCallInner(fn, exact, 'exact'); +}; + +exports.mustCallAtLeast = function (fn, minimum) { + return _mustCallInner(fn, minimum, 'minimum'); +}; + +function _mustCallInner(fn) { + var _context; + + var criteria = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; + var field = arguments[2]; + + if (process._exiting) throw new Error('Cannot use common.mustCall*() in process exit handler'); + if (typeof fn === 'number') { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== 'number') throw new TypeError('Invalid ' + field + ' value: ' + criteria); + + var context = (_context = {}, _defineProperty(_context, field, criteria), _defineProperty(_context, 'actual', 0), _defineProperty(_context, 'stack', new Error().stack), _defineProperty(_context, 'name', fn.name || ''), _context); + + // add the exit listener only once to avoid listener leak warnings + if (mustCallChecks.length === 0) process.on('exit', runCallChecks); + + mustCallChecks.push(context); + + return function () { + context.actual++; + return fn.apply(this, arguments); + }; +} + +exports.hasMultiLocalhost = function hasMultiLocalhost() { + var TCP = process.binding('tcp_wrap').TCP; + var t = new TCP(); + var ret = t.bind('127.0.0.2', 0); + t.close(); + return ret === 0; +}; + +exports.fileExists = function (pathname) { + try { + fs.accessSync(pathname); + return true; + } catch (err) { + return false; + } +}; + +exports.canCreateSymLink = function () { + // On Windows, creating symlinks requires admin privileges. + // We'll only try to run symlink test if we have enough privileges. + // On other platforms, creating symlinks shouldn't need admin privileges + if (exports.isWindows) { + // whoami.exe needs to be the one from System32 + // If unix tools are in the path, they can shadow the one we want, + // so use the full path while executing whoami + var whoamiPath = path.join(process.env['SystemRoot'], 'System32', 'whoami.exe'); + + var err = false; + var output = ''; + + try { + output = execSync(whoamiPath + ' /priv', { timout: 1000 }); + } catch (e) { + err = true; + } finally { + if (err || !output.includes('SeCreateSymbolicLinkPrivilege')) { + return false; + } + } + } + + return true; +}; + +exports.getCallSite = function getCallSite(top) { + var originalStackFormatter = Error.prepareStackTrace; + Error.prepareStackTrace = function (err, stack) { + return stack[0].getFileName() + ':' + stack[0].getLineNumber(); + }; + var err = new Error(); + Error.captureStackTrace(err, top); + // with the V8 Error API, the stack is not formatted until it is accessed + err.stack; + Error.prepareStackTrace = originalStackFormatter; + return err.stack; +}; + +exports.mustNotCall = function (msg) { + var callSite = exports.getCallSite(exports.mustNotCall); + return function mustNotCall() { + assert.fail((msg || 'function should not have been called') + ' at ' + callSite); + }; +}; + +exports.printSkipMessage = function (msg) { + console.log('1..0 # Skipped: ' + msg); +}; + +exports.skip = function (msg) { + exports.printSkipMessage(msg); + process.exit(0); +}; + +// A stream to push an array into a REPL +function ArrayStream() { + this.run = function (data) { + var _this = this; + + forEach(data, function (line) { + _this.emit('data', line + '\n'); + }); + }; +} + +util.inherits(ArrayStream, stream.Stream); +exports.ArrayStream = ArrayStream; +ArrayStream.prototype.readable = true; +ArrayStream.prototype.writable = true; +ArrayStream.prototype.pause = noop; +ArrayStream.prototype.resume = noop; +ArrayStream.prototype.write = noop; + +// Returns true if the exit code "exitCode" and/or signal name "signal" +// represent the exit code and/or signal name of a node process that aborted, +// false otherwise. +exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) { + // Depending on the compiler used, node will exit with either + // exit code 132 (SIGILL), 133 (SIGTRAP) or 134 (SIGABRT). + var expectedExitCodes = [132, 133, 134]; + + // On platforms using KSH as the default shell (like SmartOS), + // when a process aborts, KSH exits with an exit code that is + // greater than 256, and thus the exit code emitted with the 'exit' + // event is null and the signal is set to either SIGILL, SIGTRAP, + // or SIGABRT (depending on the compiler). + var expectedSignals = ['SIGILL', 'SIGTRAP', 'SIGABRT']; + + // On Windows, 'aborts' are of 2 types, depending on the context: + // (i) Forced access violation, if --abort-on-uncaught-exception is on + // which corresponds to exit code 3221225477 (0xC0000005) + // (ii) raise(SIGABRT) or abort(), which lands up in CRT library calls + // which corresponds to exit code 3. + if (exports.isWindows) expectedExitCodes = [3221225477, 3]; + + // When using --abort-on-uncaught-exception, V8 will use + // base::OS::Abort to terminate the process. + // Depending on the compiler used, the shell or other aspects of + // the platform used to build the node binary, this will actually + // make V8 exit by aborting or by raising a signal. In any case, + // one of them (exit code or signal) needs to be set to one of + // the expected exit codes or signals. + if (signal !== null) { + return expectedSignals.includes(signal); + } else { + return expectedExitCodes.includes(exitCode); + } +}; + +exports.busyLoop = function busyLoop(time) { + var startTime = Timer.now(); + var stopTime = startTime + time; + while (Timer.now() < stopTime) {} +}; + +exports.isAlive = function isAlive(pid) { + try { + process.kill(pid, 'SIGCONT'); + return true; + } catch (e) { + return false; + } +}; + +function expectWarning(name, expectedMessages) { + return exports.mustCall(function (warning) { + assert.strictEqual(warning.name, name); + assert.ok(expectedMessages.includes(warning.message), 'unexpected error message: "' + warning.message + '"'); + // Remove a warning message after it is seen so that we guarantee that we + // get each message only once. + expectedMessages.splice(expectedMessages.indexOf(warning.message), 1); + }, expectedMessages.length); +} + +function expectWarningByName(name, expected) { + if (typeof expected === 'string') { + expected = [expected]; + } + process.on('warning', expectWarning(name, expected)); +} + +function expectWarningByMap(warningMap) { + var catchWarning = {}; + forEach(objectKeys(warningMap), function (name) { + var expected = warningMap[name]; + if (typeof expected === 'string') { + expected = [expected]; + } + catchWarning[name] = expectWarning(name, expected); + }); + process.on('warning', function (warning) { + return catchWarning[warning.name](warning); + }); +} + +// accepts a warning name and description or array of descriptions or a map +// of warning names to description(s) +// ensures a warning is generated for each name/description pair +exports.expectWarning = function (nameOrMap, expected) { + if (typeof nameOrMap === 'string') { + expectWarningByName(nameOrMap, expected); + } else { + expectWarningByMap(nameOrMap); + } +}; + +/**/if (!process.browser) { + Object.defineProperty(exports, 'hasIntl', { + get: function () { + return process.binding('config').hasIntl; + } + }); +} /**/ + +/**/if (!process.browser) { + Object.defineProperty(exports, 'hasSmallICU', { + get: function () { + return process.binding('config').hasSmallICU; + } + }); +} /**/ + +// Useful for testing expected internal/error objects +exports.expectsError = function expectsError(fn, settings, exact) { + if (typeof fn !== 'function') { + exact = settings; + settings = fn; + fn = undefined; + } + var innerFn = exports.mustCall(function (error) { + assert.strictEqual(error.code, settings.code); + if ('type' in settings) { + var type = settings.type; + if (type !== Error && !Error.isPrototypeOf(type)) { + throw new TypeError('`settings.type` must inherit from `Error`'); + } + assert(error instanceof type, error.name + ' is not instance of ' + type.name); + } + if ('message' in settings) { + var message = settings.message; + if (typeof message === 'string') { + assert.strictEqual(error.message, message); + } else { + assert(message.test(error.message), error.message + ' does not match ' + message); + } + } + if ('name' in settings) { + assert.strictEqual(error.name, settings.name); + } + if (error.constructor.name === 'AssertionError') { + forEach(['generatedMessage', 'actual', 'expected', 'operator'], function (key) { + if (key in settings) { + var actual = error[key]; + var expected = settings[key]; + assert.strictEqual(actual, expected, key + ': expected ' + expected + ', not ' + actual); + } + }); + } + return true; + }, exact); + if (fn) { + assert.throws(fn, innerFn); + return; + } + return innerFn; +}; + +exports.skipIfInspectorDisabled = function skipIfInspectorDisabled() { + if (process.config.variables.v8_enable_inspector === 0) { + exports.skip('V8 inspector is disabled'); + } +}; + +exports.skipIf32Bits = function skipIf32Bits() { + if (process.binding('config').bits < 64) { + exports.skip('The tested feature is not available in 32bit builds'); + } +}; + +var arrayBufferViews = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, DataView]; + +exports.getArrayBufferViews = function getArrayBufferViews(buf) { + var buffer = buf.buffer, + byteOffset = buf.byteOffset, + byteLength = buf.byteLength; + + + var out = []; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = arrayBufferViews[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var type = _step.value; + var _type$BYTES_PER_ELEME = type.BYTES_PER_ELEMENT, + BYTES_PER_ELEMENT = _type$BYTES_PER_ELEME === undefined ? 1 : _type$BYTES_PER_ELEME; + + if (byteLength % BYTES_PER_ELEMENT === 0) { + out.push(new type(buffer, byteOffset, byteLength / BYTES_PER_ELEMENT)); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return out; +}; + +// Crash the process on unhandled rejections. +exports.crashOnUnhandledRejection = function () { + process.on('unhandledRejection', function (err) { + return process.nextTick(function () { + throw err; + }); + }); +}; + +exports.getTTYfd = function getTTYfd() { + var tty = require('tty'); + var tty_fd = 0; + if (!tty.isatty(tty_fd)) tty_fd++;else if (!tty.isatty(tty_fd)) tty_fd++;else if (!tty.isatty(tty_fd)) tty_fd++;else { + try { + tty_fd = fs.openSync('/dev/tty'); + } catch (e) { + // There aren't any tty fd's available to use. + return -1; + } + } + return tty_fd; +}; + +// Hijack stdout and stderr +var stdWrite = {}; +function hijackStdWritable(name, listener) { + var stream = process[name]; + var _write = stdWrite[name] = stream.write; + + stream.writeTimes = 0; + stream.write = function (data, callback) { + try { + listener(data); + } catch (e) { + process.nextTick(function () { + throw e; + }); + } + + _write.call(stream, data, callback); + stream.writeTimes++; + }; +} + +function restoreWritable(name) { + process[name].write = stdWrite[name]; + delete process[name].writeTimes; +} + +function onResolvedOrRejected(promise, callback) { + return promise.then(function (result) { + callback(); + return result; + }, function (error) { + callback(); + throw error; + }); +} + +function timeoutPromise(error, timeoutMs) { + var clearCallback = null; + var done = false; + var promise = onResolvedOrRejected(new Promise(function (resolve, reject) { + var timeout = setTimeout(function () { + return reject(error); + }, timeoutMs); + clearCallback = function () { + if (done) return; + clearTimeout(timeout); + resolve(); + }; + }), function () { + return done = true; + }); + promise.clear = clearCallback; + return promise; +} + +exports.hijackStdout = hijackStdWritable.bind(null, 'stdout'); +exports.hijackStderr = hijackStdWritable.bind(null, 'stderr'); +exports.restoreStdout = restoreWritable.bind(null, 'stdout'); +exports.restoreStderr = restoreWritable.bind(null, 'stderr'); + +var fd = 2; +exports.firstInvalidFD = function firstInvalidFD() { + // Get first known bad file descriptor. + try { + while (fs.fstatSync(++fd)) {} + } catch (e) {} + return fd; +}; + +exports.fires = function fires(promise, error, timeoutMs) { + if (!timeoutMs && util.isNumber(error)) { + timeoutMs = error; + error = null; + } + if (!error) error = 'timeout'; + if (!timeoutMs) timeoutMs = 100; + var timeout = timeoutPromise(error, timeoutMs); + return Promise.race([onResolvedOrRejected(promise, function () { + return timeout.clear(); + }), timeout]); +}; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} + +if (!util._errnoException) { + var uv; + util._errnoException = function (err, syscall) { + if (util.isUndefined(uv)) try { + uv = process.binding('uv'); + } catch (e) {} + var errname = uv ? uv.errname(err) : ''; + var e = new Error(syscall + ' ' + errname); + e.code = errname; + e.errno = errname; + e.syscall = syscall; + return e; + }; +} \ No newline at end of file diff --git a/test/common/index.mjs b/test/common/index.mjs new file mode 100644 index 0000000..ad89e9d --- /dev/null +++ b/test/common/index.mjs @@ -0,0 +1,131 @@ +/**/ + require('babel-polyfill'); + var util = require('util'); + for (var i in util) exports[i] = util[i]; + /**//**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +// Flags: --experimental-modules +/* eslint-disable required-modules */ + +import assert from 'assert'; + +let knownGlobals = [ + Buffer, + clearImmediate, + clearInterval, + clearTimeout, + console, + constructor, // Enumerable in V8 3.21. + global, + process, + setImmediate, + setInterval, + setTimeout +]; + +if (process.env.NODE_TEST_KNOWN_GLOBALS) { + const knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(','); + allowGlobals(...knownFromEnv); +} + +export function allowGlobals(...whitelist) { + knownGlobals = knownGlobals.concat(whitelist); +} + +export function leakedGlobals() { + //add possible expected globals + if (global.gc) { + knownGlobals.push(global.gc); + } + + if (global.DTRACE_HTTP_SERVER_RESPONSE) { + knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE); + knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST); + knownGlobals.push(DTRACE_HTTP_CLIENT_RESPONSE); + knownGlobals.push(DTRACE_HTTP_CLIENT_REQUEST); + knownGlobals.push(DTRACE_NET_STREAM_END); + knownGlobals.push(DTRACE_NET_SERVER_CONNECTION); + } + + if (global.COUNTER_NET_SERVER_CONNECTION) { + knownGlobals.push(COUNTER_NET_SERVER_CONNECTION); + knownGlobals.push(COUNTER_NET_SERVER_CONNECTION_CLOSE); + knownGlobals.push(COUNTER_HTTP_SERVER_REQUEST); + knownGlobals.push(COUNTER_HTTP_SERVER_RESPONSE); + knownGlobals.push(COUNTER_HTTP_CLIENT_REQUEST); + knownGlobals.push(COUNTER_HTTP_CLIENT_RESPONSE); + } + + if (global.LTTNG_HTTP_SERVER_RESPONSE) { + knownGlobals.push(LTTNG_HTTP_SERVER_RESPONSE); + knownGlobals.push(LTTNG_HTTP_SERVER_REQUEST); + knownGlobals.push(LTTNG_HTTP_CLIENT_RESPONSE); + knownGlobals.push(LTTNG_HTTP_CLIENT_REQUEST); + knownGlobals.push(LTTNG_NET_STREAM_END); + knownGlobals.push(LTTNG_NET_SERVER_CONNECTION); + } + + if (global.ArrayBuffer) { + knownGlobals.push(ArrayBuffer); + knownGlobals.push(Int8Array); + knownGlobals.push(Uint8Array); + knownGlobals.push(Uint8ClampedArray); + knownGlobals.push(Int16Array); + knownGlobals.push(Uint16Array); + knownGlobals.push(Int32Array); + knownGlobals.push(Uint32Array); + knownGlobals.push(Float32Array); + knownGlobals.push(Float64Array); + knownGlobals.push(DataView); + } + + // Harmony features. + if (global.Proxy) { + knownGlobals.push(Proxy); + } + + if (global.Symbol) { + knownGlobals.push(Symbol); + } + + const leaked = []; + + for (const val in global) { + if (!knownGlobals.includes(global[val])) { + leaked.push(val); + } + } + + if (global.__coverage__) { + return leaked.filter((varname) => !/^(?:cov_|__cov)/.test(varname)); + } else { + return leaked; + } +} + +// Turn this off if the test should not check for global leaks. +export let globalCheck = true; // eslint-disable-line + +process.on('exit', function() { + if (!globalCheck) return; + const leaked = leakedGlobals(); + if (leaked.length > 0) { + assert.fail(`Unexpected global(s) found: ${leaked.join(', ')}`); + } +}); + +function forEach (xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js new file mode 100644 index 0000000..6f3e818 --- /dev/null +++ b/test/common/inspector-helper.js @@ -0,0 +1,521 @@ +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var common = require('../common'); +var assert = require('assert'); +var fs = require('fs'); +var http = require('http'); +var fixtures = require('../common/fixtures'); + +var _require = require('child_process'), + spawn = _require.spawn; + +var url = require('url'); + +var _MAINSCRIPT = fixtures.path('loop.js'); +var DEBUG = false; +var TIMEOUT = common.platformTimeout(15 * 1000); + +function spawnChildProcess(inspectorFlags, scriptContents, scriptFile) { + var args = [].concat(inspectorFlags); + if (scriptContents) { + args.push('-e', scriptContents); + } else { + args.push(scriptFile); + } + var child = spawn(process.execPath, args); + + var handler = tearDown.bind(null, child); + process.on('exit', handler); + process.on('uncaughtException', handler); + process.on('unhandledRejection', handler); + process.on('SIGINT', handler); + + return child; +} + +function makeBufferingDataCallback(dataCallback) { + var buffer = Buffer.alloc(0); + return function (data) { + var newData = Buffer.concat([buffer, data]); + var str = newData.toString('utf8'); + var lines = str.replace(/\r/g, '').split('\n'); + if (str.endsWith('\n')) buffer = Buffer.alloc(0);else buffer = Buffer.from(lines.pop(), 'utf8'); + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = lines[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var line = _step.value; + + dataCallback(line); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + }; +} + +function tearDown(child, err) { + child.kill(); + if (err) { + console.error(err); + process.exit(1); + } +} + +function parseWSFrame(buffer) { + // Protocol described in https://tools.ietf.org/html/rfc6455#section-5 + var message = null; + if (buffer.length < 2) return { length: 0, message: message }; + if (buffer[0] === 0x88 && buffer[1] === 0x00) { + return { length: 2, message: message, closed: true }; + } + assert.strictEqual(0x81, buffer[0]); + var dataLen = 0x7F & buffer[1]; + var bodyOffset = 2; + if (buffer.length < bodyOffset + dataLen) return 0; + if (dataLen === 126) { + dataLen = buffer.readUInt16BE(2); + bodyOffset = 4; + } else if (dataLen === 127) { + assert(buffer[2] === 0 && buffer[3] === 0, 'Inspector message too big'); + dataLen = buffer.readUIntBE(4, 6); + bodyOffset = 10; + } + if (buffer.length < bodyOffset + dataLen) return { length: 0, message: message }; + var jsonPayload = buffer.slice(bodyOffset, bodyOffset + dataLen).toString('utf8'); + try { + message = JSON.parse(jsonPayload); + } catch (e) { + console.error('JSON.parse() failed for: ' + jsonPayload); + throw e; + } + if (DEBUG) console.log('[received]', JSON.stringify(message)); + return { length: bodyOffset + dataLen, message: message }; +} + +function formatWSFrame(message) { + var messageBuf = Buffer.from(JSON.stringify(message)); + + var wsHeaderBuf = Buffer.allocUnsafe(16); + wsHeaderBuf.writeUInt8(0x81, 0); + var byte2 = 0x80; + var bodyLen = messageBuf.length; + + var maskOffset = 2; + if (bodyLen < 126) { + byte2 = 0x80 + bodyLen; + } else if (bodyLen < 65536) { + byte2 = 0xFE; + wsHeaderBuf.writeUInt16BE(bodyLen, 2); + maskOffset = 4; + } else { + byte2 = 0xFF; + wsHeaderBuf.writeUInt32BE(bodyLen, 2); + wsHeaderBuf.writeUInt32BE(0, 6); + maskOffset = 10; + } + wsHeaderBuf.writeUInt8(byte2, 1); + wsHeaderBuf.writeUInt32BE(0x01020408, maskOffset); + + for (var _i = 0; _i < messageBuf.length; _i++) { + messageBuf[_i] = messageBuf[_i] ^ 1 << _i % 4; + }return Buffer.concat([wsHeaderBuf.slice(0, maskOffset + 4), messageBuf]); +} + +var InspectorSession = function () { + function InspectorSession(socket, instance) { + var _this = this; + + _classCallCheck(this, InspectorSession); + + this._instance = instance; + this._socket = socket; + this._nextId = 1; + this._commandResponsePromises = new Map(); + this._unprocessedNotifications = []; + this._notificationCallback = null; + this._scriptsIdsByUrl = new Map(); + + var buffer = Buffer.alloc(0); + socket.on('data', function (data) { + buffer = Buffer.concat([buffer, data]); + do { + var _parseWSFrame = parseWSFrame(buffer), + length = _parseWSFrame.length, + message = _parseWSFrame.message, + closed = _parseWSFrame.closed; + + if (!length) break; + + if (closed) { + socket.write(Buffer.from([0x88, 0x00])); // WS close frame + } + buffer = buffer.slice(length); + if (message) _this._onMessage(message); + } while (true); + }); + this._terminationPromise = new Promise(function (resolve) { + socket.once('close', resolve); + }); + } + + InspectorSession.prototype.waitForServerDisconnect = function waitForServerDisconnect() { + return this._terminationPromise; + }; + + InspectorSession.prototype.disconnect = function disconnect() { + this._socket.destroy(); + }; + + InspectorSession.prototype._onMessage = function _onMessage(message) { + if (message.id) { + var _commandResponsePromi = this._commandResponsePromises.get(message.id), + resolve = _commandResponsePromi.resolve, + reject = _commandResponsePromi.reject; + + this._commandResponsePromises.delete(message.id); + if (message.result) resolve(message.result);else reject(message.error); + } else { + if (message.method === 'Debugger.scriptParsed') { + var script = message['params']; + var scriptId = script['scriptId']; + var _url = script['url']; + this._scriptsIdsByUrl.set(scriptId, _url); + if (_url === _MAINSCRIPT) this.mainScriptId = scriptId; + } + + if (this._notificationCallback) { + // In case callback needs to install another + var callback = this._notificationCallback; + this._notificationCallback = null; + callback(message); + } else { + this._unprocessedNotifications.push(message); + } + } + }; + + InspectorSession.prototype._sendMessage = function _sendMessage(message) { + var _this2 = this; + + var msg = JSON.parse(JSON.stringify(message)); // Clone! + msg['id'] = this._nextId++; + if (DEBUG) console.log('[sent]', JSON.stringify(msg)); + + var responsePromise = new Promise(function (resolve, reject) { + _this2._commandResponsePromises.set(msg['id'], { resolve: resolve, reject: reject }); + }); + + return new Promise(function (resolve) { + return _this2._socket.write(formatWSFrame(msg), resolve); + }).then(function () { + return responsePromise; + }); + }; + + InspectorSession.prototype.send = function send(commands) { + var _this3 = this; + + if (Array.isArray(commands)) { + // Multiple commands means the response does not matter. There might even + // never be a response. + return Promise.all(commands.map(function (command) { + return _this3._sendMessage(command); + })).then(function () {}); + } else { + return this._sendMessage(commands); + } + }; + + InspectorSession.prototype.waitForNotification = function waitForNotification(methodOrPredicate, description) { + var desc = description || methodOrPredicate; + var message = 'Timed out waiting for matching notification (' + desc + '))'; + return common.fires(this._asyncWaitForNotification(methodOrPredicate), message, TIMEOUT); + }; + + InspectorSession.prototype._asyncWaitForNotification = async function _asyncWaitForNotification(methodOrPredicate) { + var _this4 = this; + + function matchMethod(notification) { + return notification.method === methodOrPredicate; + } + var predicate = typeof methodOrPredicate === 'string' ? matchMethod : methodOrPredicate; + var notification = null; + do { + if (this._unprocessedNotifications.length) { + notification = this._unprocessedNotifications.shift(); + } else { + notification = await new Promise(function (resolve) { + return _this4._notificationCallback = resolve; + }); + } + } while (!predicate(notification)); + return notification; + }; + + InspectorSession.prototype._isBreakOnLineNotification = function _isBreakOnLineNotification(message, line, url) { + if ('Debugger.paused' === message['method']) { + var callFrame = message['params']['callFrames'][0]; + var location = callFrame['location']; + assert.strictEqual(url, this._scriptsIdsByUrl.get(location['scriptId'])); + assert.strictEqual(line, location['lineNumber']); + return true; + } + }; + + InspectorSession.prototype.waitForBreakOnLine = function waitForBreakOnLine(line, url) { + var _this5 = this; + + return this.waitForNotification(function (notification) { + return _this5._isBreakOnLineNotification(notification, line, url); + }, 'break on ' + url + ':' + line); + }; + + InspectorSession.prototype._matchesConsoleOutputNotification = function _matchesConsoleOutputNotification(notification, type, values) { + if (!Array.isArray(values)) values = [values]; + if ('Runtime.consoleAPICalled' === notification['method']) { + var params = notification['params']; + if (params['type'] === type) { + var _i2 = 0; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = params['args'][Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var value = _step2.value; + + if (value['value'] !== values[_i2++]) return false; + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + return _i2 === values.length; + } + } + }; + + InspectorSession.prototype.waitForConsoleOutput = function waitForConsoleOutput(type, values) { + var _this6 = this; + + var desc = 'Console output matching ' + JSON.stringify(values); + return this.waitForNotification(function (notification) { + return _this6._matchesConsoleOutputNotification(notification, type, values); + }, desc); + }; + + InspectorSession.prototype.runToCompletion = async function runToCompletion() { + console.log('[test]', 'Verify node waits for the frontend to disconnect'); + await this.send({ 'method': 'Debugger.resume' }); + await this.waitForNotification(function (notification) { + return notification.method === 'Runtime.executionContextDestroyed' && notification.params.executionContextId === 1; + }); + while ((await this._instance.nextStderrString()) !== 'Waiting for the debugger to disconnect...') {} + await this.disconnect(); + }; + + return InspectorSession; +}(); + +var NodeInstance = function () { + function NodeInstance() { + var inspectorFlags = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['--inspect-brk=0']; + + var _this7 = this; + + var scriptContents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + var scriptFile = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _MAINSCRIPT; + + _classCallCheck(this, NodeInstance); + + this._portCallback = null; + this.portPromise = new Promise(function (resolve) { + return _this7._portCallback = resolve; + }); + this._process = spawnChildProcess(inspectorFlags, scriptContents, scriptFile); + this._running = true; + this._stderrLineCallback = null; + this._unprocessedStderrLines = []; + + this._process.stdout.on('data', makeBufferingDataCallback(function (line) { + return console.log('[out]', line); + })); + + this._process.stderr.on('data', makeBufferingDataCallback(function (message) { + return _this7.onStderrLine(message); + })); + + this._shutdownPromise = new Promise(function (resolve) { + _this7._process.once('exit', function (exitCode, signal) { + resolve({ exitCode: exitCode, signal: signal }); + _this7._running = false; + }); + }); + } + + NodeInstance.startViaSignal = async function startViaSignal(scriptContents) { + var instance = new NodeInstance([], scriptContents + '\nprocess._rawDebug(\'started\');', undefined); + var msg = 'Timed out waiting for process to start'; + while ((await common.fires(instance.nextStderrString(), msg, TIMEOUT)) !== 'started') {} + process._debugProcess(instance._process.pid); + return instance; + }; + + NodeInstance.prototype.onStderrLine = function onStderrLine(line) { + console.log('[err]', line); + if (this._portCallback) { + var matches = line.match(/Debugger listening on ws:\/\/.+:(\d+)\/.+/); + if (matches) { + this._portCallback(matches[1]); + this._portCallback = null; + } + } + if (this._stderrLineCallback) { + this._stderrLineCallback(line); + this._stderrLineCallback = null; + } else { + this._unprocessedStderrLines.push(line); + } + }; + + NodeInstance.prototype.httpGet = function httpGet(host, path) { + console.log('[test]', 'Testing ' + path); + return this.portPromise.then(function (port) { + return new Promise(function (resolve, reject) { + var req = http.get({ host: host, port: port, path: path }, function (res) { + var response = ''; + res.setEncoding('utf8'); + res.on('data', function (data) { + return response += data.toString(); + }).on('end', function () { + resolve(response); + }); + }); + req.on('error', reject); + }); + }).then(function (response) { + try { + return JSON.parse(response); + } catch (e) { + e.body = response; + throw e; + } + }); + }; + + NodeInstance.prototype.wsHandshake = function wsHandshake(devtoolsUrl) { + var _this8 = this; + + return this.portPromise.then(function (port) { + return new Promise(function (resolve) { + http.get({ + port: port, + path: url.parse(devtoolsUrl).path, + headers: { + 'Connection': 'Upgrade', + 'Upgrade': 'websocket', + 'Sec-WebSocket-Version': 13, + 'Sec-WebSocket-Key': 'key==' + } + }).on('upgrade', function (message, socket) { + resolve(new InspectorSession(socket, _this8)); + }).on('response', common.mustNotCall('Upgrade was not received')); + }); + }); + }; + + NodeInstance.prototype.connectInspectorSession = async function connectInspectorSession() { + console.log('[test]', 'Connecting to a child Node process'); + var response = await this.httpGet(null, '/json/list'); + var url = response[0]['webSocketDebuggerUrl']; + return this.wsHandshake(url); + }; + + NodeInstance.prototype.expectShutdown = function expectShutdown() { + return this._shutdownPromise; + }; + + NodeInstance.prototype.nextStderrString = function nextStderrString() { + var _this9 = this; + + if (this._unprocessedStderrLines.length) return Promise.resolve(this._unprocessedStderrLines.shift()); + return new Promise(function (resolve) { + return _this9._stderrLineCallback = resolve; + }); + }; + + NodeInstance.prototype.kill = function kill() { + this._process.kill(); + }; + + return NodeInstance; +}(); + +function readMainScriptSource() { + return fs.readFileSync(_MAINSCRIPT, 'utf8'); +} + +module.exports = { + mainScriptPath: _MAINSCRIPT, + readMainScriptSource: readMainScriptSource, + NodeInstance: NodeInstance +}; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/common/wpt.js b/test/common/wpt.js new file mode 100644 index 0000000..22a5c89 --- /dev/null +++ b/test/common/wpt.js @@ -0,0 +1,64 @@ +/**/ +require('babel-polyfill'); +var util = require('util'); +for (var i in util) { + exports[i] = util[i]; +} /**/ /**/ +if (!global.setImmediate) { + global.setImmediate = function setImmediate(fn) { + return setTimeout(fn.bind.apply(fn, arguments), 4); + }; +} +if (!global.clearImmediate) { + global.clearImmediate = function clearImmediate(i) { + return clearTimeout(i); + }; +} +/**/ +/* eslint-disable required-modules */ +'use strict'; + +/**/ +var objectKeys = objectKeys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +var assert = require('assert'); + +// https://github.com/w3c/testharness.js/blob/master/testharness.js +module.exports = { + test: function (fn, desc) { + try { + fn(); + } catch (err) { + console.error('In ' + desc + ':'); + throw err; + } + }, + assert_equals: assert.strictEqual, + assert_true: function (value, message) { + return assert.strictEqual(value, true, message); + }, + assert_false: function (value, message) { + return assert.strictEqual(value, false, message); + }, + assert_throws: function (code, func, desc) { + assert.throws(func, function (err) { + return typeof err === 'object' && 'name' in err && err.name.startsWith(code.name); + }, desc); + }, + assert_array_equals: assert.deepStrictEqual, + assert_unreached: function (desc) { + assert.fail('Reached unreachable code: ' + desc); + } +}; + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} \ No newline at end of file diff --git a/test/parallel/test-string-decoder-end.js b/test/parallel/test-string-decoder-end.js index 2c9b5ac..32abc39 100644 --- a/test/parallel/test-string-decoder-end.js +++ b/test/parallel/test-string-decoder-end.js @@ -1,3 +1,24 @@ +// 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'; var bufferShim = require('safe-buffer').Buffer; @@ -24,8 +45,6 @@ for (var i = 1; i <= 16; i++) { encodings.forEach(testEncoding); -console.log('ok'); - function testEncoding(encoding) { bufs.forEach(function (buf) { testBuf(encoding, buf); @@ -33,8 +52,6 @@ function testEncoding(encoding) { } function testBuf(encoding, buf) { - console.error('# %s', encoding, buf); - // write one byte at a time. var s = new SD(encoding); var res1 = ''; @@ -52,9 +69,6 @@ function testBuf(encoding, buf) { // .toString() on the buffer var res3 = buf.toString(encoding); - console.log('expect=%j', res3); - console.log('res1=%j', res1); - console.log('res2=%j', res2); assert.strictEqual(res1, res3, 'one byte at a time should match toString'); assert.strictEqual(res2, res3, 'all bytes at once should match toString'); } \ No newline at end of file diff --git a/test/parallel/test-string-decoder.js b/test/parallel/test-string-decoder.js index 61d6534..af4f095 100644 --- a/test/parallel/test-string-decoder.js +++ b/test/parallel/test-string-decoder.js @@ -1,3 +1,24 @@ +// 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'; var bufferShim = require('safe-buffer').Buffer; @@ -10,8 +31,6 @@ var StringDecoder = require('../../').StringDecoder; var decoder = new StringDecoder(); assert.strictEqual(decoder.encoding, 'utf8'); -process.stdout.write('scanning '); - // UTF-8 test('utf-8', bufferShim.from('$', 'utf-8'), '$'); test('utf-8', bufferShim.from('¢', 'utf-8'), '¢'); @@ -40,22 +59,17 @@ test('utf-8', bufferShim.from('C9B5A941', 'hex'), '\u0275\ufffdA'); test('utf-8', bufferShim.from('E2', 'hex'), '\ufffd'); test('utf-8', bufferShim.from('E241', 'hex'), '\ufffdA'); test('utf-8', bufferShim.from('CCCCB8', 'hex'), '\ufffd\u0338'); -test('utf-8', bufferShim.from('F0B841', 'hex'), '\ufffd\ufffdA'); test('utf-8', bufferShim.from('F1CCB8', 'hex'), '\ufffd\u0338'); test('utf-8', bufferShim.from('F0FB00', 'hex'), '\ufffd\ufffd\0'); -test('utf-8', bufferShim.from('CCE2B8B8', 'hex'), '\ufffd\u2e38'); -test('utf-8', bufferShim.from('E2B8CCB8', 'hex'), '\ufffd\ufffd\u0338'); test('utf-8', bufferShim.from('E2FBCC01', 'hex'), '\ufffd\ufffd\ufffd\u0001'); test('utf-8', bufferShim.from('CCB8CDB9', 'hex'), '\u0338\u0379'); - +// CESU-8 of U+1D40D // UCS-2 test('ucs2', bufferShim.from('ababc', 'ucs2'), 'ababc'); // UTF-16LE test('utf16le', bufferShim.from('3DD84DDC', 'hex'), '\ud83d\udc4d'); // thumbs up -console.log(' crayon!'); - // Additional UTF-8 tests decoder = new StringDecoder('utf8'); assert.strictEqual(decoder.write(bufferShim.from('E1', 'hex')), ''); @@ -63,7 +77,7 @@ assert.strictEqual(decoder.end(), '\ufffd'); decoder = new StringDecoder('utf8'); assert.strictEqual(decoder.write(bufferShim.from('E18B', 'hex')), ''); -assert.strictEqual(decoder.end(), '\ufffd\ufffd'); +assert.strictEqual(decoder.end(), '\ufffd'); decoder = new StringDecoder('utf8'); assert.strictEqual(decoder.write(bufferShim.from('\ufffd')), '\ufffd'); @@ -122,6 +136,7 @@ function test(encoding, input, expected, singleSequence) { } else { sequences = [singleSequence]; } + var hexNumberRE = /.{2}/g; sequences.forEach(function (sequence) { var decoder = new StringDecoder(encoding); var output = ''; @@ -129,9 +144,8 @@ function test(encoding, input, expected, singleSequence) { output += decoder.write(input.slice(write[0], write[1])); }); output += decoder.end(); - process.stdout.write('.'); if (output !== expected) { - var message = 'Expected "' + unicodeEscape(expected) + '", ' + 'but got "' + unicodeEscape(output) + '"\n' + 'input: ' + input.toString('hex').match(/.{2}/g) + '\n' + 'Write sequence: ' + JSON.stringify(sequence) + '\n' + 'Full Decoder State: ' + inspect(decoder); + var message = 'Expected "' + unicodeEscape(expected) + '", ' + ('but got "' + unicodeEscape(output) + '"\n') + ('input: ' + input.toString('hex').match(hexNumberRE) + '\n') + ('Write sequence: ' + JSON.stringify(sequence) + '\n') + ('Full Decoder State: ' + inspect(decoder)); assert.fail(output, expected, message); } });