diff --git a/.eslintignore b/.eslintignore index cb8a79b79..5f820b4f3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ js/deps/ js/modules/inherits.js +node_modules diff --git a/.eslintrc b/.eslintrc index e4336abdc..dbc51a25b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,7 +18,7 @@ rules: max-len: - 1 - - code: 100 + code: 160 tabWidth: 2 ignoreComments: true ignoreUrls: true @@ -26,6 +26,7 @@ rules: no-underscore-dangle: 0 global-require: 0 import/no-extraneous-dependencies: 0 + import/no-unresolved: [2, { commonjs: true, ignore: ['^\/js', 'process'] }] no-return-assign: 1 new-cap: 1 no-console: 0 diff --git a/.gitignore b/.gitignore index 93f728c6c..314c2d04a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ disk/boot/initrd-downloaded disk/boot/runtime-downloaded netdump.txt /js/node_modules +/js/test/node_modules +/js/test/unit/node_modules /node_modules /out /js/initrd diff --git a/.runtimeignore b/.runtimeignore index faa610257..d96bb9af4 100644 --- a/.runtimeignore +++ b/.runtimeignore @@ -3,12 +3,11 @@ /node_modules/eslint-plugin-runtime-internal /node_modules/runtime-tools /node_modules/runtimeify +/node_modules/runtime-cli /node_modules/tape /node_modules/eslint-config-runtime /test -tape/ module-singleton/test-modules/ -test.js README.md LICENSE.isaac LICENSE @@ -18,6 +17,7 @@ LICENSE /tmp /scripts /testcc +/test /disk /docker /docs diff --git a/js/__loader.js b/js/__loader.js index 2e9ac3f60..03fbdf5c6 100644 --- a/js/__loader.js +++ b/js/__loader.js @@ -14,292 +14,62 @@ 'use strict'; -(() => { - // from https://github.com/runtimejs/runtime-module-loader/blob/master/index.js - class Loader { - constructor(existsFileFn, readFileFn, evalScriptFn, builtins = {}, builtinsResolveFrom = '/') { - const cache = {}; - const builtinsResolveFromComponents = builtinsResolveFrom.split('/'); - - function throwError(err) { - throw err; - } - - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - function normalizePath(components) { - const r = []; - for (const p of components) { - if (p === '') { - if (r.length === 0) { - r.push(p); - } - continue; - } - - if (p === '.') { - continue; - } - - if (p === '..') { - if (r.length > 0) { - r.pop(); - } else { - return null; - } - } else { - r.push(p); - } - } - - return r; - } - - function loadAsFile(path) { - if (existsFileFn(path)) return path; - if (existsFileFn(`${path}.js`)) return `${path}.js`; - if (existsFileFn(`${path}.json`)) return `${path}.json`; - if (existsFileFn(`${path}.node`)) return `${path}.node`; - - return null; - } - - function getPackageMain(packageJsonFile) { - const json = readFileFn(packageJsonFile); - let parsed = null; - try { - parsed = JSON.parse(json); - } catch (e) { - throwError(new Error(`package.json '${packageJsonFile}' parse error`)); - } - - if (parsed.runtime) { - if (typeof parsed.runtime === 'string') { - return parsed.runtime; - } - throwError(new Error(`package.json '${packageJsonFile}' runtime field value is invalid`)); - } - - return parsed.main || 'index.js'; - } - - function loadAsDirectory(path, ignoreJson) { - let mainFile = 'index'; - let dir = false; - if (!ignoreJson && existsFileFn(`${path}/package.json`)) { - mainFile = getPackageMain(`${path}/package.json`) || 'index'; - dir = true; - } - - const normalizedPath = normalizePath(path.split('/').concat(mainFile.split('/'))); - if (!normalizedPath) { - return null; - } - - const s = normalizedPath.join('/'); - const res = loadAsFile(s); - if (res) { - return res; - } - - if (dir) { - return loadAsDirectory(s, true); - } - - return null; - } - - function loadNodeModules(dirComponents, parts) { - let count = dirComponents.length; - - while (count-- > 0) { - let p = dirComponents.slice(0, count + 1); - if (p.length === 0) { - continue; - } - - if (p[p.length - 1] === 'node_modules') { - continue; - } - - p.push('node_modules'); - p = p.concat(parts); - - const normalizedPath = normalizePath(p); - if (!normalizedPath) { - continue; - } - - const s = normalizedPath.join('/'); - const loadedPath = loadAsFile(s) || loadAsDirectory(s, false) || null; - if (loadedPath) { - return loadedPath; - } - } - - return null; - } - - function resolve(module, pathOpt = '') { - let path = String(pathOpt); - - let resolveFrom = module.dirComponents; - - if (Object.prototype.hasOwnProperty.call(builtins, path)) { - path = builtins[path]; - resolveFrom = builtinsResolveFromComponents; - } - - const pathComponents = path.split('/'); - const firstPathComponent = pathComponents[0]; - - // starts with ./ ../ or / - if (firstPathComponent === '.' || - firstPathComponent === '..' || - firstPathComponent === '') { - const combinedPathComponents = (firstPathComponent === '') ? - pathComponents : - resolveFrom.concat(pathComponents); - - const normalizedPath = normalizePath(combinedPathComponents); - if (!normalizedPath) { - return null; - } - - const pathStr = normalizedPath.join('/'); - const loadedPath = loadAsFile(pathStr) || loadAsDirectory(pathStr, false) || null; - return loadedPath; - } - - return loadNodeModules(resolveFrom, pathComponents); - } - - class Module { - constructor(pathComponents) { - this.dirComponents = pathComponents.slice(0, -1); - this.pathComponents = pathComponents; - this.filename = pathComponents.join('/'); - this.dirname = this.dirComponents.length > 1 ? this.dirComponents.join('/') : '/'; - this.exports = {}; - } - require(path) { - let module = this; - const resolvedPath = resolve(module, path); - if (!resolvedPath) { - throwError(new Error(`Cannot resolve module '${path}' from '${module.filename}'`)); - } - - // eval file - const pathComponents = resolvedPath.split('/'); - const displayPath = resolvedPath; - const cacheKey = pathComponents.join('/'); - if (cache[cacheKey]) { - return cache[cacheKey].exports; - } - - const currentModule = global.module; - module = new Module(pathComponents); - cache[cacheKey] = module; - global.module = module; - - if (endsWith(resolvedPath, '.node')) { - throwError(new Error(`Native Node.js modules are not supported '${resolvedPath}'`)); - } - - const content = readFileFn(resolvedPath); - if (!content) throwError(new Error(`Cannot load module '${resolvedPath}'`)); - - if (endsWith(resolvedPath, '.json')) { - module.exports = JSON.parse(content); - } else { - /* eslint-disable max-len */ - evalScriptFn( - `((require,exports,module,__filename,__dirname) => {${content}})(((m) => {return function(path){return m.require(path)}})(global.module),global.module.exports,global.module,global.module.filename,global.module.dirname)`, - displayPath); - /* eslint-enable max-len */ - } - - global.module = currentModule; - return module.exports; - } - } - - this.require = (path) => { - const rootModule = new Module(['', '']); - global.module = rootModule; - return rootModule.require(path); - }; - } +__SYSCALL.loaderSetupBuiltins({ + assert: 'assert', + events: 'events', + buffer: 'buffer', + process: './modules/process.js', + console: './modules/console.js', + constants: 'constants-browserify', + fs: './modules/fs.js', + os: './modules/os.js', + net: './modules/net.js', + dns: './modules/dns.js', + punycode: 'punycode', + querystring: 'querystring-es3', + string_decoder: 'string_decoder', + path: 'path-browserify', + url: 'url', + stream: './modules/stream.js', + inherits: './modules/inherits.js', + sys: 'util/util.js', + util: 'util/util.js', +}); + +global.process = require('process'); +global.Buffer = require('buffer').Buffer; + +const stream = require('stream'); + +class StdoutStream extends stream.Writable { + _write(chunk, encoding, callback) { + __SYSCALL.write(String(chunk)); + callback(); } - // end - - const files = {}; - for (const file of __SYSCALL.initrdListFiles()) { - files[file] = true; +} +class StderrStream extends stream.Writable { + _write(chunk, encoding, callback) { + __SYSCALL.write(String(chunk)); + callback(); } - - function fileExists(path) { - return !!files[path]; +} +class TermoutStream extends stream.Writable { + _write(chunk, encoding, callback) { + runtime.stdio.defaultStdio.write(String(chunk)); + callback(); } +} +class TermerrStream extends stream.Writable { + _write(chunk, encoding, callback) { + runtime.stdio.defaultStdio.writeError(String(chunk)); + callback(); + } +} - const runtimePackagePath = __SYSCALL.initrdGetKernelIndex().split('/').slice(0, -1).join('/'); - const loader = new Loader(fileExists, __SYSCALL.initrdReadFile, __SYSCALL.eval, { - assert: 'assert', - events: 'events', - buffer: 'buffer', - process: './modules/process.js', - console: './modules/console.js', - constants: 'constants-browserify', - fs: './modules/fs.js', - os: './modules/os.js', - net: './modules/net.js', - dns: './modules/dns.js', - punycode: 'punycode', - querystring: 'querystring-es3', - string_decoder: 'string_decoder', - path: 'path-browserify', - url: 'url', - stream: './modules/stream.js', - inherits: './modules/inherits.js', - sys: 'util/util.js', - util: 'util/util.js', - }, runtimePackagePath); - - loader.require(`${runtimePackagePath}/index.js`); +process.stdout = new StdoutStream(); +process.stderr = new StderrStream(); +process.termout = new TermoutStream(); +process.termerr = new TermerrStream(); - global.process = loader.require('process'); - global.Buffer = loader.require('buffer').Buffer; - const stream = loader.require('stream'); - class StdoutStream extends stream.Writable { - _write(chunk, encoding, callback) { - __SYSCALL.write(String(chunk)); - callback(); - } - } - class StderrStream extends stream.Writable { - _write(chunk, encoding, callback) { - __SYSCALL.write(String(chunk)); - callback(); - } - } - class TermoutStream extends stream.Writable { - _write(chunk, encoding, callback) { - runtime.stdio.defaultStdio.write(String(chunk)); - callback(); - } - } - class TermerrStream extends stream.Writable { - _write(chunk, encoding, callback) { - runtime.stdio.defaultStdio.writeError(String(chunk)); - callback(); - } - } - process.stdout = new StdoutStream(); - process.stderr = new StderrStream(); - process.termout = new TermoutStream(); - process.termerr = new TermerrStream(); - loader.require('console'); - loader.require('/'); -})(); +require('console'); +require('./index'); diff --git a/js/core/net/index.js b/js/core/net/index.js index a8eff5cf1..b3b328c1d 100644 --- a/js/core/net/index.js +++ b/js/core/net/index.js @@ -13,7 +13,7 @@ // limitations under the License. 'use strict'; -const assert = require('assert'); +const assert = require('../../utils').assert; const Interface = require('./interface'); const interfaces = require('./interfaces'); const route = require('./route'); diff --git a/js/core/net/interface.js b/js/core/net/interface.js index c143ae60c..349790371 100644 --- a/js/core/net/interface.js +++ b/js/core/net/interface.js @@ -13,7 +13,7 @@ // limitations under the License. 'use strict'; -const assert = require('assert'); +const assert = require('../../utils').assert; const MACAddress = require('./mac-address'); const ARPResolver = require('./arp-resolver'); const ethernet = require('./ethernet'); diff --git a/js/core/net/port-allocator.js b/js/core/net/port-allocator.js index 15b2c9884..521b3fce1 100644 --- a/js/core/net/port-allocator.js +++ b/js/core/net/port-allocator.js @@ -14,7 +14,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('../../utils').assert; const isint = require('isint'); const portUtils = require('./port-utils'); diff --git a/js/core/net/route.js b/js/core/net/route.js index 5fbe7f86f..191e8f73c 100644 --- a/js/core/net/route.js +++ b/js/core/net/route.js @@ -15,7 +15,7 @@ 'use strict'; const IP4Address = require('./ip4-address'); // const interfaces = require('./interfaces'); -const assert = require('assert'); +const assert = require('../../utils').assert; const bitTwiddle = require('bit-twiddle'); const table = []; @@ -31,7 +31,7 @@ class Entry { this.gateway = gateway; this.intf = intf; - debug(`[ ADD ROUTE ${ip.toString()}/${this.maskBits} via ${gateway} ${intf.name} ]`); + // debug(`[ ADD ROUTE ${ip.toString()}/${this.maskBits} via ${gateway} ${intf.name} ]`); } } diff --git a/js/core/net/tcp-socket.js b/js/core/net/tcp-socket.js index 901dbde20..e1a91f40c 100644 --- a/js/core/net/tcp-socket.js +++ b/js/core/net/tcp-socket.js @@ -297,7 +297,7 @@ class TCPSocket { if (buf) { const length = buf.length; const reserved = this._allocTransmitPosition(length); - debug('send at ', position, 'len', reserved); + // debug('send at ', position, 'len', reserved); if (reserved === 0) { break; } @@ -317,7 +317,7 @@ class TCPSocket { this.ondrain(); } } else { - debug('fill pos', position, 'fin'); + // debug('fill pos', position, 'fin'); this._incTransmitPosition(); this._transmitQueue.push([0, timeNow(), position, 1, null, tcpHeader.FLAG_ACK | tcpHeader.FLAG_FIN]); ++remove; diff --git a/js/core/net/tcp-timer.js b/js/core/net/tcp-timer.js index df8537ae8..1fe63c212 100644 --- a/js/core/net/tcp-timer.js +++ b/js/core/net/tcp-timer.js @@ -24,7 +24,7 @@ function timeoutHandler() { } function initTimeout() { - setTimeout(timeoutHandler, 500); + __SYSCALL.unrefTimer(setTimeout(timeoutHandler, 500)); } initTimeout(); diff --git a/js/core/net/udp-socket.js b/js/core/net/udp-socket.js index a0fe21c68..bff768426 100644 --- a/js/core/net/udp-socket.js +++ b/js/core/net/udp-socket.js @@ -32,6 +32,10 @@ class UDPSocket { this.onmessage = null; } + get port() { + return this._port; + } + send(ipOpt, port, u8) { let ip = ipOpt; if (typeutils.isString(ip)) { @@ -68,13 +72,22 @@ class UDPSocket { } bind(port) { - assertError(portUtils.isPort(port), netError.E_INVALID_PORT); + if (!port) { + this._port = ports.allocEphemeral(this); + if (!this._port) { + return false; + } + } else { + assertError(portUtils.isPort(port), netError.E_INVALID_PORT); + + if (!ports.allocPort(port, this)) { + throw netError.E_ADDRESS_IN_USE; + } - if (!ports.allocPort(port, this)) { - throw netError.E_ADDRESS_IN_USE; + this._port = port; } - this._port = port; + return true; } bindToInterface(intf, port) { diff --git a/js/core/net/udp.js b/js/core/net/udp.js index 3f8dafb9d..bf8c6af0f 100644 --- a/js/core/net/udp.js +++ b/js/core/net/udp.js @@ -15,17 +15,14 @@ 'use strict'; const udpHeader = require('./udp-header'); const UDPSocket = require('./udp-socket'); -// const portUtils = require('./port-utils'); -// const IP4Address = require('./ip4-address'); -// const interfaces = require('./interfaces'); -// const netError = require('./net-error'); +const logger = require('../../logger'); function receive(intf, srcIP, destIP, u8, headerOffset) { const srcPort = udpHeader.getSrcPort(u8, headerOffset); const destPort = udpHeader.getDestPort(u8, headerOffset); const dataLength = udpHeader.getDataLength(u8, headerOffset) - udpHeader.headerLength; const dataOffset = headerOffset + udpHeader.headerLength; - debug('recv UDP over IP4', srcPort, destPort, dataLength); + logger.debug('recv UDP over IP4', srcPort, destPort, dataLength); const socket = UDPSocket.lookupReceive(destPort); if (!socket) { diff --git a/js/core/pci/index.js b/js/core/pci/index.js index 254c14bd0..fda10846b 100644 --- a/js/core/pci/index.js +++ b/js/core/pci/index.js @@ -13,7 +13,7 @@ // limitations under the License. 'use strict'; -const assert = require('assert'); +const assert = require('../../utils').assert; const scan = require('./scan'); const PciDevice = require('./pci-device'); const typeutils = require('typeutils'); diff --git a/js/core/pci/pci-device.js b/js/core/pci/pci-device.js index 16cb406c6..740617bbc 100644 --- a/js/core/pci/pci-device.js +++ b/js/core/pci/pci-device.js @@ -13,7 +13,7 @@ // limitations under the License. 'use strict'; -const assert = require('assert'); +const assert = require('../../utils').assert; const typeutils = require('typeutils'); const isint = require('isint'); diff --git a/js/core/pci/scan.js b/js/core/pci/scan.js index 19c526e8b..793fb72b0 100644 --- a/js/core/pci/scan.js +++ b/js/core/pci/scan.js @@ -16,6 +16,7 @@ 'use strict'; const resources = require('../resources'); +const logger = require('../../logger'); const io = resources.ioRange; const irqRange = resources.irqRange; const memrange = resources.memoryRange; @@ -901,8 +902,8 @@ pciManager.each((pciDevice) => { const pins = ['dont use', 'A', 'B', 'C', 'D']; - const info = `${address.bus.toString(16)}: ${address.slot.toString(16)}.${address.func} ${pciDevice.vendorId().toString(16)}: ${pciDevice.deviceId().toString(16)} ${classData.className} IRQ: ${vector} PIN: ${pins[devicePin]}`; - debug(info); + const info = `${address.bus.toString(16)}: ${address.slot.toString(16)}.${address.func} ${pciDevice.vendorId().toString(16)}: ${pciDevice.deviceId().toString(16)} ${classData.className} IRQ: ${vector} PIN: ${pins[devicePin]}`; // eslint-disable-line + logger.debug(info); }); function listPciDevices() { diff --git a/js/core/ps2/index.js b/js/core/ps2/index.js index 2c607bead..cea063d5d 100644 --- a/js/core/ps2/index.js +++ b/js/core/ps2/index.js @@ -14,7 +14,7 @@ 'use strict'; const driverUtils = require('../driver-utils'); -const assert = require('assert'); +const assert = require('../../utils').assert; const typeutils = require('typeutils'); exports.setKeyboardDriver = (driver) => { diff --git a/js/core/timers.js b/js/core/timers.js index 6139f5998..98ce06f97 100644 --- a/js/core/timers.js +++ b/js/core/timers.js @@ -16,7 +16,7 @@ const tasks5s = []; -setInterval(() => { +__SYSCALL.unrefTimer(setInterval(() => { if (tasks5s.length === 0) { return; } @@ -24,7 +24,7 @@ setInterval(() => { for (const task of tasks5s) { task(); } -}, 5000); +}, 5000)); /** * Schedule task to run every 5 seconds diff --git a/js/driver/virtio/vring/available-ring.js b/js/driver/virtio/vring/available-ring.js index 2ad512289..6b7a08407 100644 --- a/js/driver/virtio/vring/available-ring.js +++ b/js/driver/virtio/vring/available-ring.js @@ -50,7 +50,8 @@ class AvailableRing { } readDescriptorAsDevice(idxIndex) { - return this.availableRing[AVAILABLE_RING_INDEX_RING + idxIndex]; + const idx = (idxIndex & (this.ringSize - 1)) >>> 0; + return this.availableRing[AVAILABLE_RING_INDEX_RING + idx]; } disableInterrupts() { diff --git a/js/driver/virtio/vring/index.js b/js/driver/virtio/vring/index.js index 6b84ebcc0..4ebc937df 100644 --- a/js/driver/virtio/vring/index.js +++ b/js/driver/virtio/vring/index.js @@ -14,7 +14,7 @@ 'use strict'; -const assert = require('assert'); +const assert = require('../../../utils').assert; const DescriptorTable = require('./descriptor-table'); const AvailableRing = require('./available-ring'); const UsedRing = require('./used-ring'); @@ -103,13 +103,13 @@ class VRing { pageSplitBuffers.push(u8.subarray(addr[0])); lengths.push(addr[0]); lengths.push(addr[3]); - debug('virtio: multipage buffer\n'); + // debug('virtio: multipage buffer\n'); } } const first = this.descriptorTable.placeBuffers(pageSplitBuffers, lengths, isWriteOnly); if (first < 0) { - debug('virtio: no descriptors\n'); + // debug('virtio: no descriptors\n'); return false; } diff --git a/js/index.js b/js/index.js index fd76ddecd..32754e8f4 100644 --- a/js/index.js +++ b/js/index.js @@ -68,4 +68,7 @@ require('./driver/virtio'); require('./core/cmos-time'); // load cmos require('./core/set-time'); // fetch NTP +// Load app +require(__SYSCALL.initrdGetAppIndex()); + module.exports = runtime; diff --git a/js/logger.js b/js/logger.js new file mode 100644 index 000000000..356658469 --- /dev/null +++ b/js/logger.js @@ -0,0 +1,17 @@ +// Copyright 2016-present runtime.js project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +exports.debug = () => {}; diff --git a/js/test/unit/buffers/physical-address.js b/js/test/unit/buffers/physical-address.js index 195184a06..7d7fe3f65 100644 --- a/js/test/unit/buffers/physical-address.js +++ b/js/test/unit/buffers/physical-address.js @@ -13,11 +13,12 @@ // limitations under the License. 'use strict'; -const test = require('tape'); -const resources = require('../../../core/resources'); +const createSuite = require('estap'); +const test = createSuite(); +const resources = require('/js/core/resources'); -test('buffer crosses page boundary', (t) => { - // allocate on page boundary +test('__SYSCALL.bufferAddress buffer crosses page boundary', t => { + // allocate on the page boundary const buf = resources.memoryRange.block(0x3200000 - 12, 24).buffer(); const u8 = new Uint8Array(buf); for (let i = 0; i < u8.length; i++) { @@ -27,14 +28,13 @@ test('buffer crosses page boundary', (t) => { const addr = __SYSCALL.bufferAddress(u8); const b1 = u8.subarray(0, addr[0]); const b2 = u8.subarray(addr[0]); - t.equal(b1.length, 12); - t.equal(b1[0], 0); - t.equal(b2.length, 12); - t.equal(b2[0], 12); - t.end(); + t.is(b1.length, 12, 'first part length'); + t.is(b1[0], 0, 'first part first byte'); + t.is(b2.length, 12, 'second part length'); + t.is(b2[0], 12, 'second part first byte'); }); -test('buffer does not cross page boundary', (t) => { +test('__SYSCALL.bufferAddress buffer does not cross page boundary', t => { const buf = resources.memoryRange.block(0x3200000, 24).buffer(); const u8 = new Uint8Array(buf); for (let i = 0; i < u8.length; i++) { @@ -43,7 +43,6 @@ test('buffer does not cross page boundary', (t) => { const addr = __SYSCALL.bufferAddress(u8); const b1 = u8.subarray(0, addr[0]); - t.equal(b1.length, u8.length); - t.equal(addr[3], 0); - t.end(); + t.is(b1.length, u8.length, 'buffer length'); + t.is(addr[3], 0, 'buffer first byte'); }); diff --git a/js/test/unit/lib/buffer-builder.js b/js/test/unit/helpers/buffer-builder.js similarity index 100% rename from js/test/unit/lib/buffer-builder.js rename to js/test/unit/helpers/buffer-builder.js diff --git a/js/test/unit/lib/interface-mock.js b/js/test/unit/helpers/interface-mock.js similarity index 84% rename from js/test/unit/lib/interface-mock.js rename to js/test/unit/helpers/interface-mock.js index 4cbe64a67..f533a169f 100644 --- a/js/test/unit/lib/interface-mock.js +++ b/js/test/unit/helpers/interface-mock.js @@ -14,9 +14,9 @@ 'use strict'; -const Interface = require('../../../core/net/interface'); -const MACAddress = require('../../../core/net/mac-address'); -const IP4Address = require('../../../core/net/ip4-address'); +const Interface = require('/js/core/net/interface'); +const MACAddress = require('/js/core/net/mac-address'); +const IP4Address = require('/js/core/net/ip4-address'); module.exports = (opts = {}) => { const ip = opts.ip || new IP4Address(127, 0, 0, 1); diff --git a/js/test/unit/lib/packet-builder.js b/js/test/unit/helpers/packet-builder.js similarity index 96% rename from js/test/unit/lib/packet-builder.js rename to js/test/unit/helpers/packet-builder.js index 7d5155dd8..8dc358979 100644 --- a/js/test/unit/lib/packet-builder.js +++ b/js/test/unit/helpers/packet-builder.js @@ -15,9 +15,9 @@ 'use strict'; const BufferBuilder = require('./buffer-builder'); -const IP4Address = require('../../../core/net/ip4-address'); -const MACAddress = require('../../../core/net/mac-address'); -const checksum = require('../../../core/net/checksum'); +const IP4Address = require('/js/core/net/ip4-address'); +const MACAddress = require('/js/core/net/mac-address'); +const checksum = require('/js/core/net/checksum'); function cksum(u8) { return checksum(u8, 0, u8.length, 0); diff --git a/js/test/unit/lib/test.js b/js/test/unit/helpers/test.js similarity index 81% rename from js/test/unit/lib/test.js rename to js/test/unit/helpers/test.js index 21c5f0654..ed562d519 100644 --- a/js/test/unit/lib/test.js +++ b/js/test/unit/helpers/test.js @@ -14,10 +14,11 @@ 'use strict'; -const test = require('tape'); +const createSuite = require('estap'); +const test = createSuite(); const BufferBuilder = require('./buffer-builder'); -test('build simple buffer', (t) => { +test('build simple buffer', t => { const b = new BufferBuilder() .uint8(5) .uint8(10) @@ -27,19 +28,17 @@ test('build simple buffer', (t) => { .buffer(); const ab = new Uint8Array([5, 10, 0xCC, 0xAA, 0xFF, 0xEE, 15, 0xAA, 0xBB]); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); -test('empty buffer', (t) => { +test('empty buffer', t => { const b = new BufferBuilder().buffer(); const ab = new Uint8Array(0); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); -test('repeat uint8', (t) => { +test('repeat uint8', t => { const b = new BufferBuilder() .uint8(5) .uint8(10) @@ -48,11 +47,10 @@ test('repeat uint8', (t) => { .buffer(); const ab = new Uint8Array([5, 10, 10, 10, 10, 10, 15]); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); -test('repeat uint16', (t) => { +test('repeat uint16', t => { const b = new BufferBuilder() .uint8(5) .uint16(0xFFAA) @@ -61,11 +59,10 @@ test('repeat uint16', (t) => { .buffer(); const ab = new Uint8Array([5, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 15]); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); -test('repeat uint32', (t) => { +test('repeat uint32', t => { const b = new BufferBuilder() .uint8(5) .uint32(0xAABBCCDD) @@ -74,11 +71,10 @@ test('repeat uint32', (t) => { .buffer(); const ab = new Uint8Array([5, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 15]); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); -test('align', (t) => { +test('align', t => { const b = new BufferBuilder() .uint8(5) .uint8(6) @@ -90,6 +86,5 @@ test('align', (t) => { .buffer(); const ab = new Uint8Array([5, 6, 7, 0, 0, 0, 0, 0, 1, 0, 2, 8]); - t.deepEqual(b, ab); - t.end(); + t.same(b, ab); }); diff --git a/js/test/unit/index.js b/js/test/unit/index.js index 0484ecee6..8d1b3e957 100644 --- a/js/test/unit/index.js +++ b/js/test/unit/index.js @@ -1,4 +1,4 @@ -// Copyright 2014-present runtime.js project authors +// Copyright 2016-present runtime.js project authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,26 +13,23 @@ // limitations under the License. 'use strict'; +const runner = require('estap'); +const { tapReporter } = require('estap'); -const test = require('tape'); -const stream = test.createStream(); -const { shutdown } = require('../../').machine; - -stream.on('data', (vOpt) => { - let v = vOpt; - if (v[v.length - 1] === '\n') { - v = v.slice(0, -1); - } - console.log(v); -}); - -stream.on('end', shutdown); +__SYSCALL.onexit = __SYSCALL.poweroff; +runner.disableAutorun(); +require('./helpers/test'); require('./script'); -require('./lib/test'); require('./buffers'); require('./platform'); require('./timers'); require('./virtio'); require('./random'); require('./net'); + +runner.run({ + log: tapReporter({ + log: console.log.bind(console), + }), +}); diff --git a/js/test/unit/net/helpers/setup.js b/js/test/unit/net/helpers/setup.js new file mode 100644 index 000000000..22338045c --- /dev/null +++ b/js/test/unit/net/helpers/setup.js @@ -0,0 +1,20 @@ +// Copyright 2014-present runtime.js project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const interfaces = require('/js/core/net/interfaces'); +const loopback = require('/js/core/net/loopback'); + +interfaces.add(loopback); diff --git a/js/test/unit/net/index.js b/js/test/unit/net/index.js index 4ee544bee..c1c4e021c 100644 --- a/js/test/unit/net/index.js +++ b/js/test/unit/net/index.js @@ -13,6 +13,7 @@ // limitations under the License. 'use strict'; +require('./helpers/setup'); require('./interface'); require('./ip4'); require('./tcp'); diff --git a/js/test/unit/net/interface.js b/js/test/unit/net/interface.js index 6ba528577..25db55c92 100644 --- a/js/test/unit/net/interface.js +++ b/js/test/unit/net/interface.js @@ -14,17 +14,17 @@ 'use strict'; -const test = require('tape'); -const Interface = require('../../../core/net/interface'); -const MACAddress = require('../../../core/net/mac-address'); +const createSuite = require('estap'); +const test = createSuite(); +const Interface = require('/js/core/net/interface'); +const MACAddress = require('/js/core/net/mac-address'); // const IP4Address = require('../../../core/net/ip4-address'); // const checksum = require('../../../core/net/checksum'); // const BufferBuilder = require('../lib/buffer-builder'); -test('create interface', (t) => { +test('create interface', () => { const intf = new Interface(MACAddress.ZERO); intf.ontransmit = () => {}; - t.end(); }); /* diff --git a/js/test/unit/net/ip4.js b/js/test/unit/net/ip4.js index 2eaf6430e..d294cee08 100644 --- a/js/test/unit/net/ip4.js +++ b/js/test/unit/net/ip4.js @@ -14,26 +14,27 @@ 'use strict'; -const test = require('tape'); -const runtime = require('../../..'); -const ip4fragments = require('../../../core/net/ip4-fragments'); -const interfaceMock = require('../lib/interface-mock'); -const packetBuilder = require('../lib/packet-builder'); - -test('receive ip4 udp', (t) => { - t.timeoutAfter(1000); +const createSuite = require('estap'); +const test = createSuite(); +const UDPSocket = require('/js/core/net/udp-socket'); +const ip4fragments = require('/js/core/net/ip4-fragments'); +const interfaceMock = require('../helpers/interface-mock'); +const packetBuilder = require('../helpers/packet-builder'); + +test.cb('receive ip4 udp', (t) => { t.plan(3); const intf = interfaceMock(); - const socket = new runtime.net.UDPSocket(); + const socket = new UDPSocket(); socket.bind(65432); - t.on('end', socket.close.bind(socket)); + + t.after(socket.close.bind(socket)); socket.onmessage = (ip, port, u8) => { - t.equal(ip.toString(), '33.44.55.66'); - t.equal(port, 999); - t.deepEqual(u8, new Uint8Array([1, 2, 3, 4, 5])); + t.is(ip.toString(), '33.44.55.66'); + t.is(port, 999); + t.same(u8, new Uint8Array([1, 2, 3, 4, 5])); }; const udp = packetBuilder.createUDP(new Uint8Array([1, 2, 3, 4, 5]), { @@ -46,39 +47,39 @@ test('receive ip4 udp', (t) => { intf.receive(ip4); }); -function ipFragmentsTest(t, name, length, slices, order, norecv) { - t.test(name, (t2) => { - t2.timeoutAfter(1000); - t2.plan(norecv ? 1 : 4); +function ipFragmentsTest(name, length, slices, order, norecv) { + test.cb(name, t => { + t.plan(norecv ? 1 : 4); const intf = interfaceMock(); - const socket = new runtime.net.UDPSocket(); - socket.bind(65432); + const socket = new UDPSocket(); + socket.bind(); + const socketPort = socket.port; socket.onmessage = (ip, port, u8) => { - t2.equal(ip.toString(), '33.44.55.66'); - t2.equal(port, 999); - t2.ok(packetBuilder.buffersEqual(u8, packetBuilder.makeBuffer(length))); + t.is(ip.toString(), '33.44.55.66', 'message ip address'); + t.is(port, 999, 'message port'); + t.true(packetBuilder.buffersEqual(u8, packetBuilder.makeBuffer(length)), 'message content'); socket.close(); }; const fragments = packetBuilder.createFragmentedIP4({ srcPort: 999, - destPort: 65432, + destPort: socketPort, srcIP: '33.44.55.66', }, length + 8 /* 8 bytes udp header */, slices); order.forEach((index) => intf.receive(fragments[index])); if (norecv) { - t2.ok(intf.fragments.size > 0); + t.true(intf.fragments.size > 0, 'fragments left in the queue'); } else { - t2.equal(intf.fragments.size, 0); + t.is(intf.fragments.size, 0, 'fragment queue is empty'); } }); } -test('receive ip4 fragmented non overlapped', (t) => { +(() => { const slices = [ { offset: 0, len: 8 }, { offset: 8, len: 8 }, @@ -86,15 +87,14 @@ test('receive ip4 fragmented non overlapped', (t) => { { offset: 32, len: 16 }, { offset: 48, len: 24 }, ]; - ipFragmentsTest(t, 'normal ordered', 64, slices, [0, 1, 2, 3, 4]); - ipFragmentsTest(t, 'reverse ordered', 64, slices, [4, 3, 2, 1, 0]); - ipFragmentsTest(t, 'mixed ordered', 64, slices, [2, 1, 4, 0, 3]); - ipFragmentsTest(t, 'duplicated', 64, slices, [1, 1, 1, 2, 2, 3, 4, 0]); - ipFragmentsTest(t, 'duplicated last', 64, slices, [0, 1, 2, 4, 4, 4, 4, 3]); - t.end(); -}); - -test('receive ip4 fragmented overlapped fragments', (t) => { + ipFragmentsTest('receive ip4 fragmented non overlapped > normal ordered', 64, slices, [0, 1, 2, 3, 4]); + ipFragmentsTest('receive ip4 fragmented non overlapped > reverse ordered', 64, slices, [4, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented non overlapped > mixed ordered', 64, slices, [2, 1, 4, 0, 3]); + ipFragmentsTest('receive ip4 fragmented non overlapped > duplicated', 64, slices, [1, 1, 1, 2, 2, 3, 4, 0]); + ipFragmentsTest('receive ip4 fragmented non overlapped > duplicated last', 64, slices, [0, 1, 2, 4, 4, 4, 4, 3]); +})(); + +(() => { const slices = [ { offset: 0, len: 8 }, { offset: 8, len: 8 }, @@ -102,17 +102,16 @@ test('receive ip4 fragmented overlapped fragments', (t) => { { offset: 24, len: 16 }, { offset: 24, len: 48 }, ]; - ipFragmentsTest(t, 'fragments left edge overlap', 64, slices, [0, 1, 2, 3, 4]); - ipFragmentsTest(t, 'fragments full and right edge overlap', 64, slices, [4, 3, 2, 1, 0]); - ipFragmentsTest(t, 'mixed ordered (fragment 3 is unnecessary)', 64, slices, [2, 1, 4, 0]); - ipFragmentsTest(t, 'mixed ordered (fragment 3 first)', 64, slices, [3, 2, 1, 4, 0]); - ipFragmentsTest(t, 'duplicated', 64, slices, [1, 1, 1, 2, 2, 3, 4, 0]); - ipFragmentsTest(t, 'duplicated last (fragment 3 is unnecessary)', 64, slices, [0, 1, 4, 4, 4, 4, 2]); - ipFragmentsTest(t, 'duplicated last (fragment 3 first)', 64, slices, [3, 3, 0, 1, 4, 4, 4, 4, 2]); - t.end(); -}); - -test('receive ip4 fragmented overlapped fragments ladder 1', (t) => { + ipFragmentsTest('receive ip4 fragmented overlapped fragments > fragments left edge overlap', 64, slices, [0, 1, 2, 3, 4]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > fragments full and right edge overlap', 64, slices, [4, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > mixed ordered (fragment 3 is unnecessary)', 64, slices, [2, 1, 4, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > mixed ordered (fragment 3 first)', 64, slices, [3, 2, 1, 4, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > duplicated', 64, slices, [1, 1, 1, 2, 2, 3, 4, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > duplicated last (fragment 3 is unnecessary)', 64, slices, [0, 1, 4, 4, 4, 4, 2]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments > duplicated last (fragment 3 first)', 64, slices, [3, 3, 0, 1, 4, 4, 4, 4, 2]); +})(); + +(() => { const slices = [ { offset: 8, len: 64 }, // [ ========] { offset: 16, len: 56 }, // [ =======] @@ -124,16 +123,15 @@ test('receive ip4 fragmented overlapped fragments ladder 1', (t) => { { offset: 64, len: 8 }, // [ =] { offset: 0, len: 8 }, // [= ] (last piece) ]; - ipFragmentsTest(t, 'normal order', 64, slices, [0, 1, 2, 3, 4, 5, 6, 7, 8]); - ipFragmentsTest(t, 'reverse order except last', 64, slices, [7, 6, 5, 4, 3, 2, 1, 0, 8]); - ipFragmentsTest(t, 'reverse order including last', 64, slices, [8, 7, 6, 5, 4, 3, 2, 1, 0]); - ipFragmentsTest(t, 'mixed order except last', 64, slices, [3, 2, 4, 7, 1, 6, 0, 5, 8]); - ipFragmentsTest(t, 'last first 1', 64, slices, [8, 0]); - ipFragmentsTest(t, 'last first 2', 64, slices, [8, 1, 0]); - t.end(); -}); - -test('receive ip4 fragmented overlapped fragments ladder 2', (t) => { + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > normal order', 64, slices, [0, 1, 2, 3, 4, 5, 6, 7, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > reverse order except last', 64, slices, [7, 6, 5, 4, 3, 2, 1, 0, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > reverse order including last', 64, slices, [8, 7, 6, 5, 4, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > mixed order except last', 64, slices, [3, 2, 4, 7, 1, 6, 0, 5, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > last first 1', 64, slices, [8, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 1 > last first 2', 64, slices, [8, 1, 0]); +})(); + +(() => { const slices = [ { offset: 0, len: 64 }, // [======== ] { offset: 0, len: 56 }, // [======= ] @@ -145,16 +143,15 @@ test('receive ip4 fragmented overlapped fragments ladder 2', (t) => { { offset: 0, len: 8 }, // [= ] { offset: 64, len: 8 }, // [ =] (last piece) ]; - ipFragmentsTest(t, 'normal order', 64, slices, [0, 1, 2, 3, 4, 5, 6, 7, 8]); - ipFragmentsTest(t, 'reverse order except last', 64, slices, [7, 6, 5, 4, 3, 2, 1, 0, 8]); - ipFragmentsTest(t, 'reverse order including last', 64, slices, [8, 7, 6, 5, 4, 3, 2, 1, 0]); - ipFragmentsTest(t, 'mixed order except last', 64, slices, [3, 2, 4, 7, 1, 6, 0, 5, 8]); - ipFragmentsTest(t, 'last first 1', 64, slices, [8, 0]); - ipFragmentsTest(t, 'last first 2', 64, slices, [8, 1, 0]); - t.end(); -}); - -test('receive ip4 fragmented overlapped fragments pyramid', (t) => { + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > normal order', 64, slices, [0, 1, 2, 3, 4, 5, 6, 7, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > reverse order except last', 64, slices, [7, 6, 5, 4, 3, 2, 1, 0, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > reverse order including last', 64, slices, [8, 7, 6, 5, 4, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > mixed order except last', 64, slices, [3, 2, 4, 7, 1, 6, 0, 5, 8]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > last first 1', 64, slices, [8, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments ladder 2 > last first 2', 64, slices, [8, 1, 0]); +})(); + +(() => { const slices = [ { offset: 8, len: 56 }, // [ ======= ] { offset: 16, len: 40 }, // [ ===== ] @@ -163,15 +160,14 @@ test('receive ip4 fragmented overlapped fragments pyramid', (t) => { { offset: 64, len: 8 }, // [ =] { offset: 0, len: 8 }, // [= ] ]; - ipFragmentsTest(t, 'normal order', 64, slices, [0, 1, 2, 3, 4, 5]); - ipFragmentsTest(t, 'reverse order except last two 1', 64, slices, [3, 2, 1, 0, 4, 5]); - ipFragmentsTest(t, 'reverse order except last two 2', 64, slices, [3, 2, 1, 0, 5, 4]); - ipFragmentsTest(t, 'reverse order except last two 3', 64, slices, [4, 5, 3, 2, 1, 0]); - ipFragmentsTest(t, 'reverse order except last two 4', 64, slices, [5, 4, 3, 2, 1, 0]); - t.end(); -}); - -test('receive ip4 fragmented overlapped fragments small chunks', (t) => { + ipFragmentsTest('receive ip4 fragmented overlapped fragments pyramid > normal order', 64, slices, [0, 1, 2, 3, 4, 5]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments pyramid > reverse order except last two 1', 64, slices, [3, 2, 1, 0, 4, 5]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments pyramid > reverse order except last two 2', 64, slices, [3, 2, 1, 0, 5, 4]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments pyramid > reverse order except last two 3', 64, slices, [4, 5, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments pyramid > reverse order except last two 4', 64, slices, [5, 4, 3, 2, 1, 0]); +})(); + +(() => { const slices = [ { offset: 0, len: 8 }, // [= ] { offset: 16, len: 8 }, // [ = ] @@ -180,33 +176,29 @@ test('receive ip4 fragmented overlapped fragments small chunks', (t) => { { offset: 64, len: 8 }, // [ =] { offset: 8, len: 56 }, // [ ======= ] ]; - ipFragmentsTest(t, 'normal order', 64, slices, [0, 1, 2, 3, 4, 5]); - ipFragmentsTest(t, 'reverse order', 64, slices, [5, 4, 3, 2, 1, 0]); - ipFragmentsTest(t, 'reverse order duplicates', 64, slices, [5, 5, 4, 3, 3, 3, 2, 1, 1, 0]); - ipFragmentsTest(t, 'mixed order', 64, slices, [2, 3, 1, 4, 0, 5]); - t.end(); -}); + ipFragmentsTest('receive ip4 fragmented overlapped fragments small chunks > normal order', 64, slices, [0, 1, 2, 3, 4, 5]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments small chunks > reverse order', 64, slices, [5, 4, 3, 2, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments small chunks > reverse order duplicates', 64, slices, [5, 5, 4, 3, 3, 3, 2, 1, 1, 0]); + ipFragmentsTest('receive ip4 fragmented overlapped fragments small chunks > mixed order', 64, slices, [2, 3, 1, 4, 0, 5]); +})(); -test('receive ip4 fragmented max offset and size', (t) => { +(() => { const slices = [ { offset: 0, len: 65528 }, { offset: 65528, len: 7 }, ]; - ipFragmentsTest(t, 'max size', 65535 - 8, slices, [0, 1]); - t.end(); -}); + ipFragmentsTest('receive ip4 fragmented max offset and size', 65535 - 8, slices, [0, 1]); +})(); -test('receive ip4 fragmented too big', (t) => { +(() => { const slices = [ { offset: 0, len: 65528 }, { offset: 65528, len: 7 + 1 }, ]; - ipFragmentsTest(t, 'max-size + 1', (65535 - 8) + 1, slices, [0, 1], true); - t.end(); -}); + ipFragmentsTest('receive ip4 fragmented too big (max-size + 1)', (65535 - 8) + 1, slices, [0, 1], true); +})(); -test('too many fragment queues and timeouts', (t) => { - t.timeoutAfter(1000); +test('too many fragment queues and timeouts', t => { const intf = interfaceMock(); const originalNow = performance.now; @@ -224,18 +216,17 @@ test('too many fragment queues and timeouts', (t) => { intf.receive(fragments[0]); } - t.equal(intf.fragments.size, 100); + t.is(intf.fragments.size, 100); performance.now = () => 20100; ip4fragments.tick(intf); - t.equal(intf.fragments.size, 100); + t.is(intf.fragments.size, 100); performance.now = () => 30100; ip4fragments.tick(intf); - t.equal(intf.fragments.size, 0); + t.is(intf.fragments.size, 0); performance.now = originalNow; - t.end(); }); diff --git a/js/test/unit/net/port-allocator.js b/js/test/unit/net/port-allocator.js index 32945553e..5d53ad6ac 100644 --- a/js/test/unit/net/port-allocator.js +++ b/js/test/unit/net/port-allocator.js @@ -14,18 +14,18 @@ 'use strict'; -const test = require('tape'); -// const assert = require('assert'); -const PortAllocator = require('../../../core/net/port-allocator'); +const createSuite = require('estap'); +const test = createSuite(); +const PortAllocator = require('/js/core/net/port-allocator'); const EPHEMERAL_PORT_FIRST = 49152; -test('ephemeral only: simple allocation', (t) => { +test('ephemeral only: simple allocation', t => { const allocator = new PortAllocator(); const socket = {}; // alloc 10 ports for (let i = 0; i < 10; ++i) { - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + i); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + i); } // free first 3 ports @@ -34,22 +34,20 @@ test('ephemeral only: simple allocation', (t) => { allocator.free(EPHEMERAL_PORT_FIRST + 2); // alloc the same 3 ports again - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); // alloc and realloc 11th port - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 10); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 10); allocator.free(EPHEMERAL_PORT_FIRST + 10); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 10); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 10); // alloc 12th port - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 11); - - t.end(); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 11); }); -test('ephemeral only: allocate and free all ports', (t) => { +test('ephemeral only: allocate and free all ports', t => { const allocator = new PortAllocator(); const socket = {}; @@ -61,19 +59,19 @@ test('ephemeral only: allocate and free all ports', (t) => { } if (port !== EPHEMERAL_PORT_FIRST + next++) { - t.equal(port, EPHEMERAL_PORT_FIRST + next++); + t.is(port, EPHEMERAL_PORT_FIRST + next++); } } - t.equal(next, 16000); - t.equal(allocator.allocatedCount, 16000); + t.is(next, 16000); + t.is(allocator.allocatedCount, 16000); while (next-- > 0) { allocator.free(((16000 - next) - 1) + EPHEMERAL_PORT_FIRST); } - t.equal(allocator.allocatedCount, 0); - t.equal(allocator._sockets.length, 0); + t.is(allocator.allocatedCount, 0); + t.is(allocator._sockets.length, 0); for (;;) { const port = allocator.allocEphemeral(socket); @@ -82,40 +80,37 @@ test('ephemeral only: allocate and free all ports', (t) => { } } - t.equal(allocator.allocatedCount, 16000); - t.end(); + t.is(allocator.allocatedCount, 16000); }); -test('ephemeral only: handle double free', (t) => { +test('ephemeral only: handle double free', t => { const allocator = new PortAllocator(); const socket = {}; - t.equal(allocator.allocatedCount, 0); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); - t.equal(allocator.allocatedCount, 3); + t.is(allocator.allocatedCount, 0); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); + t.is(allocator.allocatedCount, 3); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 2); + t.is(allocator.allocatedCount, 2); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 2); - t.end(); + t.is(allocator.allocatedCount, 2); }); -test('alloc port', (t) => { +test('alloc port', t => { const allocator = new PortAllocator(); const socket = {}; - t.equal(allocator.allocPort(80, socket), true); - t.equal(allocator.allocatedCount, 1); - t.equal(allocator.allocPort(8080, socket), true); - t.equal(allocator.allocatedCount, 2); + t.is(allocator.allocPort(80, socket), true); + t.is(allocator.allocatedCount, 1); + t.is(allocator.allocPort(8080, socket), true); + t.is(allocator.allocatedCount, 2); allocator.free(80); - t.equal(allocator.allocatedCount, 1); + t.is(allocator.allocatedCount, 1); allocator.free(8080); - t.equal(allocator.allocatedCount, 0); - t.end(); + t.is(allocator.allocatedCount, 0); }); -test('directly alloc and free all ports', (t) => { +test('directly alloc and free all ports', t => { const allocator = new PortAllocator(); const socket = {}; @@ -123,17 +118,16 @@ test('directly alloc and free all ports', (t) => { allocator.allocPort(i, socket); } - t.equal(allocator.allocatedCount, 65535); + t.is(allocator.allocatedCount, 65535); for (let i = 1; i < 65536; ++i) { allocator.free(i); } - t.equal(allocator.allocatedCount, 0); - t.end(); + t.is(allocator.allocatedCount, 0); }); -test('directly alloc all ports and try to get ephemeral', (t) => { +test('directly alloc all ports and try to get ephemeral', t => { const allocator = new PortAllocator(); const socket = {}; @@ -141,106 +135,102 @@ test('directly alloc all ports and try to get ephemeral', (t) => { allocator.allocPort(i, socket); } - t.equal(allocator.allocatedCount, 65535); - t.equal(allocator.allocEphemeral(socket), 0); - t.equal(allocator.allocatedCount, 65535); + t.is(allocator.allocatedCount, 65535); + t.is(allocator.allocEphemeral(socket), 0); + t.is(allocator.allocatedCount, 65535); allocator.free(80); - t.equal(allocator.allocatedCount, 65534); - t.equal(allocator.allocEphemeral(socket), 0); + t.is(allocator.allocatedCount, 65534); + t.is(allocator.allocEphemeral(socket), 0); allocator.free(EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocatedCount, 65533); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocatedCount, 65534); - t.equal(allocator.allocEphemeral(socket), 0); - t.equal(allocator.allocatedCount, 65534); + t.is(allocator.allocatedCount, 65533); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); + t.is(allocator.allocatedCount, 65534); + t.is(allocator.allocEphemeral(socket), 0); + t.is(allocator.allocatedCount, 65534); allocator.free(EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocatedCount, 65533); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocatedCount, 65534); + t.is(allocator.allocatedCount, 65533); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); + t.is(allocator.allocatedCount, 65534); for (let i = 1; i < 65536; ++i) { allocator.free(i); } - t.equal(allocator.allocatedCount, 0); - t.end(); + t.is(allocator.allocatedCount, 0); }); -test('cannot allocate port twice', (t) => { +test('cannot allocate port twice', t => { const allocator = new PortAllocator(); const socket = {}; - t.equal(allocator.allocatedCount, 0); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), true); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), false); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), false); - t.equal(allocator.allocatedCount, 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 3); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 0, socket), false); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 1, socket), false); - t.equal(allocator.allocatedCount, 3); - t.end(); + t.is(allocator.allocatedCount, 0); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), true); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), false); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 2, socket), false); + t.is(allocator.allocatedCount, 1); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 0); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocatedCount, 3); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 0, socket), false); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 1, socket), false); + t.is(allocator.allocatedCount, 3); }); -test('skip directly allocated ephemeral port', (t) => { +test('skip directly allocated ephemeral port', t => { const allocator = new PortAllocator(); const socket = {}; - t.equal(allocator.allocatedCount, 0); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST, socket), true); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 3); - t.equal(allocator.allocatedCount, 4); + t.is(allocator.allocatedCount, 0); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST, socket), true); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 3); + t.is(allocator.allocatedCount, 4); allocator.free(EPHEMERAL_PORT_FIRST + 1); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 3); + t.is(allocator.allocatedCount, 3); allocator.free(EPHEMERAL_PORT_FIRST + 2); allocator.free(EPHEMERAL_PORT_FIRST + 3); allocator.free(EPHEMERAL_PORT_FIRST + 3); - t.equal(allocator.allocatedCount, 1); + t.is(allocator.allocatedCount, 1); allocator.free(EPHEMERAL_PORT_FIRST); allocator.free(EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocatedCount, 0); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); - t.equal(allocator.allocatedCount, 3); + t.is(allocator.allocatedCount, 0); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 2); + t.is(allocator.allocatedCount, 3); allocator.free(EPHEMERAL_PORT_FIRST + 1); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 2); - t.equal(allocator.allocPort(EPHEMERAL_PORT_FIRST + 1, socket), true); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 3); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 4); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 5); - t.equal(allocator.allocatedCount, 6); + t.is(allocator.allocatedCount, 2); + t.is(allocator.allocPort(EPHEMERAL_PORT_FIRST + 1, socket), true); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 3); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 4); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 5); + t.is(allocator.allocatedCount, 6); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.allocatedCount, 6); - t.end(); + t.is(allocator.allocEphemeral(socket), EPHEMERAL_PORT_FIRST + 1); + t.is(allocator.allocatedCount, 6); }); -test('lookups', (t) => { +test('lookups', t => { const allocator = new PortAllocator(); const socket1 = 'socket1'; const socket2 = 'socket2'; const socket3 = 'socket3'; allocator.allocPort(EPHEMERAL_PORT_FIRST + 1, socket1); - t.equal(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), socket1); + t.is(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), socket1); const port1 = allocator.allocEphemeral(socket2); const port2 = allocator.allocEphemeral(socket3); - t.equal(port1, EPHEMERAL_PORT_FIRST + 0); - t.equal(port2, EPHEMERAL_PORT_FIRST + 2); - t.equal(allocator.lookup(port1), socket2); - t.equal(allocator.lookup(port2), socket3); - t.equal(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), socket1); + t.is(port1, EPHEMERAL_PORT_FIRST + 0); + t.is(port2, EPHEMERAL_PORT_FIRST + 2); + t.is(allocator.lookup(port1), socket2); + t.is(allocator.lookup(port2), socket3); + t.is(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), socket1); allocator.free(port1); - t.equal(allocator.lookup(port1), null); - t.equal(allocator.lookup(port2), socket3); + t.is(allocator.lookup(port1), null); + t.is(allocator.lookup(port2), socket3); allocator.free(port2); - t.equal(allocator.lookup(port1), null); - t.equal(allocator.lookup(port2), null); + t.is(allocator.lookup(port1), null); + t.is(allocator.lookup(port2), null); allocator.free(EPHEMERAL_PORT_FIRST + 1); - t.equal(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), null); - t.end(); + t.is(allocator.lookup(EPHEMERAL_PORT_FIRST + 1), null); }); diff --git a/js/test/unit/net/tcp-receive.js b/js/test/unit/net/tcp-receive.js index 6240ae998..71d1648da 100644 --- a/js/test/unit/net/tcp-receive.js +++ b/js/test/unit/net/tcp-receive.js @@ -16,9 +16,9 @@ /* eslint-disable comma-dangle, array-bracket-spacing */ -const test = require('tape'); -// const assert = require('assert'); -const TCPSocket = require('../../../core/net/tcp-socket'); +const createSuite = require('estap'); +const test = createSuite(); +const TCPSocket = require('/js/core/net/tcp-socket'); function receiveTest(opts, cb) { const initialSequenceNumber = opts.initialSequenceNumber; @@ -91,15 +91,15 @@ function receiveTestBatch(t, seqList, bufs) { receiveTest({ initialSequenceNumber: seq, bufs, - }, () => t.ok(true, `default ordered, initial sequence number ${seq}`)); + }, () => t.pass(`default ordered, initial sequence number ${seq}`)); receiveTest({ initialSequenceNumber: seq, bufs: reversed, - }, () => t.ok(true, `reverse ordered, initial sequence number ${seq}`)); + }, () => t.pass(`reverse ordered, initial sequence number ${seq}`)); receiveTest({ initialSequenceNumber: seq, bufs: shuffled, - }, () => t.ok(true, `random ordered, initial sequence number ${seq}`)); + }, () => t.pass(`random ordered, initial sequence number ${seq}`)); }); } @@ -107,7 +107,7 @@ const sequenceNumbersList = [ 0, 1, Math.pow(2, 32) - 1, Math.pow(2, 32) - 2, Math.pow(2, 32) - 3, Math.pow(2, 32) - 9999, ]; -test('receive fast path (small sequence numbers)', (t) => { +test.cb('receive fast path (small sequence numbers)', t => { receiveTest({ initialSequenceNumber: 1, bufs: [{ @@ -120,10 +120,10 @@ test('receive fast path (small sequence numbers)', (t) => { seqOffset: 5, len: 1, }, ], - }, t.end.bind(t)); + }, t.end); }); -test('receive fast path (large sequence numbers) #1', (t) => { +test.cb('receive fast path (large sequence numbers) #1', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 4, bufs: [{ @@ -154,10 +154,10 @@ test('receive fast path (large sequence numbers) #1', (t) => { seqOffset: 25, len: 1, }, ], - }, t.end.bind(t)); + }, t.end); }); -test('receive fast path (large sequence numbers) #2', (t) => { +test.cb('receive fast path (large sequence numbers) #2', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 3, bufs: [{ @@ -173,10 +173,10 @@ test('receive fast path (large sequence numbers) #2', (t) => { seqOffset: 6, len: 4, }, ], - }, t.end.bind(t)); + }, t.end); }); -test('receive fast path (large sequence numbers) #3', (t) => { +test.cb('receive fast path (large sequence numbers) #3', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 1, bufs: [{ @@ -192,10 +192,10 @@ test('receive fast path (large sequence numbers) #3', (t) => { seqOffset: 6, len: 4, }, ], - }, t.end.bind(t)); + }, t.end); }); -test('receive fast path (large sequence numbers) #4', (t) => { +test.cb('receive fast path (large sequence numbers) #4', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 1, bufs: [{ @@ -205,10 +205,10 @@ test('receive fast path (large sequence numbers) #4', (t) => { seqOffset: 10, len: 1, }, ], - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (small sequence numbers) #1', (t) => { +test.cb('receive reverse order (small sequence numbers) #1', t => { receiveTest({ initialSequenceNumber: 1, bufs: [{ @@ -221,10 +221,10 @@ test('receive reverse order (small sequence numbers) #1', (t) => { seqOffset: 5, len: 1, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (small sequence numbers) #2', (t) => { +test.cb('receive reverse order (small sequence numbers) #2', t => { receiveTest({ initialSequenceNumber: 1, bufs: [{ @@ -255,10 +255,10 @@ test('receive reverse order (small sequence numbers) #2', (t) => { seqOffset: 25, len: 1, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (large sequence numbers) #1', (t) => { +test.cb('receive reverse order (large sequence numbers) #1', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 4, bufs: [{ @@ -274,10 +274,10 @@ test('receive reverse order (large sequence numbers) #1', (t) => { seqOffset: 9, len: 2, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (large sequence numbers) #2', (t) => { +test.cb('receive reverse order (large sequence numbers) #2', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 4, bufs: [{ @@ -308,10 +308,10 @@ test('receive reverse order (large sequence numbers) #2', (t) => { seqOffset: 25, len: 1, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (large sequence numbers) #3', (t) => { +test.cb('receive reverse order (large sequence numbers) #3', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 3, bufs: [{ @@ -327,10 +327,10 @@ test('receive reverse order (large sequence numbers) #3', (t) => { seqOffset: 6, len: 4, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (large sequence numbers) #4', (t) => { +test.cb('receive reverse order (large sequence numbers) #4', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 1, bufs: [{ @@ -346,10 +346,10 @@ test('receive reverse order (large sequence numbers) #4', (t) => { seqOffset: 6, len: 4, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive reverse order (large sequence numbers) #5', (t) => { +test.cb('receive reverse order (large sequence numbers) #5', t => { receiveTest({ initialSequenceNumber: Math.pow(2, 32) - 1, bufs: [{ @@ -359,10 +359,10 @@ test('receive reverse order (large sequence numbers) #5', (t) => { seqOffset: 10, len: 1, }, ].reverse(), - }, t.end.bind(t)); + }, t.end); }); -test('receive mixed fast path and reverse order', (t) => { +test.cb('receive mixed fast path and reverse order', t => { receiveTestBatch(t, sequenceNumbersList, [{ seqOffset: 25, len: 1, @@ -393,7 +393,7 @@ test('receive mixed fast path and reverse order', (t) => { }, ]); }); -test('receive fast path duplicates', (t) => { +test.cb('receive fast path duplicates', t => { receiveTestBatch(t, sequenceNumbersList, [{ seqOffset: 0, len: 1, @@ -427,7 +427,7 @@ test('receive fast path duplicates', (t) => { }, ]); }); -test('receive overlapped duplicated data', (t) => { +test.cb('receive overlapped duplicated data', t => { receiveTestBatch(t, sequenceNumbersList, [{ seqOffset: 0, len: 8, @@ -455,7 +455,7 @@ test('receive overlapped duplicated data', (t) => { }, ]); }); -test('receive mixed overlapped duplicated and non-duplicated data', (t) => { +test.cb('receive mixed overlapped duplicated and non-duplicated data', (t) => { receiveTestBatch(t, sequenceNumbersList, [{ seqOffset: 0, len: 8, @@ -494,5 +494,3 @@ test('receive mixed overlapped duplicated and non-duplicated data', (t) => { len: 2, }, ]); }); - -/* eslint-enable comma-dangle, array-bracket-spacing */ diff --git a/js/test/unit/net/tcp.js b/js/test/unit/net/tcp.js index bfd737698..c306de22f 100644 --- a/js/test/unit/net/tcp.js +++ b/js/test/unit/net/tcp.js @@ -16,13 +16,13 @@ 'use strict'; -const test = require('tape'); -const assert = require('assert'); -const TCPSocket = require('../../../core/net/tcp-socket'); -const TCPServerSocket = require('../../../core/net/tcp-server-socket'); -const IP4Address = require('../../../core/net/ip4-address'); -const tcpHeader = require('../../../core/net/tcp-header'); -const tcpSocketState = require('../../../core/net/tcp-socket-state'); +const createSuite = require('estap'); +const test = createSuite(); +const TCPSocket = require('/js/core/net/tcp-socket'); +const TCPServerSocket = require('/js/core/net/tcp-server-socket'); +const IP4Address = require('/js/core/net/ip4-address'); +const tcpHeader = require('/js/core/net/tcp-header'); +const tcpSocketState = require('/js/core/net/tcp-socket-state'); function createTcpPacket(seq, ack, flags, window = 8192, u8data = null) { const u8 = new Uint8Array(tcpHeader.headerLength + (u8data ? u8data.length : 0)); @@ -34,15 +34,17 @@ function createTcpPacket(seq, ack, flags, window = 8192, u8data = null) { return u8; } -function getEstablished(cb) { +let nextPort = 81; + +function getEstablished(t, cb) { const socket = new TCPSocket(); const txSeq = 1; let rxSeq = 0; function ACK(seq, ack, flags, window, u8) { - assert.equal(flags, tcpHeader.FLAG_ACK); - assert.equal(u8, null); - assert.equal(ack, txSeq + 1); + t.is(flags, tcpHeader.FLAG_ACK); + t.is(u8, null); + t.is(ack, txSeq + 1); socket._transmit = () => {}; socket._state = tcpSocketState.STATE_ESTABLISHED; cb(socket, txSeq, rxSeq, () => { @@ -51,8 +53,8 @@ function getEstablished(cb) { } function SYN(seq, ack, flags, window, u8) { - assert.equal(flags, tcpHeader.FLAG_SYN); - assert.equal(u8, null); + t.is(flags, tcpHeader.FLAG_SYN); + t.is(u8, null); socket._transmit = ACK; const synack = createTcpPacket(txSeq, seq + 1, tcpHeader.FLAG_SYN | tcpHeader.FLAG_ACK); @@ -60,33 +62,35 @@ function getEstablished(cb) { socket._receive(synack, IP4Address.ANY, 45001, 0); } - assert.equal(socket._state, tcpSocketState.STATE_CLOSED); + t.is(socket._state, tcpSocketState.STATE_CLOSED); socket._transmit = SYN; - socket.open('127.0.0.1', 80); + socket.open('127.0.0.1', nextPort++); } -test('tcp connect', (t) => { +getEstablished.plan = 6; + +test.cb('tcp connect', t => { t.plan(6); const socket = new TCPSocket(); const serverSeq = 1; function testACK(seq, ack, flags, window, u8) { - t.equal(flags, tcpHeader.FLAG_ACK, 'ACK flag set'); - t.equal(u8, null, 'no buffer in ACK packet'); - t.equal(ack, serverSeq + 1, 'seq number is valid'); + t.is(flags, tcpHeader.FLAG_ACK, 'ACK flag set'); + t.is(u8, null, 'no buffer in ACK packet'); + t.is(ack, serverSeq + 1, 'seq number is valid'); socket._destroy(); } function testSYN(seq, ack, flags, window, u8) { - t.equal(flags, tcpHeader.FLAG_SYN, 'SYN flag set'); - t.equal(u8, null, 'no buffer in SYN packet'); + t.is(flags, tcpHeader.FLAG_SYN, 'SYN flag set'); + t.is(u8, null, 'no buffer in SYN packet'); socket._transmit = testACK; const synack = createTcpPacket(serverSeq, seq + 1, tcpHeader.FLAG_SYN | tcpHeader.FLAG_ACK); socket._receive(synack, IP4Address.ANY, 45001, 0); } - t.equal(socket._state, tcpSocketState.STATE_CLOSED, 'initial state is closed'); + t.is(socket._state, tcpSocketState.STATE_CLOSED, 'initial state is closed'); socket._transmit = testSYN; socket.open('127.0.0.1', 80); }); @@ -106,27 +110,26 @@ test('tcp transmit queue', (t) => { socket._transmitWindowSize = 20; socket.send(new Uint8Array(1)); socket.send(new Uint8Array(30)); - t.equal(socket._transmitQueue.length, 2); - t.equal(transmitQueueItemLength(socket._transmitQueue[0]), 1); - t.equal(transmitQueueItemLength(socket._transmitQueue[1]), 19); - t.equal(transmitQueueItemBuffer(socket._transmitQueue[0]).length, 1); - t.equal(transmitQueueItemBuffer(socket._transmitQueue[1]).length, 19); + t.is(socket._transmitQueue.length, 2); + t.is(transmitQueueItemLength(socket._transmitQueue[0]), 1); + t.is(transmitQueueItemLength(socket._transmitQueue[1]), 19); + t.is(transmitQueueItemBuffer(socket._transmitQueue[0]).length, 1); + t.is(transmitQueueItemBuffer(socket._transmitQueue[1]).length, 19); socket._acceptACK(socket._getTransmitPosition(), 20); - t.equal(socket._transmitQueue.length, 1); - t.end(); + t.is(socket._transmitQueue.length, 1); }); -test('tcp receive', (t) => { - t.plan(4); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive', t => { + t.plan(getEstablished.plan + 4); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data = new Uint8Array([1, 2, 3]); socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], 1); - t.equal(u8[1], 2); - t.equal(u8[2], 3); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], 1); + t.is(u8[1], 2); + t.is(u8[2], 3); }; const packet = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data); @@ -134,17 +137,17 @@ test('tcp receive', (t) => { }); }); -test('tcp receive filter full duplicates', (t) => { - t.plan(4); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive filter full duplicates', t => { + t.plan(getEstablished.plan + 4); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data = new Uint8Array([1, 2, 3]); socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], 1); - t.equal(u8[1], 2); - t.equal(u8[2], 3); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], 1); + t.is(u8[1], 2); + t.is(u8[2], 3); }; const packet = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data); @@ -153,19 +156,19 @@ test('tcp receive filter full duplicates', (t) => { }); }); -test('tcp receive in order', (t) => { - t.plan(8); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive in order', t => { + t.plan(getEstablished.plan + 8); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data1 = new Uint8Array([1, 2, 3]); const data2 = new Uint8Array([4, 5, 6]); let index = 0; socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], ++index); - t.equal(u8[1], ++index); - t.equal(u8[2], ++index); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], ++index); + t.is(u8[1], ++index); + t.is(u8[2], ++index); }; const packet1 = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data1); @@ -175,10 +178,10 @@ test('tcp receive in order', (t) => { }); }); -test('tcp receive in order and filter full duplicates', (t) => { - t.plan(9); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive in order and filter full duplicates', t => { + t.plan(getEstablished.plan + 9); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data1 = new Uint8Array([1, 2, 3]); const data2 = new Uint8Array([4, 5, 6]); @@ -189,10 +192,10 @@ test('tcp receive in order and filter full duplicates', (t) => { let index = 0; socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], ++index); - t.equal(u8[1], ++index); - t.equal(u8[2], ++index); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], ++index); + t.is(u8[1], ++index); + t.is(u8[2], ++index); }; const packet1 = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data1); @@ -202,14 +205,14 @@ test('tcp receive in order and filter full duplicates', (t) => { socket._receive(packet2, IP4Address.ANY, 45001, 0); socket._receive(packet1, IP4Address.ANY, 45001, 0); socket._receive(packet1, IP4Address.ANY, 45001, 0); - t.equal(lastAck, 8); + t.is(lastAck, 8); }); }); -test('tcp receive partial duplicates', (t) => { - t.plan(9); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive partial duplicates', (t) => { + t.plan(getEstablished.plan + 9); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data1 = new Uint8Array([1, 2, 3]); const data2 = new Uint8Array([3, 4, 5, 6]); const data3 = new Uint8Array([2, 3]); @@ -221,10 +224,10 @@ test('tcp receive partial duplicates', (t) => { let index = 0; socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], ++index); - t.equal(u8[1], ++index); - t.equal(u8[2], ++index); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], ++index); + t.is(u8[1], ++index); + t.is(u8[2], ++index); }; const packet1 = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data1); @@ -234,14 +237,14 @@ test('tcp receive partial duplicates', (t) => { socket._receive(packet1, IP4Address.ANY, 45001, 0); socket._receive(packet3, IP4Address.ANY, 45001, 0); socket._receive(packet3, IP4Address.ANY, 45001, 0); - t.equal(lastAck, 8); + t.is(lastAck, 8); }); }); -test('tcp receive partial duplicate with acked data and small window', (t) => { - t.plan(9); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive partial duplicate with acked data and small window', t => { + t.plan(getEstablished.plan + 9); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); const data1 = new Uint8Array([1, 2, 3]); const data2 = new Uint8Array([1, 2, 3, 4, 5, 6]); @@ -252,10 +255,10 @@ test('tcp receive partial duplicate with acked data and small window', (t) => { let index = 0; socket.ondata = (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8[0], ++index); - t.equal(u8[1], ++index); - t.equal(u8[2], ++index); + t.true(u8 instanceof Uint8Array); + t.is(u8[0], ++index); + t.is(u8[1], ++index); + t.is(u8[2], ++index); }; const packet1 = createTcpPacket(txSeq + 1, rxSeq, tcpHeader.FLAG_PSH | tcpHeader.FLAG_ACK, 8192, data1); @@ -264,52 +267,52 @@ test('tcp receive partial duplicate with acked data and small window', (t) => { socket._receive(packet1, IP4Address.ANY, 45001, 0); socket._receiveWindowSize = 3; socket._receive(packet2, IP4Address.ANY, 45001, 0); - t.equal(lastAck, 8); + t.is(lastAck, 8); }); }); -test('tcp send FIN', (t) => { - t.plan(6); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp send FIN', (t) => { + t.plan(getEstablished.plan + 6); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); function recvACK(seq, ack, flags) { - t.ok(flags & tcpHeader.FLAG_ACK); + t.truthy(flags & tcpHeader.FLAG_ACK); } function recvACKFIN(seq, ack, flags) { let packet; socket._transmit = recvACK; - t.ok(flags & tcpHeader.FLAG_FIN); - t.equal(socket._state, tcpSocketState.STATE_FIN_WAIT_1); + t.truthy(flags & tcpHeader.FLAG_FIN); + t.is(socket._state, tcpSocketState.STATE_FIN_WAIT_1); packet = createTcpPacket(txSeq, seq + 1, tcpHeader.FLAG_ACK); socket._receive(packet, IP4Address.ANY, 45001, 0); - t.equal(socket._state, tcpSocketState.STATE_FIN_WAIT_2); + t.is(socket._state, tcpSocketState.STATE_FIN_WAIT_2); packet = createTcpPacket(txSeq, seq + 1, tcpHeader.FLAG_FIN); socket._receive(packet, IP4Address.ANY, 45001, 0); - t.equal(socket._state, tcpSocketState.STATE_TIME_WAIT); + t.is(socket._state, tcpSocketState.STATE_TIME_WAIT); } socket._transmit = recvACKFIN; socket.close(); - t.equal(socket._state, tcpSocketState.STATE_TIME_WAIT); + t.is(socket._state, tcpSocketState.STATE_TIME_WAIT); }); }); -test('tcp receive FIN', (t) => { - t.plan(5); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive FIN', t => { + t.plan(getEstablished.plan + 5); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); function onRecvFIN(seq, ack, flags) { socket._transmit = () => {}; - t.ok(flags & tcpHeader.FLAG_ACK); + t.truthy(flags & tcpHeader.FLAG_ACK); } function onSentFIN(seq, ack, flags) { socket._transmit = () => {}; - t.ok(flags & tcpHeader.FLAG_FIN); - t.equal(socket._state, tcpSocketState.STATE_LAST_ACK); + t.truthy(flags & tcpHeader.FLAG_FIN); + t.is(socket._state, tcpSocketState.STATE_LAST_ACK); const packet = createTcpPacket(txSeq, seq + 1, tcpHeader.FLAG_ACK); socket._receive(packet, IP4Address.ANY, 45001, 0); } @@ -317,22 +320,22 @@ test('tcp receive FIN', (t) => { socket._transmit = onRecvFIN; const packet = createTcpPacket(txSeq, rxSeq, tcpHeader.FLAG_FIN | tcpHeader.FLAG_ACK); socket._receive(packet, IP4Address.ANY, 45001, 0); - t.equal(socket._state, tcpSocketState.STATE_CLOSE_WAIT); + t.is(socket._state, tcpSocketState.STATE_CLOSE_WAIT); socket._transmit = onSentFIN; socket.close(); - t.equal(socket._state, tcpSocketState.STATE_CLOSED); + t.is(socket._state, tcpSocketState.STATE_CLOSED); }); }); -test('tcp receive FIN, then send more data', (t) => { - t.plan(4); - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('tcp receive FIN, then send more data', t => { + t.plan(getEstablished.plan + 4); + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); function handleLastAck(seq, ack, flags) { socket._transmit = () => {}; - t.ok(flags & tcpHeader.FLAG_FIN); - t.equal(socket._state, tcpSocketState.STATE_LAST_ACK); + t.truthy(flags & tcpHeader.FLAG_FIN); + t.is(socket._state, tcpSocketState.STATE_LAST_ACK); const packet = createTcpPacket(txSeq, seq + 1, tcpHeader.FLAG_ACK); socket._receive(packet, IP4Address.ANY, 45001, 0); } @@ -341,18 +344,18 @@ test('tcp receive FIN, then send more data', (t) => { socket.send(new Uint8Array([1, 2, 3])); const packet = createTcpPacket(txSeq, rxSeq, tcpHeader.FLAG_FIN | tcpHeader.FLAG_ACK); socket._receive(packet, IP4Address.ANY, 45001, 0); - t.equal(socket._state, tcpSocketState.STATE_CLOSE_WAIT); + t.is(socket._state, tcpSocketState.STATE_CLOSE_WAIT); socket.send(new Uint8Array([4, 5, 6])); socket.send(new Uint8Array([7, 8, 9])); socket._transmit = handleLastAck; socket.close(); - t.equal(socket._state, tcpSocketState.STATE_CLOSED); + t.is(socket._state, tcpSocketState.STATE_CLOSED); }); }); -test('cannot send more data after close', (t) => { - getEstablished((socket, txSeq, rxSeq, done) => { - t.on('end', done); +test.cb('cannot send more data after close', (t) => { + getEstablished(t, (socket, txSeq, rxSeq, done) => { + t.after(done); socket._transmit = () => {}; socket.send(new Uint8Array([1, 2, 3])); socket.close(); @@ -361,48 +364,46 @@ test('cannot send more data after close', (t) => { }); }); -test('server socket listening', (t) => { +test.cb('server socket listening', t => { t.plan(6); const socket = new TCPServerSocket(); socket.onlisten = (port) => { - t.equal(port, 100); - t.ok(true); + t.is(port, 100); + t.pass(); }; - socket.onclose = () => t.ok(true); + socket.onclose = () => t.pass(); socket.listen(100); - t.equal(socket.localPort, 100); + t.is(socket.localPort, 100); t.throws(() => { socket.localPort = 200; }); - t.equal(socket.localPort, 100); + t.is(socket.localPort, 100); socket.close(); }); -test('server socket can reuse port', (t) => { +test('server socket can reuse port', () => { const socket = new TCPServerSocket(); socket.listen(200); socket.close(); socket.listen(200); socket.close(); - // const socket2 = new TCPServerSocket(); socket.listen(200); socket.close(); - t.end(); }); -test('server socket can listen to random port', (t) => { +test.cb('server socket can listen to random port', (t) => { t.plan(2); const socket = new TCPServerSocket(); - socket.onlisten = (port) => t.ok(port > 0); + socket.onlisten = port => t.true(port > 0); socket.listen(0); - t.ok(socket.localPort > 0); + t.true(socket.localPort > 0); socket.close(); }); -test('localhost echo server', (t) => { +test.cb('localhost echo server', t => { t.plan(8); const server = new TCPServerSocket(); server.listen(71); @@ -411,7 +412,7 @@ test('localhost echo server', (t) => { socket.onend = () => { socket.close(); server.close(); - t.ok(true); + t.pass(); }; }; @@ -423,33 +424,33 @@ test('localhost echo server', (t) => { }; client.ondata = (u8) => { for (let i = 0; i < u8.length; ++i) { - t.equal(u8[i], ++recvIndex); + t.is(u8[i], ++recvIndex); } if (recvIndex === 6) { client.close(); - t.ok(true); + t.pass(); } }; client.open('127.0.0.1', 71); }); -test('small sequence numbers', (t) => { +test.cb('small sequence numbers', t => { t.plan(4); const server = new TCPServerSocket(); - t.on('end', server.close.bind(server)); + t.after(server.close.bind(server)); let next = 0; server.onconnect = (socket) => { socket.ondata = (u8) => { for (let i = 0; i < u8.length; ++i) { - t.equal(u8[i], ++next); + t.is(u8[i], ++next); } }; }; - server.listen(71); + server.listen(72); const client = new TCPSocket(); client._transmitWindowEdge = 1; @@ -458,28 +459,28 @@ test('small sequence numbers', (t) => { client.onopen = () => { client.send(new Uint8Array([1, 2, 3])); client.close(); - t.ok(true); + t.pass(); }; - client.open('127.0.0.1', 71); + client.open('127.0.0.1', 72); }); -test('large sequence numbers and wrap around', (t) => { +test.cb('large sequence numbers and wrap around', t => { t.plan(4); const server = new TCPServerSocket(); - t.on('end', server.close.bind(server)); + t.after(server.close.bind(server)); let next = 0; server.onconnect = (socket) => { socket.ondata = (u8) => { for (let i = 0; i < u8.length; ++i) { - t.equal(u8[i], ++next); + t.is(u8[i], ++next); } }; }; - server.listen(71); + server.listen(73); const client = new TCPSocket(); client._transmitWindowEdge = Math.pow(2, 32) - 1; @@ -488,10 +489,8 @@ test('large sequence numbers and wrap around', (t) => { client.onopen = () => { client.send(new Uint8Array([1, 2, 3])); client.close(); - t.ok(true); + t.pass(); }; - client.open('127.0.0.1', 71); + client.open('127.0.0.1', 73); }); - -/* eslint-enable no-param-reassign */ diff --git a/js/test/unit/platform/index.js b/js/test/unit/platform/index.js index 53d2f9c56..43a04ad19 100644 --- a/js/test/unit/platform/index.js +++ b/js/test/unit/platform/index.js @@ -13,50 +13,60 @@ // limitations under the License. 'use strict'; -const test = require('tape'); +const createSuite = require('estap'); +const test = createSuite(); /* global TextEncoder */ /* global TextDecoder */ -test('__SYSCALL.eval valid code', (t) => { +test('__SYSCALL.eval valid code', t => { global.a = 10; __SYSCALL.eval('a++'); - t.equal(global.a, 11); - t.end(); + t.is(global.a, 11, 'eval works'); }); -test('__SYSCALL.eval invalid code', (t) => { +test('__SYSCALL.eval invalid code', t => { t.plan(1); try { __SYSCALL.eval('not a js'); } catch (e) { - t.ok(e instanceof SyntaxError, 'throws on syntax error'); + t.is(e instanceof SyntaxError, true, 'throws on syntax error'); } }); -test('__SYSCALL.eval throws', (t) => { +test('__SYSCALL.eval throws', t => { t.plan(2); try { __SYSCALL.eval('throw new Error("some error")'); } catch (e) { - t.ok(e instanceof Error, 'caught error thrown from the script'); - t.ok(e.message.indexOf('some error') >= 0, 'error object is valid'); + t.is(e instanceof Error, true, 'caught error thrown from the script'); + t.true(e.message.indexOf('some error') >= 0, 'error object is valid'); } }); -test('TextEncoder and TextDecoder', (t) => { +test('TextEncoder and TextDecoder', t => { const encoder = new TextEncoder('utf-8'); const decoder = new TextDecoder('utf-8'); const u8 = encoder.encode('test string'); - t.ok(u8 instanceof Uint8Array); - t.equal(decoder.decode(u8), 'test string'); - t.end(); + t.true(u8 instanceof Uint8Array, 'encoded result is u8 array'); + t.is(decoder.decode(u8), 'test string', 'decoded result is a string'); }); -test('TextEncoder and TextDecoder call as a function', (t) => { - t.throws(() => TextEncoder('utf-8')); // eslint-disable-line new-cap - t.throws(() => TextDecoder('utf-8')); // eslint-disable-line new-cap - t.end(); +test('TextEncoder and TextDecoder call as a function', t => { + t.throws(() => TextEncoder('utf-8'), // eslint-disable-line new-cap + [Error, 'constructor cannot be called as a function'], + 'TextEncoder() throws'); + t.throws(() => TextDecoder('utf-8'), // eslint-disable-line new-cap + [Error, 'constructor cannot be called as a function'], + 'TextDecoder() throws'); +}); + +test('module globals', t => { + t.true(typeof require.resolve === 'function', 'require.resolve exists'); + t.is(require.resolve('../index'), '/js/test/unit/index.js', 'require.resolve can resolve path'); + t.is(__filename, '/js/test/unit/platform/index.js', '__filename global'); + t.is(__dirname, '/js/test/unit/platform', '__dirname global'); + t.truthy(module, 'module global'); }); diff --git a/js/test/unit/random/index.js b/js/test/unit/random/index.js index 48b78496d..1397e03cb 100644 --- a/js/test/unit/random/index.js +++ b/js/test/unit/random/index.js @@ -14,13 +14,12 @@ 'use strict'; -const test = require('tape'); -const runtime = require('../../../core'); -const EntropySource = require('../../../core/random/entropy-source'); - -test('EntropySource', (t) => { - t.timeoutAfter(1000); +const createSuite = require('estap'); +const test = createSuite(); +const random = require('/js/core/random'); +const EntropySource = require('/js/core/random/entropy-source'); +test.cb('EntropySource', t => { const source = new EntropySource('test-source'); source.ongetbytes = (u8, cb) => { for (let i = 0; i < u8.length; ++i) { @@ -31,41 +30,37 @@ test('EntropySource', (t) => { const u8 = new Uint8Array(3); source.getBytes(u8, () => { - t.equal(u8[0], 0x33); - t.equal(u8[1], 0x34); - t.equal(u8[2], 0x35); + t.is(u8[0], 0x33); + t.is(u8[1], 0x34); + t.is(u8[2], 0x35); t.end(); }); }); -test('getTrueRandomValues buffer', (t) => { - t.timeoutAfter(1000); +test.cb('getTrueRandomValues buffer', t => { const u8 = new Uint8Array([0, 0, 0]); - runtime.random.getTrueRandomValues(u8, (u8out) => { - t.equal(u8, u8out); + random.getTrueRandomValues(u8, (u8out) => { + t.is(u8, u8out); t.end(); }); }); -test('getTrueRandomValues length', (t) => { - t.timeoutAfter(1000); - runtime.random.getTrueRandomValues(4, (u8) => { - t.ok(u8 instanceof Uint8Array); - t.equal(u8.length, 4); +test.cb('getTrueRandomValues length', t => { + random.getTrueRandomValues(4, (u8) => { + t.true(u8 instanceof Uint8Array); + t.is(u8.length, 4); t.end(); }); }); -test('getRandomValues buffer', (t) => { +test('getRandomValues buffer', t => { const u8 = new Uint8Array([0, 0, 0]); - const u8out = runtime.random.getRandomValues(u8); - t.equal(u8, u8out); - t.end(); + const u8out = random.getRandomValues(u8); + t.is(u8, u8out); }); -test('getRandomValues length', (t) => { - const u8 = runtime.random.getRandomValues(10); - t.ok(u8 instanceof Uint8Array); - t.equal(u8.length, 10); - t.end(); +test('getRandomValues length', t => { + const u8 = random.getRandomValues(10); + t.true(u8 instanceof Uint8Array); + t.is(u8.length, 10); }); diff --git a/js/test/unit/script/index.js b/js/test/unit/script/index.js index dee96b908..00f79394f 100644 --- a/js/test/unit/script/index.js +++ b/js/test/unit/script/index.js @@ -14,31 +14,28 @@ 'use strict'; -const test = require('tape'); +const createSuite = require('estap'); +const test = createSuite(); -test('div by zero should not trigger CPU Divide Error exception', (t) => { +test('division by zero', t => { const v = 10; - t.equal(v / 0, Infinity); - t.end(); + t.is(v / 0, Infinity, 'should not trigger CPU Divide Error exception'); }); -test('some math functions (in case they rely on embedded libc)', (t) => { - t.equal(Math.abs(-45.4), 45.4); - t.equal(Math.acos(-0.2).toFixed(2), '1.77'); - t.equal((Math.atan2(1, 0) * 2).toFixed(2), '3.14'); - t.equal((Math.atan(1, 0) * 4).toFixed(2), '3.14'); - t.equal(Math.pow(2, 10), 1024); - t.equal(Math.sqrt(81), 9); - t.equal(Math.exp(1).toFixed(2), '2.72'); - t.end(); +test('basic math functions (may use embedded libc)', t => { + t.is(Math.abs(-45.4), 45.4, 'Math.abs'); + t.is(Math.acos(-0.2).toFixed(2), '1.77', 'Math.acos'); + t.is((Math.atan2(1, 0) * 2).toFixed(2), '3.14', 'Math.atan2'); + t.is((Math.atan(1, 0) * 4).toFixed(2), '3.14', 'Math.atan'); + t.is(Math.pow(2, 10), 1024, 'Math.pow'); + t.is(Math.sqrt(81), 9, 'Math.sqrt'); + t.is(Math.exp(1).toFixed(2), '2.72', 'Math.exp'); }); -test('Math.random', (t) => { - t.equal(typeof Math.random(), 'number'); - t.end(); +test('Math.random', t => { + t.is(typeof Math.random(), 'number', 'Math.random'); }); -test('Date object', (t) => { - t.equal(typeof new Date(), 'object'); - t.end(); +test('Date object', t => { + t.is(typeof new Date(), 'object', 'can create Date() object'); }); diff --git a/js/test/unit/timers/index.js b/js/test/unit/timers/index.js index c6fd64a8b..f9cc05d02 100644 --- a/js/test/unit/timers/index.js +++ b/js/test/unit/timers/index.js @@ -14,28 +14,98 @@ 'use strict'; -const test = require('tape'); +const createSuite = require('estap'); +const test = createSuite(); -test('setTimeout', t => setTimeout(t.end.bind(t), 0)); +test.cb('setTimeout', t => { + t.plan(1); -test('setImmediate', t => setImmediate(t.end.bind(t))); + setTimeout(() => { + t.pass('setTimeout callback'); + }, 0); +}); -test('clearTimeout', (t) => { +test.cb('setImmediate', t => { + t.plan(1); + + setImmediate(() => { + t.pass('setImmediate callback'); + }); +}); + +test.cb('clearTimeout', t => { const timer = setTimeout(() => { - t.fail('should not call callback'); - throw new Error('should not call callback'); + t.fail('should not call the callback'); }, 0); clearTimeout(timer); - setTimeout(t.end.bind(t), 0); + setTimeout(() => { + t.pass('cleared timeout'); + t.end(); + }, 0); }); -test('clearInterval', (t) => { - function timer() { - setInterval(() => { - t.fail('should not call callback'); - throw new Error('should not call callback'); - }, 0); - } +test.cb('clearInterval', t => { + const timer = setInterval(() => { + t.fail('should not call the callback'); + }, 0); clearInterval(timer); - setTimeout(t.end.bind(t), 0); + setTimeout(() => { + t.pass('cleared interval'); + t.end(); + }, 0); +}); + +test.cb('setInterval multiple calls', t => { + let i = 0; + const timer = setInterval(() => { + if (++i === 3) { + clearInterval(timer); + t.pass('wait for 3 intervals'); + t.end(); + } + }, 100); +}); + +test.cb('cleared setTimeout should not keep its ref', t => { + const timer = setTimeout(() => { + t.fail('should not call the callback'); + }, 1000000); + clearTimeout(timer); + t.end(); +}); + +test.cb('unrefTimer with long setTimeout', t => { + const timer = setTimeout(() => { + t.fail('should not call the callback'); + }, 1000000); + __SYSCALL.unrefTimer(timer); + t.end(); +}); + +test.cb('unrefTimer with long setInterval', t => { + const timer = setInterval(() => { + t.fail('should not call the callback'); + }, 1000000); + __SYSCALL.unrefTimer(timer); + t.end(); +}); + +test.cb('unrefTimer with clearTimeout', t => { + t.plan(2); + + const timer1 = setTimeout(() => { + t.pass('this should fire'); + }, 0); + + const timer2 = setTimeout(() => { + t.fail('this should not fire'); + }, 10); + + setTimeout(() => { + t.pass('this should fire'); + }, 100); + + __SYSCALL.unrefTimer(timer1); + __SYSCALL.unrefTimer(timer2); + clearTimeout(timer2); }); diff --git a/js/test/unit/virtio/index.js b/js/test/unit/virtio/index.js index 8edf8343f..355b31ff4 100644 --- a/js/test/unit/virtio/index.js +++ b/js/test/unit/virtio/index.js @@ -13,11 +13,10 @@ // limitations under the License. 'use strict'; -const test = require('tape'); -const resources = require('../../../core/resources'); -const mem = __SYSCALL.allocDMA(); -const VRing = require('../../../driver/virtio/vring'); -// const DescriptorTable = require('../../../driver/virtio/vring/descriptor-table'); +const createSuite = require('estap'); +const test = createSuite(); +const resources = require('/js/core/resources'); +const VRing = require('/js/driver/virtio/vring'); function clearBuffer(u8) { for (let i = 0; i < u8.length; ++i) { @@ -45,104 +44,105 @@ function getTwoPageBuffer() { return b; } -test('ring place one physical page buffer', (t) => { +test('ring place one physical page buffer', t => { + const mem = __SYSCALL.allocDMA(); const ring = new VRing(mem, 0, 16); - t.equal(ring.descriptorTable.descriptorsAvailable, 16); - t.equal(ring.availableRing.readIdx(), 0); + t.is(ring.descriptorTable.descriptorsAvailable, 16); + t.is(ring.availableRing.readIdx(), 0); ring.placeBuffers([getOnePageBuffer()], true); - t.equal(ring.descriptorTable.descriptorsAvailable, 15); - t.equal(ring.availableRing.readIdx(), 1); + t.is(ring.descriptorTable.descriptorsAvailable, 15); + t.is(ring.availableRing.readIdx(), 1); const descId = ring.availableRing.readDescriptorAsDevice(0); - t.equal(descId, 0); + t.is(descId, 0); const firstDesc = ring.descriptorTable.get(descId); const u8devWrite = ring.descriptorTable.getDescriptorBuffer(descId); bufferWriteNumbers(u8devWrite, 4); - t.equal(firstDesc.flags, 2 /* no next flag, write only flag */); - t.equal(firstDesc.len, 22); - t.equal(firstDesc.next, 1); - t.equal(ring.usedRing.readIdx(), 0); + t.is(firstDesc.flags, 2 /* no next flag, write only flag */); + t.is(firstDesc.len, 22); + t.is(firstDesc.next, 1); + t.is(ring.usedRing.readIdx(), 0); ring.usedRing.placeDescriptorAsDevice(descId, 5 /* bytes written */); - t.equal(ring.usedRing.readIdx(), 1); + t.is(ring.usedRing.readIdx(), 1); const u8 = ring.getBuffer(); - t.equal(ring.descriptorTable.descriptorsAvailable, 16); - t.ok(u8 instanceof Uint8Array); - t.equal(u8.length, 5); - t.equal(u8[0], 4); - t.equal(u8[1], 5); - t.equal(u8[2], 6); - t.equal(u8[3], 7); - t.equal(u8[4], 8); - t.end(); + t.is(ring.descriptorTable.descriptorsAvailable, 16); + t.true(u8 instanceof Uint8Array); + t.is(u8.length, 5); + t.is(u8[0], 4); + t.is(u8[1], 5); + t.is(u8[2], 6); + t.is(u8[3], 7); + t.is(u8[4], 8); }); -test('ring place two physical page buffer', (t) => { +test('ring place two physical page buffer', t => { + const mem = __SYSCALL.allocDMA(); const ring = new VRing(mem, 0, 16); - t.equal(ring.descriptorTable.descriptorsAvailable, 16); - t.equal(ring.availableRing.readIdx(), 0); + t.is(ring.descriptorTable.descriptorsAvailable, 16); + t.is(ring.availableRing.readIdx(), 0); ring.placeBuffers([getTwoPageBuffer()], false); - t.equal(ring.descriptorTable.descriptorsAvailable, 14); - t.equal(ring.availableRing.readIdx(), 1); + t.is(ring.descriptorTable.descriptorsAvailable, 14); + t.is(ring.availableRing.readIdx(), 1); const descId = ring.availableRing.readDescriptorAsDevice(0); - t.equal(descId, 0); + t.is(descId, 0); const firstDesc = ring.descriptorTable.get(descId); const u8devWrite = ring.descriptorTable.getDescriptorBuffer(descId); bufferWriteNumbers(u8devWrite, 4); - t.equal(firstDesc.flags, 1 /* next flag */); - t.equal(firstDesc.len, 12); - t.equal(firstDesc.next, 1); + t.is(firstDesc.flags, 1 /* next flag */); + t.is(firstDesc.len, 12); + t.is(firstDesc.next, 1); const nextDescId = firstDesc.next; const secondDesc = ring.descriptorTable.get(nextDescId); const u8devWrite2 = ring.descriptorTable.getDescriptorBuffer(nextDescId); bufferWriteNumbers(u8devWrite2, 0); - t.equal(secondDesc.flags, 0 /* no next flag */); - t.equal(secondDesc.len, 10); - t.equal(secondDesc.next, 2); - t.equal(ring.usedRing.readIdx(), 0); + t.is(secondDesc.flags, 0 /* no next flag */); + t.is(secondDesc.len, 10); + t.is(secondDesc.next, 2); + t.is(ring.usedRing.readIdx(), 0); ring.usedRing.placeDescriptorAsDevice(descId, 15 /* bytes written */); - t.equal(ring.usedRing.readIdx(), 1); + t.is(ring.usedRing.readIdx(), 1); const u8 = ring.getBuffer(); - t.equal(ring.descriptorTable.descriptorsAvailable, 16); - t.ok(u8 instanceof Uint8Array); - t.equal(u8.length, 15); - t.equal(u8[0], 4); - t.equal(u8[11], 15); - t.equal(u8[12], 0); // second subarray data - t.equal(u8[13], 1); - t.equal(u8[14], 2); - t.end(); + t.is(ring.descriptorTable.descriptorsAvailable, 16); + t.true(u8 instanceof Uint8Array); + t.is(u8.length, 15); + t.is(u8[0], 4); + t.is(u8[11], 15); + t.is(u8[12], 0); // second subarray data + t.is(u8[13], 1); + t.is(u8[14], 2); }); -test('ring fill all slots and process', (t) => { +test('ring fill all slots and process', t => { + const mem = __SYSCALL.allocDMA(); const ring = new VRing(mem, 0, 4 /* slots */); let i; let descId; - t.ok(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(0), 1)], true)); - t.ok(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(1), 2)], true)); - t.ok(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(2), 3)], true)); - t.ok(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(3), 4)], true)); - t.equal(ring.placeBuffers([getOnePageBuffer(4)], true), false); + t.true(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(0), 1)], true)); + t.true(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(1), 2)], true)); + t.true(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(2), 3)], true)); + t.true(ring.placeBuffers([bufferWriteNumbers(getOnePageBuffer(3), 4)], true)); + t.is(ring.placeBuffers([getOnePageBuffer(4)], true), false); const currentIdx = ring.availableRing.readIdx(); - t.equal(currentIdx, 4); + t.is(currentIdx, 4); for (i = 0; i < 3; ++i) { descId = ring.availableRing.readDescriptorAsDevice(i); ring.usedRing.placeDescriptorAsDevice(descId, 5 /* bytes written */); } - t.equal(ring.getBuffer()[0], 1); - t.equal(ring.getBuffer()[0], 2); - t.equal(ring.getBuffer()[0], 3); - t.equal(ring.getBuffer(), null); - t.equal(ring.getBuffer(), null); + t.is(ring.getBuffer()[0], 1); + t.is(ring.getBuffer()[0], 2); + t.is(ring.getBuffer()[0], 3); + t.is(ring.getBuffer(), null); + t.is(ring.getBuffer(), null); descId = ring.availableRing.readDescriptorAsDevice(3); ring.usedRing.placeDescriptorAsDevice(descId, 5 /* bytes written */); - t.equal(ring.getBuffer()[0], 4); - t.equal(ring.getBuffer(), null); - t.end(); + t.is(ring.getBuffer()[0], 4); + t.is(ring.getBuffer(), null); }); -test('vring operation', (t) => { +test('vring operation', t => { + const mem = __SYSCALL.allocDMA(); const ring = new VRing(mem, 0, 4); let devIndex = 0; let count = 0; @@ -150,9 +150,11 @@ test('vring operation', (t) => { function devProcessAll() { const bytesWritten = 3; let descId = 0; + t.false(ring.usedRing.hasUnprocessedBuffers(), 'no unprocessed buffers for the driver'); while (devIndex < ring.availableRing.readIdx()) { - descId = ring.availableRing.readDescriptorAsDevice(); + descId = ring.availableRing.readDescriptorAsDevice(devIndex); ring.usedRing.placeDescriptorAsDevice(descId, bytesWritten); + t.true(ring.usedRing.hasUnprocessedBuffers(), 'new unprocessed buffers'); --count; ++devIndex; } @@ -168,12 +170,11 @@ test('vring operation', (t) => { } } - t.equal(count, 0); + t.is(count, 0, 'no unhandled buffers'); for (let i = 0; i < 4; ++i) { driverProcessAll(); - t.ok(count > 0); + t.true(count > 0, 'unhandled buffers are ready'); devProcessAll(); } - t.equal(count, 0); - t.end(); + t.is(count, 0, 'no unhandled buffers'); }); diff --git a/js/utils/index.js b/js/utils/index.js index 07c488ae0..516dcaf42 100644 --- a/js/utils/index.js +++ b/js/utils/index.js @@ -23,3 +23,9 @@ exports.getRandomUint8 = () => Math.floor(Math.random() * 0x100) >>> 0; * (monotonic clock) */ exports.timeNow = () => Math.floor(performance.now()); + +exports.assert = (value, message) => { + if (!value) { + throw new Error(`AssertionError ${message ? (`: ${message}`) : ''}`); + } +}; diff --git a/package.json b/package.json index 71d2a6987..201d40a44 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "scripts": { "postinstall": "node scripts/update-versions.js", "lint": "eslint js", - "test": "runtimeify test/unit/index.js -o initrd && runtime-qemu ./initrd", - "test-build": "runtimeify test/unit/index.js -o initrd && runtime-qemu ./initrd --kernel ../disk/boot/runtime" + "test": "runtime pack . --system-entry /js/test/unit && runtime run --kernel disk/boot/runtime .initrd" }, "repository": { "type": "git", @@ -25,7 +24,7 @@ "eslint-config-airbnb-base": "^4.0.0", "eslint-plugin-import": "^1.10.2", "eslint-plugin-runtime-internal": "^1.0.0", - "tape": "^4.0.0" + "estap": "^1.2.0" }, "runtimejs": { "debug": false diff --git a/src/kernel/engines.h b/src/kernel/engines.h index f32027bb1..75ecda65e 100644 --- a/src/kernel/engines.h +++ b/src/kernel/engines.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace rt { @@ -100,16 +101,9 @@ class Engines { RT_ASSERT(first_engine); ResourceHandle st = first_engine->threads().Create(ThreadType::DEFAULT); - const char* filename = GLOBAL_initrd()->runtime_index_name(); - InitrdFile startup_file = GLOBAL_initrd()->Get(filename); - if (startup_file.IsEmpty()) { - printf("Unable to load %s from initrd.\n", filename); - abort(); - } - { TransportData data; - data.SetEvalData(startup_file.Data(), startup_file.Size(), "__loader"); + data.SetEvalData(reinterpret_cast(LOADER_JS), sizeof(LOADER_JS) - 1, "__loader"); std::unique_ptr msg(new ThreadMessage(ThreadMessage::Type::EVALUATE, ResourceHandle(), std::move(data))); diff --git a/src/kernel/initjs.h b/src/kernel/initjs.h index f0270c878..6f13ed3f8 100644 --- a/src/kernel/initjs.h +++ b/src/kernel/initjs.h @@ -16,7 +16,7 @@ const char INIT_JS[] = R"JAVASCRIPT( // JS code to prepare runtime.js environment -var console = (function() { +global.console = (() => { var times = {}; return { @@ -37,5 +37,75 @@ var console = (function() { }, }; })(); -// No more code here + +(() => { + const promises = new WeakMap(); + const setPromiseHandlers = __SYSCALL._setPromiseHandlers; + __SYSCALL._setPromiseHandlers = void 0; + let pendingTask = false; + let pendingPromises = []; + + function rejected(promise, reason) { + if (promises.has(promise)) { + return; + } + + promises.set(promise, false); + pendingPromises.push([promise, reason]); + setTask(); + } + + function rejectedHandled(promise) { + if (!promises.has(promise)) { + return; + } + + const hasBeenNotified = promises.get(promise); + promises.delete(promise); + + if (hasBeenNotified) { + if (typeof global.onrejectionhandled === 'function') { + setImmediate(() => global.onrejectionhandled({ promise })); + } else { + if (typeof global.onunhandledrejection !== 'function') { + console.log('Unhandled promise rejection has been handled.'); + } + } + } + } + + function setTask() { + if (pendingTask) { + return; + } + + pendingTask = true; + setImmediate(() => { + pendingTask = false; + + for (let i = 0; i < pendingPromises.length; ++i) { + let [promise, reason] = pendingPromises[i]; + if (!promises.has(promise)) { + return; + } + + promises.set(promise, true); + + if (typeof global.onunhandledrejection === 'function') { + setImmediate(() => global.onunhandledrejection({ promise, reason })); + } else { + if (reason instanceof Error) { + console.log('Possibly unhandled promise rejection: ' + String(reason.stack)); + } else { + console.log('Possibly unhandled promise rejection: ' + String(reason)); + } + } + } + + pendingPromises = []; + }); + } + + setPromiseHandlers(rejected, rejectedHandled); +})(); )JAVASCRIPT"; diff --git a/src/kernel/js/loaderjs.h b/src/kernel/js/loaderjs.h new file mode 100644 index 000000000..73be1d021 --- /dev/null +++ b/src/kernel/js/loaderjs.h @@ -0,0 +1,270 @@ +// Copyright 2014-2015 runtime.js project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +const char LOADER_JS[] = R"JAVASCRIPT( +'use strict'; +(() => { + function createLoader(existsFileFn, readFileFn, evalScriptFn, builtinsResolveFrom = '/') { + let builtins = {}; + const builtinsResolveFromComponents = builtinsResolveFrom.split('/'); + const cache = {}; + + function throwError(err) { + throw err; + } + + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + + function normalizePath(components) { + const r = []; + for (const p of components) { + if (p === '') { + if (r.length === 0) { + r.push(p); + } + continue; + } + + if (p === '.') { + continue; + } + + if (p === '..') { + if (r.length > 0) { + r.pop(); + } else { + return null; + } + } else { + r.push(p); + } + } + + return r; + } + + function loadAsFile(path) { + if (existsFileFn(path)) return path; + if (existsFileFn(`${path}.js`)) return `${path}.js`; + if (existsFileFn(`${path}.json`)) return `${path}.json`; + if (existsFileFn(`${path}.node`)) return `${path}.node`; + + return null; + } + + function getPackageMain(packageJsonFile) { + const json = readFileFn(packageJsonFile); + let parsed = null; + try { + parsed = JSON.parse(json); + } catch (e) { + throwError(new Error(`package.json '${packageJsonFile}' parse error`)); + } + + if (parsed.runtime) { + if (typeof parsed.runtime === 'string') { + return parsed.runtime; + } + throwError(new Error(`package.json '${packageJsonFile}' runtime field value is invalid`)); + } + + return parsed.main || 'index.js'; + } + + function loadAsDirectory(path, ignoreJson) { + let mainFile = 'index'; + let dir = false; + if (!ignoreJson && existsFileFn(`${path}/package.json`)) { + mainFile = getPackageMain(`${path}/package.json`) || 'index'; + dir = true; + } + + const normalizedPath = normalizePath(path.split('/').concat(mainFile.split('/'))); + if (!normalizedPath) { + return null; + } + + const s = normalizedPath.join('/'); + const res = loadAsFile(s); + if (res) { + return res; + } + + if (dir) { + return loadAsDirectory(s, true); + } + + return null; + } + + function loadNodeModules(dirComponents, parts) { + let count = dirComponents.length; + + while (count-- > 0) { + let p = dirComponents.slice(0, count + 1); + if (p.length === 0) { + continue; + } + + if (p[p.length - 1] === 'node_modules') { + continue; + } + + p.push('node_modules'); + p = p.concat(parts); + + const normalizedPath = normalizePath(p); + if (!normalizedPath) { + continue; + } + + const s = normalizedPath.join('/'); + const loadedPath = loadAsFile(s) || loadAsDirectory(s, false) || null; + if (loadedPath) { + return loadedPath; + } + } + + return null; + } + + function resolve(module, pathOpt = '') { + let path = String(pathOpt); + + let resolveFrom = module.dirComponents; + + if (Object.prototype.hasOwnProperty.call(builtins, path)) { + path = builtins[path]; + resolveFrom = builtinsResolveFromComponents; + } + + const pathComponents = path.split('/'); + const firstPathComponent = pathComponents[0]; + + // starts with ./ ../ or / + if (firstPathComponent === '.' || + firstPathComponent === '..' || + firstPathComponent === '') { + const combinedPathComponents = (firstPathComponent === '') ? + pathComponents : + resolveFrom.concat(pathComponents); + + const normalizedPath = normalizePath(combinedPathComponents); + if (!normalizedPath) { + return null; + } + + const pathStr = normalizedPath.join('/'); + const loadedPath = loadAsFile(pathStr) || loadAsDirectory(pathStr, false) || null; + return loadedPath; + } + + return loadNodeModules(resolveFrom, pathComponents); + } + + class Module { + constructor(pathComponents) { + this.dirComponents = pathComponents.slice(0, -1); + this.pathComponents = pathComponents; + this.filename = pathComponents.join('/'); + this.dirname = this.dirComponents.length > 1 ? this.dirComponents.join('/') : '/'; + this.exports = {}; + } + + resolve(path) { + let module = this; + const resolvedPath = resolve(module, path); + if (!resolvedPath) { + throwError(new Error(`Cannot resolve module '${path}' from '${module.filename}'`)); + } + + return resolvedPath; + } + + require(path) { + let module = this; + const resolvedPath = resolve(module, path); + if (!resolvedPath) { + throwError(new Error(`Cannot resolve module '${path}' from '${module.filename}'`)); + } + + // eval file + const pathComponents = resolvedPath.split('/'); + const displayPath = resolvedPath; + const cacheKey = pathComponents.join('/'); + if (cache[cacheKey]) { + return cache[cacheKey].exports; + } + + const currentModule = global.module; + module = new Module(pathComponents); + cache[cacheKey] = module; + global.module = module; + + if (endsWith(resolvedPath, '.node')) { + throwError(new Error(`Native Node.js modules are not supported '${resolvedPath}'`)); + } + + const content = readFileFn(resolvedPath); + if (!content) throwError(new Error(`Cannot load module '${resolvedPath}'`)); + + if (endsWith(resolvedPath, '.json')) { + module.exports = JSON.parse(content); + } else { + /* eslint-disable max-len */ + evalScriptFn( + `((require,exports,module,__filename,__dirname) => {${content}})(((m) => {var f = function(path){return m.require(path)};f.resolve = function(path){return m.resolve(path)};return f})(global.module),global.module.exports,global.module,global.module.filename,global.module.dirname)`, + displayPath); + /* eslint-enable max-len */ + } + + global.module = currentModule; + return module.exports; + } + } + + return { + require(path) { + const rootModule = new Module(['', '']); + global.module = rootModule; + return rootModule.require(path); + }, + setupBuiltins(newBuiltins) { + builtins = newBuiltins; + } + }; + } + // end + + const files = {}; + for (const file of __SYSCALL.initrdListFiles()) { + files[file] = true; + } + + function fileExists(path) { + return !!files[path]; + } + + const kernelImportPath = __SYSCALL.initrdGetKernelIndex(); + const runtimePackagePath = kernelImportPath.split('/').slice(0, -1).join('/'); + const loader = createLoader(fileExists, __SYSCALL.initrdReadFile, __SYSCALL.eval, runtimePackagePath); + __SYSCALL.loaderSetupBuiltins = loader.setupBuiltins; + + loader.require(kernelImportPath); +})(); +)JAVASCRIPT"; diff --git a/src/kernel/logger.h b/src/kernel/logger.h index 8417ae79e..bd101e5f3 100644 --- a/src/kernel/logger.h +++ b/src/kernel/logger.h @@ -129,6 +129,10 @@ class Logger { console_enabled_ = true; } + void DisableConsole() { + console_enabled_ = false; + } + void DisableVideo() { video_enabled_ = false; } diff --git a/src/kernel/native-object.cc b/src/kernel/native-object.cc index 6d856f10b..c30d8ccf9 100644 --- a/src/kernel/native-object.cc +++ b/src/kernel/native-object.cc @@ -193,9 +193,24 @@ NATIVE_FUNCTION(NativesObject, TextDecoder) { args.This()->Set(context, s_encoding, s_utf8); } +NATIVE_FUNCTION(NativesObject, UnrefTimer) { + PROLOGUE_NOTHIS; + USEARG(0); + if (!arg0->IsNumber()) { + return; + } + + args.GetReturnValue().Set(v8::Boolean::New(iv8, + th->SetTimeoutUnref(arg0->Uint32Value(context).FromJust()))); +} + NATIVE_FUNCTION(NativesObject, ClearTimer) { PROLOGUE_NOTHIS; USEARG(0); + if (!arg0->IsNumber()) { + return; + } + args.GetReturnValue().Set(v8::Boolean::New(iv8, th->FlagTimeoutCleared(arg0->Uint32Value(context).FromJust()))); } @@ -207,7 +222,7 @@ uint32_t SetTimer(v8::Isolate* iv8, Thread* th, RT_ASSERT(!cb.IsEmpty()); RT_ASSERT(cb->IsFunction()); uint32_t index = th->AddTimeoutData( - TimeoutData(v8::UniquePersistent(iv8, cb), delay, autoreset)); + TimeoutData(v8::UniquePersistent(iv8, cb), delay, autoreset, true)); RT_ASSERT(th->thread_manager()); th->thread_manager()->SetTimeout(th, index, delay); @@ -348,6 +363,12 @@ NATIVE_FUNCTION(NativesObject, InitrdGetKernelIndex) { GLOBAL_initrd()->runtime_index_name(), v8::NewStringType::kNormal).ToLocalChecked()); } +NATIVE_FUNCTION(NativesObject, InitrdGetAppIndex) { + PROLOGUE_NOTHIS; + args.GetReturnValue().Set(v8::String::NewFromUtf8(iv8, + GLOBAL_initrd()->app_index_name(), v8::NewStringType::kNormal).ToLocalChecked()); +} + NATIVE_FUNCTION(NativesObject, InitrdListFiles) { PROLOGUE_NOTHIS; size_t files_count { GLOBAL_initrd()->files_count() }; @@ -511,6 +532,23 @@ NATIVE_FUNCTION(NativesObject, Reboot) { Cpu::HangSystem(); } +NATIVE_FUNCTION(NativesObject, Poweroff) { + PROLOGUE_NOTHIS; + GLOBAL_engines()->acpi_manager(); // Ensure ACPI is initialized + GLOBAL_boot_services()->logger()->DisableConsole(); // ACPI may print to console + GLOBAL_platform()->EnterSleepState(5); + Cpu::HangSystem(); +} + +NATIVE_FUNCTION(NativesObject, Exit) { + PROLOGUE_NOTHIS; + RT_ASSERT(iv8); + RT_ASSERT(th); + RT_ASSERT(th->thread_manager()); + RT_ASSERT(th->thread_manager()->current_thread()); + th->thread_manager()->current_thread()->SetTerminateFlag(); +} + void PrintMemory(void* buf, size_t offset, size_t size) { uint8_t* p = reinterpret_cast(buf); size_t counter = 0; @@ -1125,4 +1163,14 @@ NATIVE_FUNCTION(NativesObject, AllocDMA) { args.GetReturnValue().Set(ret); } +NATIVE_FUNCTION(NativesObject, SetPromiseHandlers) { + PROLOGUE_NOTHIS; + USEARG(0); + USEARG(1); + VALIDATEARG(0, FUNCTION, "argument 0 is not a function"); + VALIDATEARG(1, FUNCTION, "argument 1 is not a function"); + + th->SetPromiseHandlers(arg0.As(), arg1.As()); +} + } // namespace rt diff --git a/src/kernel/native-object.h b/src/kernel/native-object.h index 3b2308d24..4660fb5ce 100644 --- a/src/kernel/native-object.h +++ b/src/kernel/native-object.h @@ -48,12 +48,14 @@ class NativesObject : public JsObjectWrapper TemplateCache::NewContext() { SET_SYSCALL("initrdReadFileBuffer", NativesObject::InitrdReadFileBuffer); SET_SYSCALL("initrdListFiles", NativesObject::InitrdListFiles); SET_SYSCALL("initrdGetKernelIndex", NativesObject::InitrdGetKernelIndex); + SET_SYSCALL("initrdGetAppIndex", NativesObject::InitrdGetAppIndex); // Profiler, debug and system info SET_SYSCALL("startProfiling", NativesObject::StartProfiling); @@ -108,6 +109,9 @@ v8::Local TemplateCache::NewContext() { SET_SYSCALL("memoryInfo", NativesObject::MemoryInfo); SET_SYSCALL("systemInfo", NativesObject::SystemInfo); SET_SYSCALL("reboot", NativesObject::Reboot); + SET_SYSCALL("poweroff", NativesObject::Poweroff); + SET_SYSCALL("exit", NativesObject::Exit); + SET_SYSCALL("unrefTimer", NativesObject::UnrefTimer); // Low level system access SET_SYSCALL("bufferAddress", NativesObject::BufferAddress); @@ -117,12 +121,17 @@ v8::Local TemplateCache::NewContext() { SET_SYSCALL("stopVideoLog", NativesObject::StopVideoLog); SET_SYSCALL("setTime", NativesObject::SetTime); + // Temporary for init.js script + SET_SYSCALL("_setPromiseHandlers", NativesObject::SetPromiseHandlers); + // ACPI control bindings SET_SYSCALL("acpiGetPciDevices", NativesObject::AcpiGetPciDevices); SET_SYSCALL("acpiSystemReset", NativesObject::AcpiSystemReset); SET_SYSCALL("acpiEnterSleepState", NativesObject::AcpiEnterSleepState); - #undef SET_SYSCALL + syscall->Set(iv8_, "onexit", v8::Null(iv8_)); + syscall->Set(iv8_, "onerror", v8::Null(iv8_)); + global->Set(iv8_, "__SYSCALL", syscall); // Global performance object @@ -147,7 +156,6 @@ v8::Local TemplateCache::NewContext() { RT_ASSERT(!init_script_.IsEmpty()); init_script_.Get(iv8_)->BindToCurrentContext()->Run(context).ToLocalChecked(); - return scope.Escape(context); } diff --git a/src/kernel/thread.cc b/src/kernel/thread.cc index 3654a2eb0..1e5ca6fa5 100644 --- a/src/kernel/thread.cc +++ b/src/kernel/thread.cc @@ -22,6 +22,8 @@ namespace rt { +void PromiseRejectCallback(v8::PromiseRejectMessage message); + void* MallocArrayBufferAllocator::Allocate(size_t length) { return GLOBAL_engines()->AllocateBuffer(length); } @@ -106,6 +108,9 @@ void Thread::SetUp() { v8::Isolate::Scope ivscope(iv8_); v8::HandleScope local_handle_scope(iv8_); tpl_cache_ = new TemplateCache(iv8_); + + iv8_->SetPromiseRejectCallback(PromiseRejectCallback); + iv8_->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); } void Thread::TearDown() { @@ -114,6 +119,7 @@ void Thread::TearDown() { RT_ASSERT(iv8_->GetData(0) == this); RT_ASSERT(tpl_cache_); + GLOBAL_platform()->Reboot(); RT_ASSERT(!"main isolate exited"); RT_ASSERT(thread_mgr_); @@ -126,6 +132,7 @@ void Thread::TearDown() { context_.Reset(); args_.Reset(); exit_value_.Reset(); + syscall_object_.Reset(); call_wrapper_.Reset(); RT_ASSERT(tpl_cache_); @@ -150,195 +157,191 @@ v8::Local Thread::GetIsolateGlobal() { return scope.Escape(isolate_value.As()); } -bool Thread::Run() { - RuntimeStateScope event_loop_state(thread_manager()); - - // Not possible to run terminated thread - RT_ASSERT(ThreadType::TERMINATED != type_); - - // Idle thread does nothing and never terminates - if (ThreadType::IDLE == type_) { +void PromiseRejectCallback(v8::PromiseRejectMessage message) { + v8::Local promise = message.GetPromise(); + RT_ASSERT(!promise.IsEmpty()); + v8::Isolate* iv8 = promise->GetIsolate(); + RT_ASSERT(iv8); - if (thread_mgr_->CanHalt()) { - RuntimeStateScope halt_state(thread_manager()); - Cpu::Halt(); - } + Thread* th = V8Utils::GetThreadFromIsolate(iv8); + RT_ASSERT(th); - thread_mgr_->SetEvCheckpoint(); - return true; + v8::Local value = message.GetValue(); + if (value.IsEmpty()) { + value = v8::Undefined(iv8).As(); } - RT_ASSERT(iv8_); - RT_ASSERT(tpl_cache_); - - - uint64_t time_now { GLOBAL_platform()->BootTimeMicroseconds() }; - - std::vector messages = ethread_.getUnsafe()->TakeMessages(); - if (0 == messages.size()) { - return true; + if (message.GetEvent() == v8::kPromiseRejectWithNoHandler) { + th->CallPromiseRejected(promise, value); + return; } - v8::Isolate::Scope ivscope(iv8_); - v8::HandleScope local_handle_scope(iv8_); - - if (context_.IsEmpty()) { - v8::Local context = tpl_cache_->NewContext(); - context_ = std::move(v8::UniquePersistent(iv8_, context)); + if (message.GetEvent() == v8::kPromiseHandlerAddedAfterReject) { + th->CallPromiseRejectedHandled(promise); + return; } - RT_ASSERT(!context_.IsEmpty()); - v8::Local context = v8::Local::New(iv8_, context_); - v8::Context::Scope cs(context); - - v8::TryCatch trycatch(iv8_); - uint64_t ev_count = 0; + RT_ASSERT(!"unknown promise reject callback"); +} - for (ThreadMessage* message : messages) { - if (nullptr == message) { - continue; // Skip removed message +void Thread::RunEvent(ThreadMessage* message) { + RT_ASSERT(message); + v8::Isolate* iv8 = iv8_; + v8::TryCatch trycatch(iv8); + ThreadMessage::Type type = message->type(); + v8::Local context = v8::Local::New(iv8, context_); + + switch (type) { + case ThreadMessage::Type::EVALUATE: { + v8::Local unpacked { message->data().Unpack(this) }; + RT_ASSERT(!unpacked.IsEmpty()); + RT_ASSERT(unpacked->IsArray()); + v8::Local arr { v8::Local::Cast(unpacked) }; + v8::Local code { arr->Get(context, 0).ToLocalChecked() }; + v8::Local filename { arr->Get(context, 1).ToLocalChecked() }; + RT_ASSERT(code->IsString()); + RT_ASSERT(filename->IsString()); + if (filename_.empty()) { + filename_ = V8Utils::ToString(filename.As()); } - ++ev_count; - ThreadMessage::Type type = message->type(); - - switch (type) { - case ThreadMessage::Type::EVALUATE: { - v8::Local unpacked { message->data().Unpack(this) }; - RT_ASSERT(!unpacked.IsEmpty()); - RT_ASSERT(unpacked->IsArray()); - v8::Local arr { v8::Local::Cast(unpacked) }; - v8::Local code { arr->Get(context, 0).ToLocalChecked() }; - v8::Local filename { arr->Get(context, 1).ToLocalChecked() }; - RT_ASSERT(code->IsString()); - RT_ASSERT(filename->IsString()); - if (filename_.empty()) { - filename_ = V8Utils::ToString(filename.As()); - } - - v8::ScriptOrigin origin(filename); - v8::ScriptCompiler::Source source(code.As(), origin); - v8::MaybeLocal maybe_script = v8::ScriptCompiler::Compile(context, &source, - v8::ScriptCompiler::CompileOptions::kNoCompileOptions); - v8::Local script; - if (maybe_script.ToLocal(&script)) { - RuntimeStateScope js_state(thread_manager()); - script->Run(context); - } + v8::ScriptOrigin origin(filename); + v8::ScriptCompiler::Source source(code.As(), origin); + v8::MaybeLocal maybe_script = v8::ScriptCompiler::Compile(context, &source, + v8::ScriptCompiler::CompileOptions::kNoCompileOptions); + v8::Local script; + if (maybe_script.ToLocal(&script)) { + RuntimeStateScope js_state(thread_manager()); + script->Run(context); } - break; - case ThreadMessage::Type::FUNCTION_CALL: { - v8::Local unpacked { message->data().Unpack(this) }; - RT_ASSERT(!unpacked.IsEmpty()); - - ExternalFunction* efn = static_cast(message->ptr()); - RT_ASSERT(efn); + } + break; + case ThreadMessage::Type::FUNCTION_CALL: { + v8::Local unpacked { message->data().Unpack(this) }; + RT_ASSERT(!unpacked.IsEmpty()); - v8::Local fnval { exports_.Get(efn->index(), efn->export_id()) }; - if (fnval.IsEmpty()) { - fnval = v8::Null(iv8_); - } else { - RT_ASSERT(fnval->IsFunction()); - } + ExternalFunction* efn = static_cast(message->ptr()); + RT_ASSERT(efn); - { - RT_ASSERT(!call_wrapper_.IsEmpty()); - v8::Local fnwrap { v8::Local::New(iv8_, call_wrapper_) }; - v8::Local argv[] { - fnval, - message->sender().NewExternal(iv8_), - unpacked, - v8::Uint32::NewFromUnsigned(iv8_, message->recv_index()), - }; - RuntimeStateScope js_state(thread_manager()); - fnwrap->Call(context, context->Global(), 4, argv); - } + v8::Local fnval { exports_.Get(efn->index(), efn->export_id()) }; + if (fnval.IsEmpty()) { + fnval = v8::Null(iv8_); + } else { + RT_ASSERT(fnval->IsFunction()); } - break; - case ThreadMessage::Type::FUNCTION_RETURN_RESOLVE: { - v8::Local unpacked { message->data().Unpack(this) }; - RT_ASSERT(!unpacked.IsEmpty()); - v8::Local resolver { - v8::Local::New(iv8_, TakePromise(message->recv_index())) + { + RT_ASSERT(!call_wrapper_.IsEmpty()); + v8::Local fnwrap { v8::Local::New(iv8_, call_wrapper_) }; + v8::Local argv[] { + fnval, + message->sender().NewExternal(iv8_), + unpacked, + v8::Uint32::NewFromUnsigned(iv8_, message->recv_index()), }; - - resolver->Resolve(context, unpacked); RuntimeStateScope js_state(thread_manager()); - iv8_->RunMicrotasks(); + fnwrap->Call(context, context->Global(), 4, argv); } - break; - case ThreadMessage::Type::FUNCTION_RETURN_REJECT: { - v8::Local unpacked { message->data().Unpack(this) }; - RT_ASSERT(!unpacked.IsEmpty()); + } + break; + case ThreadMessage::Type::FUNCTION_RETURN_RESOLVE: { + v8::Local unpacked { message->data().Unpack(this) }; + RT_ASSERT(!unpacked.IsEmpty()); - v8::Local resolver { - v8::Local::New(iv8_, TakePromise(message->recv_index())) - }; + v8::Local resolver { + v8::Local::New(iv8_, TakePromise(message->recv_index())) + }; - resolver->Reject(context, unpacked); - RuntimeStateScope js_state(thread_manager()); - iv8_->RunMicrotasks(); - } - break; - case ThreadMessage::Type::TIMEOUT_EVENT: { - auto recv_index = message->recv_index(); - RT_ASSERT(recv_index < std::numeric_limits::max()); - uint32_t timeout_id { static_cast(recv_index) }; - TimeoutData data(TakeTimeoutData(timeout_id)); - - if (data.cleared()) { - break; - } + resolver->Resolve(context, unpacked); + RuntimeStateScope js_state(thread_manager()); + } + break; + case ThreadMessage::Type::FUNCTION_RETURN_REJECT: { + v8::Local unpacked { message->data().Unpack(this) }; + RT_ASSERT(!unpacked.IsEmpty()); + v8::Local resolver { + v8::Local::New(iv8_, TakePromise(message->recv_index())) + }; + + resolver->Reject(context, unpacked); + RuntimeStateScope js_state(thread_manager()); + } + break; + case ThreadMessage::Type::TIMEOUT_EVENT: { + auto recv_index = message->recv_index(); + RT_ASSERT(recv_index < std::numeric_limits::max()); + uint32_t timeout_id { static_cast(recv_index) }; + TimeoutData& data = GetTimeoutData(timeout_id); + + if (!data.cleared()) { auto fnv = data.GetCallback(iv8_); RT_ASSERT(!fnv.IsEmpty()); RT_ASSERT(fnv->IsFunction()); auto fn = fnv.As(); - if (data.autoreset()) { - // TODO: don't take timeout data in a first place - thread_mgr_->SetTimeout(this, AddTimeoutData(std::move(data)), data.delay()); + if (!data.autoreset()) { + TakeTimeoutData(timeout_id); + if (data.ref()) { + Unref(); + } + } else { + thread_mgr_->SetTimeout(this, timeout_id, data.delay()); } RuntimeStateScope js_state(thread_manager()); fn->Call(context, context->Global(), 0, nullptr); + } else { + // No unref here, it's been done before + TakeTimeoutData(timeout_id); } + } + break; + case ThreadMessage::Type::IRQ_RAISE: { + v8::Local fnv { v8::Local::New(iv8_, GetObject(message->recv_index())) }; + RT_ASSERT(fnv->IsFunction()); + v8::Local fn { v8::Local::Cast(fnv) }; + RuntimeStateScope js_state(thread_manager()); + fn->Call(context, context->Global(), 0, nullptr); + } + break; + case ThreadMessage::Type::SET_IMMEDIATE: { + v8::Local fnv { v8::Local::New(iv8_, TakeObject(message->recv_index())) }; + RT_ASSERT(fnv->IsFunction()); + v8::Local fn { v8::Local::Cast(fnv) }; + RuntimeStateScope js_state(thread_manager()); + fn->Call(context, context->Global(), 0, nullptr); + } + break; + case ThreadMessage::Type::EMPTY: break; - case ThreadMessage::Type::IRQ_RAISE: { - v8::Local fnv { v8::Local::New(iv8_, - GetObject(message->recv_index())) - }; - RT_ASSERT(fnv->IsFunction()); - v8::Local fn { v8::Local::Cast(fnv) }; - RuntimeStateScope js_state(thread_manager()); - fn->Call(context, context->Global(), 0, nullptr); - } - break; - case ThreadMessage::Type::SET_IMMEDIATE: { - v8::Local fnv { v8::Local::New(iv8_, - TakeObject(message->recv_index())) - }; - RT_ASSERT(fnv->IsFunction()); - v8::Local fn { v8::Local::Cast(fnv) }; - RuntimeStateScope js_state(thread_manager()); - fn->Call(context, context->Global(), 0, nullptr); - } + default: + RT_ASSERT(!"Unknown thread message"); break; - case ThreadMessage::Type::EMPTY: - break; - default: - RT_ASSERT(!"Unknown thread message"); - break; - } - - if (!message->reusable()) { - delete message; - } } + HandleException(trycatch, true); +} + +void Thread::HandleException(v8::TryCatch& trycatch, bool allow_onerror) { + v8::Isolate* iv8 = iv8_; v8::Local ex = trycatch.Exception(); - if (!ex.IsEmpty() && !trycatch.HasTerminated()) { + if (ex.IsEmpty() || trycatch.HasTerminated()) { + return; + } + + LOCAL_V8STRING(s_onexit, "onerror"); + v8::Local context = v8::Local::New(iv8, context_); + v8::Local syscall_object = v8::Local::New(iv8_, syscall_object_); + v8::Local onerror = syscall_object->Get(s_onexit); + RT_ASSERT(!onerror.IsEmpty()); + + if (allow_onerror && onerror->IsFunction()) { + v8::Local onerror_handler { v8::Local::Cast(onerror) }; + v8::TryCatch handler_trycatch(iv8_); + onerror_handler->Call(context, context->Global(), 1, &ex); + HandleException(handler_trycatch, false); + } else { v8::String::Utf8Value exception_str(ex); v8::Local message = trycatch.Message(); if (message.IsEmpty()) { @@ -357,9 +360,79 @@ bool Thread::Run() { printf("%s\n", *stack); } } + + terminate_ = true; } +} - trycatch.Reset(); +bool Thread::Run() { + RuntimeStateScope event_loop_state(thread_manager()); + + // Not possible to run terminated thread + RT_ASSERT(ThreadType::TERMINATED != type_); + + // Idle thread does nothing and never terminates + if (ThreadType::IDLE == type_) { + + if (thread_mgr_->CanHalt()) { + RuntimeStateScope halt_state(thread_manager()); + Cpu::Halt(); + } + + thread_mgr_->SetEvCheckpoint(); + return true; + } + + RT_ASSERT(iv8_); + RT_ASSERT(tpl_cache_); + + uint64_t time_now { GLOBAL_platform()->BootTimeMicroseconds() }; + + std::vector messages = ethread_.getUnsafe()->TakeMessages(); + if (0 == messages.size()) { + return true; + } + + v8::Isolate::Scope ivscope(iv8_); + v8::HandleScope local_handle_scope(iv8_); + + if (context_.IsEmpty()) { + v8::Local context = tpl_cache_->NewContext(); + context_ = std::move(v8::UniquePersistent(iv8_, context)); + + v8::Context::Scope cs(context); + v8::Isolate* iv8 = iv8_; + LOCAL_V8STRING(s_syscall, "__SYSCALL"); + v8::Local syscall = context->Global()->Get(s_syscall); + RT_ASSERT(!syscall.IsEmpty()); + RT_ASSERT(syscall->IsObject()); + syscall_object_ = std::move(v8::UniquePersistent(iv8_, syscall.As())); + } + + RT_ASSERT(!context_.IsEmpty()); + v8::Local context = v8::Local::New(iv8_, context_); + v8::Context::Scope cs(context); + + uint64_t ev_count = 0; + + for (ThreadMessage* message : messages) { + if (nullptr == message) { + continue; // Skip removed message + } + + ++ev_count; + if (!terminate_) { + RunEvent(message); + } + + if (!message->reusable()) { + delete message; + } + } + + v8::TryCatch trycatch(iv8_); + iv8_->RunMicrotasks(); + HandleException(trycatch, true); if (ev_count > 0) { thread_mgr_->SubmitEvWork(ev_count); @@ -378,10 +451,22 @@ bool Thread::Run() { } if (0 == ref_count_ || terminate_) { + v8::Isolate* iv8 = iv8_; + LOCAL_V8STRING(s_onexit, "onexit"); + v8::Local syscall_object = v8::Local::New(iv8_, syscall_object_); + v8::Local onexit = syscall_object->Get(s_onexit); + RT_ASSERT(!onexit.IsEmpty()); + if (onexit->IsFunction()) { + v8::Local onexit_handler { v8::Local::Cast(onexit) }; + v8::TryCatch handler_trycatch(iv8_); + onexit_handler->Call(context, context->Global(), 0, nullptr); + HandleException(handler_trycatch, true); + } + if (terminate_) { - printf("[ terminate thread (reason: isolate.exit() called) ]\n"); + printf("^^^ restarting runtime.js (exit)\n\n"); } else { - printf("[ terminate thread (reason: refcount 0) ]\n"); + printf("^^^ restarting runtime.js (nothing else to do)\n\n"); } terminate_ = true; diff --git a/src/kernel/thread.h b/src/kernel/thread.h index 596c418b6..97800b4e5 100644 --- a/src/kernel/thread.h +++ b/src/kernel/thread.h @@ -34,6 +34,7 @@ namespace rt { class ThreadManager; class Interface; class EngineThread; +class ThreadMessage; class MallocArrayBufferAllocator : public v8::ArrayBuffer::Allocator { public: @@ -95,17 +96,17 @@ enum class ThreadType { */ class TimeoutData { public: - TimeoutData(v8::UniquePersistent cb, uint32_t delay_ms, bool is_autoreset) + TimeoutData(v8::UniquePersistent cb, uint32_t delay_ms, bool is_autoreset, bool ref) : cb_(std::move(cb)), delay_(delay_ms), - cleared_(false), has_args_(false), autoreset_(is_autoreset) { + cleared_(false), has_args_(false), autoreset_(is_autoreset), ref_(ref) { RT_ASSERT(!cb_.IsEmpty()); RT_ASSERT(args_array_.IsEmpty()); } TimeoutData(v8::UniquePersistent cb, v8::UniquePersistent args_array, - uint32_t delay_ms, bool is_autoreset) + uint32_t delay_ms, bool is_autoreset, bool ref) : cb_(std::move(cb)), args_array_(std::move(args_array)), delay_(delay_ms), - cleared_(false), has_args_(true), autoreset_(is_autoreset) { + cleared_(false), has_args_(true), autoreset_(is_autoreset), ref_(ref) { RT_ASSERT(!cb_.IsEmpty()); RT_ASSERT(!args_array_.IsEmpty()); } @@ -114,7 +115,7 @@ class TimeoutData { : cb_(std::move(other.cb_)), args_array_(std::move(other.args_array_)), delay_(other.delay_), cleared_(other.cleared_), has_args_(other.has_args_), - autoreset_(other.autoreset_) {} + autoreset_(other.autoreset_), ref_(other.ref_) {} TimeoutData& operator=(TimeoutData&& other) { cb_ = std::move(other.cb_); @@ -123,6 +124,7 @@ class TimeoutData { cleared_ = other.cleared_; has_args_ = other.has_args_; autoreset_ = other.autoreset_; + ref_ = other.ref_; return *this; } @@ -147,6 +149,13 @@ class TimeoutData { void SetAutoreset() { autoreset_ = true; } + void SetUnref() { + ref_ = false; + } + void SetRef() { + ref_ = true; + } + uint32_t delay() const { return delay_; } @@ -159,6 +168,9 @@ class TimeoutData { bool cleared() const { return cleared_; } + bool ref() const { + return ref_; + } private: v8::UniquePersistent cb_; v8::UniquePersistent args_array_; @@ -166,6 +178,7 @@ class TimeoutData { bool cleared_; bool has_args_; bool autoreset_; + bool ref_; DELETE_COPY_AND_ASSIGN(TimeoutData); }; @@ -202,6 +215,16 @@ class Thread { */ bool Run(); + /** + * Run single thread message event + */ + void RunEvent(ThreadMessage* message); + + /** + * Handle uncaught exception + */ + void HandleException(v8::TryCatch& trycatch, bool allow_onerror); + /** * Get global isolate object */ @@ -257,14 +280,34 @@ class Thread { v8::Local TakeObject(uint32_t index) { v8::EscapableHandleScope scope(iv8_); + Unref(); return scope.Escape(v8::Local::New(iv8_, object_handles_.Take(index))); } uint32_t AddTimeoutData(TimeoutData data) { - Ref(); + if (data.ref()) { + Ref(); + } return timeout_data_.Push(std::move(data)); } + bool SetTimeoutUnref(uint32_t index) { + if (index >= timeout_data_.size()) { + return false; + } + + auto& data = timeout_data_.Get(index); + if (data.IsEmpty()) { + return false; + } + + if (data.ref()) { + data.SetUnref(); + Unref(); + } + return true; + } + bool FlagTimeoutCleared(uint32_t index) { if (index >= timeout_data_.size()) { return false; @@ -275,15 +318,21 @@ class Thread { return false; } + if (data.ref()) { + Unref(); + } data.SetCleared(); return true; } TimeoutData TakeTimeoutData(uint32_t index) { - Unref(); return timeout_data_.Take(index); } + TimeoutData& GetTimeoutData(uint32_t index) { + return timeout_data_.Get(index); + } + uint32_t AddPromise(v8::UniquePersistent resolver) { Ref(); return promises_.Push(std::move(resolver)); @@ -351,6 +400,31 @@ class Thread { return type_; } + void SetPromiseHandlers(v8::Local on_rejected, v8::Local on_rejected_handled) { + onpromise_rejected_ = std::move(v8::UniquePersistent(iv8_, on_rejected)); + onpromise_rejected_handled_ = std::move(v8::UniquePersistent(iv8_, on_rejected_handled)); + } + + void CallPromiseRejected(v8::Local promise, v8::Local value) { + v8::Isolate* iv8 = iv8_; + RT_ASSERT(!onpromise_rejected_.IsEmpty()); + v8::Local context = context_.Get(iv8); + v8::Local fn = onpromise_rejected_.Get(iv8); + RT_ASSERT(fn->IsFunction()); + v8::Local argv[] = { promise, value }; + fn->Call(context, context->Global(), 2, argv); + } + + void CallPromiseRejectedHandled(v8::Local promise) { + v8::Isolate* iv8 = iv8_; + RT_ASSERT(!onpromise_rejected_handled_.IsEmpty()); + v8::Local context = context_.Get(iv8); + v8::Local fn = onpromise_rejected_handled_.Get(iv8); + RT_ASSERT(fn->IsFunction()); + v8::Local argv[] = { promise }; + fn->Call(context, context->Global(), 1, argv); + } + /** * Get thread info like events processed count and total runtime */ @@ -378,6 +452,7 @@ class Thread { v8::UniquePersistent args_; v8::UniquePersistent exit_value_; v8::UniquePersistent call_wrapper_; + v8::UniquePersistent syscall_object_; VirtualStack stack_; std::atomic priority_; @@ -395,6 +470,9 @@ class Thread { IndexedPool timeout_data_; UniquePersistentIndexedPool object_handles_; UniquePersistentIndexedPool promises_; + + v8::Global onpromise_rejected_; + v8::Global onpromise_rejected_handled_; }; } // namespace rt diff --git a/src/kernel/v8utils.h b/src/kernel/v8utils.h index d46d83fbb..b118ab1dc 100644 --- a/src/kernel/v8utils.h +++ b/src/kernel/v8utils.h @@ -115,6 +115,12 @@ class V8Utils { inline static Thread* GetThread(const v8::FunctionCallbackInfo& args) { return reinterpret_cast(args.GetIsolate()->GetData(0)); } + + inline static Thread* GetThreadFromIsolate(v8::Isolate* iv8) { + RT_ASSERT(iv8); + return reinterpret_cast(iv8->GetData(0)); + } + inline static bool ValidateArg(const v8::FunctionCallbackInfo& args, int argnum, ArgType type) { @@ -284,6 +290,10 @@ class UniquePersistentIndexedPool { void Clear() { data_.clear(); } + + uint32_t Size() const { + return data_.size(); + } private: std::vector> data_; };