From 0bb099f444c6256e2af51fce158b85bef41e354c Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 5 Aug 2015 14:44:21 +0200 Subject: [PATCH 01/11] build: expand ~ in install prefix early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install prefix gets written to config.gypi and config.mk. Tildes were expanded in the first file but not in the second one, causing the `make install` target to install files to a directory named `~` in the current working directory. Fixes: https://github.com/nodejs/node/issues/75 PR-URL: https://github.com/nodejs/io.js/pull/2307 Reviewed-By: Johan Bergström Reviewed-By: Sakthipriyan Vairamani --- configure | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/configure b/configure index ab2383c8141b15..f01aea6f927f69 100755 --- a/configure +++ b/configure @@ -335,6 +335,9 @@ parser.add_option('--enable-static', (options, args) = parser.parse_args() +# Expand ~ in the install prefix now, it gets written to multiple files. +options.prefix = os.path.expanduser(options.prefix or '') + # set up auto-download list auto_downloads = nodedownload.parse(options.download_list) @@ -611,7 +614,7 @@ def configure_mips(o): def configure_node(o): if options.dest_os == 'android': o['variables']['OS'] = 'android' - o['variables']['node_prefix'] = os.path.expanduser(options.prefix or '') + o['variables']['node_prefix'] = options.prefix o['variables']['node_install_npm'] = b(not options.without_npm) o['default_configuration'] = 'Debug' if options.debug else 'Release' From 2f154b52dad62a4c6b0b1e3bad906706a9a45a3c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 23 Jun 2015 20:42:49 -0700 Subject: [PATCH 02/11] doc: multiple documentation updates cherry picked from v0.12 * doc: improve http.abort description * doc: mention that mode is ignored if file exists * docs: Fix default options for fs.createWriteStream() * Documentation update about Buffer initialization * doc: add a note about readable in flowing mode * doc: Document http.request protocol option * doc, comments: Grammar and spelling fixes * updated documentation for fs.createReadStream * Update child_process.markdown, spelling * doc: Clarified read method with specified size argument. * docs:events clarify emitter.listener() behavior * doc: two minor stream doc improvements * doc: clarify Readable._read and Readable.push * doc: stream.unshift does not reset reading state * doc: readable event clarification * doc: additional refinement to readable event Reviewed-By: James M Snell Reviewed-By: Ben Noorduis PR-URL: https://github.com/nodejs/io.js/pull/2302 --- doc/api/buffer.markdown | 6 ++- doc/api/child_process.markdown | 2 +- doc/api/cluster.markdown | 4 +- doc/api/dns.markdown | 4 +- doc/api/events.markdown | 2 +- doc/api/fs.markdown | 11 +++- doc/api/http.markdown | 4 +- doc/api/stream.markdown | 96 ++++++++++++++++++++++++---------- lib/_http_client.js | 4 +- lib/url.js | 4 +- src/node.cc | 2 +- src/node_object_wrap.h | 2 +- 12 files changed, 98 insertions(+), 43 deletions(-) diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 92b7f2ba93b288..94f3e3d8e164f5 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -43,7 +43,7 @@ Creating a typed array from a `Buffer` works with the following caveats: 2. The buffer's memory is interpreted as an array, not a byte array. That is, `new Uint32Array(new Buffer([1,2,3,4]))` creates a 4-element `Uint32Array` - with elements `[1,2,3,4]`, not an `Uint32Array` with a single element + with elements `[1,2,3,4]`, not a `Uint32Array` with a single element `[0x1020304]` or `[0x4030201]`. NOTE: Node.js v0.8 simply retained a reference to the buffer in `array.buffer` @@ -67,6 +67,10 @@ Allocates a new buffer of `size` bytes. `size` must be less than 2,147,483,648 bytes (2 GB) on 64 bits architectures, otherwise a `RangeError` is thrown. +Unlike `ArrayBuffers`, the underlying memory for buffers is not initialized. So +the contents of a newly created `Buffer` is unknown. Use `buf.fill(0)`to +initialize a buffer to zeroes. + ### new Buffer(array) * `array` Array diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index 3d6e5f5fe2f048..4acd61bfa6a97c 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -279,7 +279,7 @@ Here is an example of sending a server: child.send('server', server); }); -And the child would the receive the server object as: +And the child would then receive the server object as: process.on('message', function(m, server) { if (m === 'server') { diff --git a/doc/api/cluster.markdown b/doc/api/cluster.markdown index b7e76fcb8ffa0e..abf05bf9f40c82 100644 --- a/doc/api/cluster.markdown +++ b/doc/api/cluster.markdown @@ -121,7 +121,7 @@ values are `"rr"` and `"none"`. ## cluster.settings * {Object} - * `execArgv` {Array} list of string arguments passed to the io.js executable. + * `execArgv` {Array} list of string arguments passed to the io.js executable. (Default=`process.execArgv`) * `exec` {String} file path to worker file. (Default=`process.argv[1]`) * `args` {Array} string arguments passed to worker. @@ -613,7 +613,7 @@ It is not emitted in the worker. ### Event: 'disconnect' -Similar to the `cluster.on('disconnect')` event, but specfic to this worker. +Similar to the `cluster.on('disconnect')` event, but specific to this worker. cluster.fork().on('disconnect', function() { // Worker has disconnected diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index d8ed53e3fa0f79..7c9f419ce08ae7 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -85,7 +85,7 @@ All properties are optional. An example usage of options is shown below. ``` The callback has arguments `(err, address, family)`. `address` is a string -representation of a IP v4 or v6 address. `family` is either the integer 4 or 6 +representation of an IP v4 or v6 address. `family` is either the integer 4 or 6 and denotes the family of `address` (not necessarily the value initially passed to `lookup`). @@ -163,7 +163,7 @@ attribute (e.g. `[{'priority': 10, 'exchange': 'mx.example.com'},...]`). ## dns.resolveTxt(hostname, callback) The same as `dns.resolve()`, but only for text queries (`TXT` records). -`addresses` is an 2-d array of the text records available for `hostname` (e.g., +`addresses` is a 2-d array of the text records available for `hostname` (e.g., `[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of one record. Depending on the use case, the could be either joined together or treated separately. diff --git a/doc/api/events.markdown b/doc/api/events.markdown index fbc04a96239b0f..a51905ab4da110 100644 --- a/doc/api/events.markdown +++ b/doc/api/events.markdown @@ -122,7 +122,7 @@ Note that `emitter.setMaxListeners(n)` still has precedence over ### emitter.listeners(event) -Returns an array of listeners for the specified event. +Returns a copy of the array of listeners for the specified event. server.on('connection', function (stream) { console.log('someone connected!'); diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown index 5f96b10763fe45..ebc2046d76ac1e 100644 --- a/doc/api/fs.markdown +++ b/doc/api/fs.markdown @@ -801,6 +801,10 @@ on Unix systems, it never was. Returns a new ReadStream object (See `Readable Stream`). +Be aware that, unlike the default value set for `highWaterMark` on a +readable stream (16 kb), the stream returned by this method has a +default value of 64 kb for the same parameter. + `options` is an object or string with the following defaults: { flags: 'r', @@ -823,6 +827,9 @@ there's no file descriptor leak. If `autoClose` is set to true (default behavior), on `error` or `end` the file descriptor will be closed automatically. +`mode` sets the file mode (permission and sticky bits), but only if the +file was created. + An example to read the last 10 bytes of a file which is 100 bytes long: fs.createReadStream('sample.txt', {start: 90, end: 99}); @@ -847,14 +854,14 @@ Returns a new WriteStream object (See `Writable Stream`). `options` is an object or string with the following defaults: { flags: 'w', - encoding: null, + defaultEncoding: 'utf8', fd: null, mode: 0o666 } `options` may also include a `start` option to allow writing data at some position past the beginning of the file. Modifying a file rather than replacing it may require a `flags` mode of `r+` rather than the -default mode `w`. The `encoding` can be `'utf8'`, `'ascii'`, `binary`, +default mode `w`. The `defaultEncoding` can be `'utf8'`, `'ascii'`, `binary`, or `'base64'`. Like `ReadStream` above, if `fd` is specified, `WriteStream` will ignore the diff --git a/doc/api/http.markdown b/doc/api/http.markdown index 5cf3b07a9cd778..966b304b56d282 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -462,6 +462,7 @@ automatically parsed with [url.parse()][]. Options: +- `protocol`: Protocol to use. Defaults to `'http'`. - `host`: A domain name or IP address of the server to issue the request to. Defaults to `'localhost'`. - `hostname`: Alias for `host`. To support `url.parse()` `hostname` is @@ -911,7 +912,8 @@ is finished. ### request.abort() -Aborts a request. (New since v0.3.8.) +Marks the request as aborting. Calling this will cause remaining data +in the response to be dropped and the socket to be destroyed. ### request.setTimeout(timeout[, callback]) diff --git a/doc/api/stream.markdown b/doc/api/stream.markdown index a7a78f229edb57..ffad1717f75096 100644 --- a/doc/api/stream.markdown +++ b/doc/api/stream.markdown @@ -164,6 +164,34 @@ readable.on('readable', function() { Once the internal buffer is drained, a `readable` event will fire again when more data is available. +The `readable` event is not emitted in the "flowing" mode with the +sole exception of the last one, on end-of-stream. + +The 'readable' event indicates that the stream has new information: +either new data is available or the end of the stream has been reached. +In the former case, `.read()` will return that data. In the latter case, +`.read()` will return null. For instance, in the following example, `foo.txt` +is an empty file: + +```javascript +var fs = require('fs'); +var rr = fs.createReadStream('foo.txt'); +rr.on('readable', function() { + console.log('readable:', rr.read()); +}); +rr.on('end', function() { + console.log('end'); +}); +``` + +The output of running this script is: + +``` +bash-3.2$ node test.js +readable: null +end +``` + #### Event: 'data' * `chunk` {Buffer | String} The chunk of data. @@ -221,7 +249,9 @@ returns it. If there is no data available, then it will return `null`. If you pass in a `size` argument, then it will return that many -bytes. If `size` bytes are not available, then it will return `null`. +bytes. If `size` bytes are not available, then it will return `null`, +unless we've ended, in which case it will return the data remaining +in the buffer. If you do not specify a `size` argument, then it will return all the data in the internal buffer. @@ -243,6 +273,9 @@ readable.on('readable', function() { If this method returns a data chunk, then it will also trigger the emission of a [`'data'` event][]. +Note that calling `readable.read([size])` after the `end` event has been +triggered will return `null`. No runtime error will be raised. + #### readable.setEncoding(encoding) * `encoding` {String} The encoding to use. @@ -414,6 +447,9 @@ parser, which needs to "un-consume" some data that it has optimistically pulled out of the source, so that the stream can be passed on to some other party. +Note that `stream.unshift(chunk)` cannot be called after the `end` event +has been triggered; a runtime error will be raised. + If you find that you must often call `stream.unshift(chunk)` in your programs, consider implementing a [Transform][] stream instead. (See API for Stream Implementors, below.) @@ -452,6 +488,13 @@ function parseHeader(stream, callback) { } } ``` +Note that, unlike `stream.push(chunk)`, `stream.unshift(chunk)` will not +end the reading process by resetting the internal reading state of the +stream. This can cause unexpected results if `unshift` is called during a +read (i.e. from within a `_read` implementation on a custom stream). Following +the call to `unshift` with an immediate `stream.push('')` will reset the +reading state appropriately, however it is best to simply avoid calling +`unshift` while in the process of performing a read. #### readable.wrap(stream) @@ -883,6 +926,10 @@ SimpleProtocol.prototype._read = function(n) { // back into the read queue so that our consumer will see it. var b = chunk.slice(split); this.unshift(b); + // calling unshift by itself does not reset the reading state + // of the stream; since we're inside _read, doing an additional + // push('') will reset the state appropriately. + this.push(''); // and let them know that we are done parsing the header. this.emit('header', this.header); @@ -922,24 +969,22 @@ initialized. * `size` {Number} Number of bytes to read asynchronously -Note: **Implement this function, but do NOT call it directly.** +Note: **Implement this method, but do NOT call it directly.** -This function should NOT be called directly. It should be implemented -by child classes, and only called by the internal Readable class -methods. +This method is prefixed with an underscore because it is internal to the +class that defines it and should only be called by the internal Readable +class methods. All Readable stream implementations must provide a _read +method to fetch data from the underlying resource. -All Readable stream implementations must provide a `_read` method to -fetch data from the underlying resource. - -This method is prefixed with an underscore because it is internal to -the class that defines it, and should not be called directly by user -programs. However, you **are** expected to override this method in -your own extension classes. +When _read is called, if data is available from the resource, `_read` should +start pushing that data into the read queue by calling `this.push(dataChunk)`. +`_read` should continue reading from the resource and pushing data until push +returns false, at which point it should stop reading from the resource. Only +when _read is called again after it has stopped should it start reading +more data from the resource and pushing that data onto the queue. -When data is available, put it into the read queue by calling -`readable.push(chunk)`. If `push` returns false, then you should stop -reading. When `_read` is called again, you should start pushing more -data. +Note: once the `_read()` method is called, it will not be called again until +the `push` method is called. The `size` argument is advisory. Implementations where a "read" is a single call that returns data can use this to know how much data to @@ -955,19 +1000,16 @@ becomes available. There is no need, for example to "wait" until Buffer encoding, such as `'utf8'` or `'ascii'` * return {Boolean} Whether or not more pushes should be performed -Note: **This function should be called by Readable implementors, NOT +Note: **This method should be called by Readable implementors, NOT by consumers of Readable streams.** -The `_read()` function will not be called again until at least one -`push(chunk)` call is made. - -The `Readable` class works by putting data into a read queue to be -pulled out later by calling the `read()` method when the `'readable'` -event fires. +If a value other than null is passed, The `push()` method adds a chunk of data +into the queue for subsequent stream processors to consume. If `null` is +passed, it signals the end of the stream (EOF), after which no more data +can be written. -The `push()` method will explicitly insert some data into the read -queue. If it is called with `null` then it will signal the end of the -data (EOF). +The data added with `push` can be pulled out by calling the `read()` method +when the `'readable'`event fires. This API is designed to be as flexible as possible. For example, you may be wrapping a lower-level source which has some sort of @@ -1315,7 +1357,7 @@ for examples and testing, but there are occasionally use cases where it can come in handy as a building block for novel sorts of streams. -## Simplified Constructor API +## Simplified Constructor API diff --git a/lib/_http_client.js b/lib/_http_client.js index a7d714f7e0b0b2..50d1052b44e15c 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -359,7 +359,7 @@ function parserOnIncomingClient(res, shouldKeepAlive) { var req = socket._httpMessage; - // propogate "domain" setting... + // propagate "domain" setting... if (req.domain && !res.domain) { debug('setting "res.domain"'); res.domain = req.domain; @@ -465,7 +465,7 @@ function tickOnSocket(req, socket) { socket.parser = parser; socket._httpMessage = req; - // Setup "drain" propogation. + // Setup "drain" propagation. httpSocketSetup(socket); // Propagate headers limit from request object to parser diff --git a/lib/url.js b/lib/url.js index 55c5248e4751dd..45155fee936bbf 100644 --- a/lib/url.js +++ b/lib/url.js @@ -587,7 +587,7 @@ Url.prototype.resolveObject = function(relative) { if (psychotic) { result.hostname = result.host = srcPath.shift(); //occationaly the auth can get stuck only in host - //this especialy happens in cases like + //this especially happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; @@ -669,7 +669,7 @@ Url.prototype.resolveObject = function(relative) { result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; //occationaly the auth can get stuck only in host - //this especialy happens in cases like + //this especially happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; diff --git a/src/node.cc b/src/node.cc index 7c0a80ec31462b..c272139e5518cd 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2184,7 +2184,7 @@ static void OnFatalError(const char* location, const char* message) { NO_RETURN void FatalError(const char* location, const char* message) { OnFatalError(location, message); - // to supress compiler warning + // to suppress compiler warning abort(); } diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index d00e1484b7c10c..f0226622272a5c 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -80,7 +80,7 @@ class ObjectWrap { * attached to detached state it will be freed. Be careful not to access * the object after making this call as it might be gone! * (A "weak reference" means an object that only has a - * persistant handle.) + * persistent handle.) * * DO NOT CALL THIS FROM DESTRUCTOR */ From c8282de860c434ed45982d5f737bd47b31accb93 Mon Sep 17 00:00:00 2001 From: Gireesh Punathil Date: Wed, 18 Mar 2015 04:12:16 -0400 Subject: [PATCH 03/11] test: address timing issues in simple http tests simple tests test-http-request-end.js, test-http-default-encoding.js hangs in AIX. The root cause for both the failures is related to the timing with which packets are sent between the client and server. On the client side, one factor that affects the timing is Nagle's algorithm. With Nagle enabled there may be a delay between two packets as the stack may wait until either: a. An acknowledgement for the first packet is received, or b. 200 ms elapses. before sending the second packet. Similarly at the server side 2 sequential packages can be delivered to the application either together or separatly. On AIX we see that they are delivered separately to the server, while on Linux delivered together. If we change the timing, for example disabling Nagle on AIX we see the 2 packets delivered together and the tests pass. In the test case simple/test-http-request-end.js, the client request handler of the server receives and stores the data in a data callback, closes the server in a request end callback, and writes to the client and ends the response, in-line with the request receipt. An HTTP parser module parses the incoming message, and invokes callback routines which are registered for HTTP events (such as header, body, end etc.) Because the termination sequence arrive in a separate packet, there is a delay in parsing that message and identify that the client request ended (and thereby invoke the request end call backhandler). Due to this delay, the response close happens first, which in-turn destroys the server socket leading to the fd and watcher removal from the uv loop abandoning further events on this connection, and end call back never being called, causing the reported hang. simple/test-http-default-encoding.js suffers from the same problem. Also, remove the timer logic from the test case. Test harness anyways contain a timer which controls the individual tests so remove such controls from the test case, as suggested by @tjfontaine Reviewed-By: Michael Dawson PR-URL: https://github.com/joyent/node/pull/9432 PORT-FROM: joyent/node @ 13e1131406b2239f99962ecc05b8179aa781d0f8 PR-URL: https://github.com/nodejs/io.js/pull/2294 Reviewed-By: Fedor Indutny Reviewed-By: Ben Noordhuis --- test/parallel/test-http-default-encoding.js | 9 ++------- test/parallel/test-http-request-end.js | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/test/parallel/test-http-default-encoding.js b/test/parallel/test-http-default-encoding.js index a40c8841eceab9..612a75bce46647 100644 --- a/test/parallel/test-http-default-encoding.js +++ b/test/parallel/test-http-default-encoding.js @@ -11,16 +11,11 @@ var server = http.Server(function(req, res) { req.on('data', function(chunk) { result += chunk; }).on('end', function() { - clearTimeout(timeout); server.close(); + res.writeHead(200); + res.end('hello world\n'); }); - var timeout = setTimeout(function() { - process.exit(1); - }, 100); - - res.writeHead(200); - res.end('hello world\n'); }); server.listen(common.PORT, function() { diff --git a/test/parallel/test-http-request-end.js b/test/parallel/test-http-request-end.js index 43d023028a4fd0..6ecfc0672e35ff 100644 --- a/test/parallel/test-http-request-end.js +++ b/test/parallel/test-http-request-end.js @@ -14,10 +14,10 @@ var server = http.Server(function(req, res) { req.on('end', function() { server.close(); + res.writeHead(200); + res.end('hello world\n'); }); - res.writeHead(200); - res.end('hello world\n'); }); server.listen(common.PORT, function() { From 6f89de9c9ab18468be19c526136a0c8ec607f922 Mon Sep 17 00:00:00 2001 From: Ryan Graham Date: Thu, 2 Jul 2015 15:26:21 -0700 Subject: [PATCH 04/11] net: ensure Socket reported address is current Any time the connection state or the underlying handle itself changes, the socket's name (aka, local address) can change. To deal with this we need to reset the cached sockname any time we set or unset the internal handle or an existing handle establishes a connection. PR-URL: https://github.com/nodejs/io.js/pull/2095 Reviewed-By: Ben Noordhuis Reviewed-By: Jeremiah Senkpiel --- lib/net.js | 4 +++ .../parallel/test-net-socket-local-address.js | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 test/parallel/test-net-socket-local-address.js diff --git a/lib/net.js b/lib/net.js index 6146029d3c4743..10e789678440fd 100644 --- a/lib/net.js +++ b/lib/net.js @@ -93,6 +93,7 @@ function initSocketHandle(self) { self.destroyed = false; self.bytesRead = 0; self._bytesDispatched = 0; + self._sockname = null; // Handle creation may be deferred to bind() or connect() time. if (self._handle) { @@ -469,6 +470,7 @@ Socket.prototype._destroy = function(exception, cb) { }); this._handle.onread = noop; this._handle = null; + this._sockname = null; } // we set destroyed to true before firing error callbacks in order @@ -871,6 +873,7 @@ Socket.prototype.connect = function(options, cb) { this.destroyed = false; this._handle = null; this._peername = null; + this._sockname = null; } var self = this; @@ -1032,6 +1035,7 @@ function afterConnect(status, handle, req, readable, writable) { assert.ok(self._connecting); self._connecting = false; + self._sockname = null; if (status == 0) { self.readable = readable; diff --git a/test/parallel/test-net-socket-local-address.js b/test/parallel/test-net-socket-local-address.js new file mode 100644 index 00000000000000..4c0e31d08c4771 --- /dev/null +++ b/test/parallel/test-net-socket-local-address.js @@ -0,0 +1,34 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); + +var conns = 0; +var clientLocalPorts = []; +var serverRemotePorts = []; + +var server = net.createServer(function(socket) { + serverRemotePorts.push(socket.remotePort); + conns++; +}); + +var client = new net.Socket(); + +server.on('close', function() { + assert.deepEqual(clientLocalPorts, serverRemotePorts, + 'client and server should agree on the ports used'); + assert.equal(2, conns); +}); + +server.listen(common.PORT, common.localhostIPv4, testConnect); + +function testConnect() { + if (conns == 2) { + return server.close(); + } + client.connect(common.PORT, common.localhostIPv4, function() { + clientLocalPorts.push(this.localPort); + this.once('close', testConnect); + this.destroy(); + }); +} From 6ef7aea5875f3d8cc471c26f428e8ed5685d69e9 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Wed, 10 Jun 2015 08:41:03 -0700 Subject: [PATCH 05/11] test: make listen-fd-cluster/server more robust - eliminate unnecessary intermediate process ("parent") - children exit if runner dies unexpectedly (killed on a test timeout, for example) - use explicit messaging from children to parents to indicate when worker is ready to accept http requests, rather than racing to see whether runner will make request before worker is listening PR-URL: https://github.com/nodejs/io.js/pull/1944 Reviewed-By: Johan Bergstrom Reviewed-By: Ben Noordhuis --- test/parallel/test-listen-fd-cluster.js | 85 +++++++++++++++---------- test/parallel/test-listen-fd-server.js | 82 +++++++++++------------- 2 files changed, 88 insertions(+), 79 deletions(-) diff --git a/test/parallel/test-listen-fd-cluster.js b/test/parallel/test-listen-fd-cluster.js index e895a2944789bd..f6d00c72a0f78c 100644 --- a/test/parallel/test-listen-fd-cluster.js +++ b/test/parallel/test-listen-fd-cluster.js @@ -7,63 +7,62 @@ var PORT = common.PORT; var spawn = require('child_process').spawn; var cluster = require('cluster'); -console.error('Cluster listen fd test', process.argv.slice(2)); +console.error('Cluster listen fd test', process.argv[2] || 'runner'); if (common.isWindows) { console.log('1..0 # Skipped: This test is disabled on windows.'); return; } +// Process relationship is: +// +// parent: the test main script +// -> master: the cluster master +// -> worker: the cluster worker switch (process.argv[2]) { case 'master': return master(); case 'worker': return worker(); case 'parent': return parent(); - default: return test(); } +var ok; + +process.on('exit', function() { + assert.ok(ok); +}); + // spawn the parent, and listen for it to tell us the pid of the cluster. // WARNING: This is an example of listening on some arbitrary FD number // that has already been bound elsewhere in advance. However, binding // server handles to stdio fd's is NOT a good or reliable way to do // concurrency in HTTP servers! Use the cluster module, or if you want // a more low-level approach, use child process IPC manually. -function test() { - var parent = spawn(process.execPath, [__filename, 'parent'], { - stdio: [ 0, 'pipe', 2 ] - }); - var json = ''; - parent.stdout.on('data', function(c) { - json += c.toString(); - if (json.indexOf('\n') !== -1) next(); - }); - function next() { - console.error('output from parent = %s', json); - var cluster = JSON.parse(json); - // now make sure that we can request to the worker, then kill it. - http.get({ - server: 'localhost', - port: PORT, - path: '/', - }).on('response', function(res) { - var s = ''; - res.on('data', function(c) { - s += c.toString(); - }); - res.on('end', function() { - // kill the worker before we start doing asserts. - // it's really annoying when tests leave orphans! - parent.kill(); - process.kill(cluster.master, 'SIGKILL'); - +test(function(parent) { + // now make sure that we can request to the worker, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function(res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the worker before we start doing asserts. + // it's really annoying when tests leave orphans! + parent.kill(); + parent.on('exit', function() { assert.equal(s, 'hello from worker\n'); assert.equal(res.statusCode, 200); console.log('ok'); + ok = true; }); }); - } -} + }); +}); -function parent() { +function test(cb) { console.error('about to listen in parent'); var server = net.createServer(function(conn) { console.error('connection on parent'); @@ -73,7 +72,7 @@ function parent() { var spawn = require('child_process').spawn; var master = spawn(process.execPath, [__filename, 'master'], { - stdio: [ 0, 1, 2, server._handle ], + stdio: [ 0, 'pipe', 2, server._handle, 'ipc' ], detached: true }); @@ -90,6 +89,11 @@ function parent() { console.error('master closed'); }); console.error('master spawned'); + master.on('message', function(msg) { + if (msg === 'started worker') { + cb(master); + } + }); }); } @@ -99,7 +103,17 @@ function master() { args: [ 'worker' ] }); var worker = cluster.fork(); - console.log('%j\n', { master: process.pid, worker: worker.pid }); + worker.on('message', function(msg) { + if (msg === 'worker ready') { + process.send('started worker'); + } + }); + // Prevent outliving our parent process in case it is abnormally killed - + // under normal conditions our parent kills this process before exiting. + process.on('disconnect', function() { + console.error('master exit on disconnect'); + process.exit(0); + }); } @@ -112,5 +126,6 @@ function worker() { res.end('hello from worker\n'); }).listen({ fd: 3 }, function() { console.error('worker listening on fd=3'); + process.send('worker ready'); }); } diff --git a/test/parallel/test-listen-fd-server.js b/test/parallel/test-listen-fd-server.js index d4d11e5a8d9693..d51d51ee8eb4de 100644 --- a/test/parallel/test-listen-fd-server.js +++ b/test/parallel/test-listen-fd-server.js @@ -13,54 +13,50 @@ if (common.isWindows) { switch (process.argv[2]) { case 'child': return child(); - case 'parent': return parent(); - default: return test(); } -// spawn the parent, and listen for it to tell us the pid of the child. +var ok; + +process.on('exit', function() { + assert.ok(ok); +}); + // WARNING: This is an example of listening on some arbitrary FD number // that has already been bound elsewhere in advance. However, binding // server handles to stdio fd's is NOT a good or reliable way to do // concurrency in HTTP servers! Use the cluster module, or if you want // a more low-level approach, use child process IPC manually. -function test() { - var parent = spawn(process.execPath, [__filename, 'parent'], { - stdio: [ 0, 'pipe', 2 ] - }); - var json = ''; - parent.stdout.on('data', function(c) { - json += c.toString(); - if (json.indexOf('\n') !== -1) next(); - }); - function next() { - console.error('output from parent = %s', json); - var child = JSON.parse(json); - // now make sure that we can request to the child, then kill it. - http.get({ - server: 'localhost', - port: PORT, - path: '/', - }).on('response', function(res) { - var s = ''; - res.on('data', function(c) { - s += c.toString(); - }); - res.on('end', function() { - // kill the child before we start doing asserts. - // it's really annoying when tests leave orphans! - process.kill(child.pid, 'SIGKILL'); - try { - parent.kill(); - } catch (e) {} - +test(function(child) { + // now make sure that we can request to the child, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function(res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + child.kill(); + child.on('exit', function() { assert.equal(s, 'hello from child\n'); assert.equal(res.statusCode, 200); + console.log('ok'); + ok = true; }); }); - } -} + }); +}); function child() { + // Prevent outliving the parent process in case it is terminated before + // killing this child process. + process.on('disconnect', function() { + console.error('exit on disconnect'); + process.exit(0); + }); + // start a server on fd=3 http.createServer(function(req, res) { console.error('request on child'); @@ -68,10 +64,11 @@ function child() { res.end('hello from child\n'); }).listen({ fd: 3 }, function() { console.error('child listening on fd=3'); + process.send('listening'); }); } -function parent() { +function test(cb) { var server = net.createServer(function(conn) { console.error('connection on parent'); conn.end('hello from parent\n'); @@ -80,7 +77,7 @@ function parent() { var spawn = require('child_process').spawn; var child = spawn(process.execPath, [__filename, 'child'], { - stdio: [ 0, 1, 2, server._handle ] + stdio: [ 0, 1, 2, server._handle, 'ipc' ] }); console.log('%j\n', { pid: child.pid }); @@ -90,13 +87,10 @@ function parent() { // be accepted, because the child has the fd open. server.close(); - child.on('exit', function(code) { - console.error('child exited', code); - }); - - child.on('close', function() { - console.error('child closed'); + child.on('message', function(msg) { + if (msg === 'listening') { + cb(child); + } }); - console.error('child spawned'); }); } From 51b6bdcb499f04a22d9ef7199bb280ac4c553302 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Fri, 7 Aug 2015 18:12:44 -0700 Subject: [PATCH 06/11] tls: introduce internal `onticketkeycallback` `enableTicketKeyCallback` and `onticketkeycallback` could be potentially used to renew the TLS Session Tickets before they expire. However this commit will introduce it only for private use yet, because we are not sure about the API, and already need this feature for testing. See: https://github.com/nodejs/io.js/issues/2304 PR-URL: https://github.com/nodejs/io.js/pull/2312 Reviewed-By: Shigeki Ohtsu --- src/env.h | 1 + src/node_crypto.cc | 104 +++++++++++++++++++++++++++++++++++++++++++++ src/node_crypto.h | 16 +++++++ 3 files changed, 121 insertions(+) diff --git a/src/env.h b/src/env.h index 501c151122198e..bf80881907e65a 100644 --- a/src/env.h +++ b/src/env.h @@ -198,6 +198,7 @@ namespace node { V(syscall_string, "syscall") \ V(tick_callback_string, "_tickCallback") \ V(tick_domain_cb_string, "_tickDomainCallback") \ + V(ticketkeycallback_string, "onticketkeycallback") \ V(timeout_string, "timeout") \ V(times_string, "times") \ V(timestamp_string, "timestamp") \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c14f2b600c677e..e33b249242e3e5 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -300,9 +300,23 @@ void SecureContext::Initialize(Environment* env, Handle target) { env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys); env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys); env->SetProtoMethod(t, "setFreeListLength", SecureContext::SetFreeListLength); + env->SetProtoMethod(t, + "enableTicketKeyCallback", + SecureContext::EnableTicketKeyCallback); env->SetProtoMethod(t, "getCertificate", SecureContext::GetCertificate); env->SetProtoMethod(t, "getIssuer", SecureContext::GetCertificate); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyReturnIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyReturnIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyHMACIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyHMACIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyAESIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyAESIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyNameIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyNameIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyIVIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyIVIndex)); + t->PrototypeTemplate()->SetAccessor( FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), CtxGetter, @@ -378,6 +392,7 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { } sc->ctx_ = SSL_CTX_new(method); + SSL_CTX_set_app_data(sc->ctx_, sc); // Disable SSLv2 in the case when method == SSLv23_method() and the // cipher list contains SSLv2 ciphers (not the default, should be rare.) @@ -982,6 +997,95 @@ void SecureContext::SetFreeListLength(const FunctionCallbackInfo& args) { } +void SecureContext::EnableTicketKeyCallback( + const FunctionCallbackInfo& args) { + SecureContext* wrap = Unwrap(args.Holder()); + + SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_, TicketKeyCallback); +} + + +int SecureContext::TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc) { + static const int kTicketPartSize = 16; + + SecureContext* sc = static_cast( + SSL_CTX_get_app_data(ssl->ctx)); + + Environment* env = sc->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Buffer::New(env, + reinterpret_cast(name), + kTicketPartSize).ToLocalChecked(), + Buffer::New(env, + reinterpret_cast(iv), + kTicketPartSize).ToLocalChecked(), + Boolean::New(env->isolate(), enc != 0) + }; + Local ret = node::MakeCallback(env, + sc->object(), + env->ticketkeycallback_string(), + ARRAY_SIZE(argv), + argv); + Local arr = ret.As(); + + int r = arr->Get(kTicketKeyReturnIndex)->Int32Value(); + if (r < 0) + return r; + + Local hmac = arr->Get(kTicketKeyHMACIndex); + Local aes = arr->Get(kTicketKeyAESIndex); + if (Buffer::Length(aes) != kTicketPartSize) + return -1; + + if (enc) { + Local name_val = arr->Get(kTicketKeyNameIndex); + Local iv_val = arr->Get(kTicketKeyIVIndex); + + if (Buffer::Length(name_val) != kTicketPartSize || + Buffer::Length(iv_val) != kTicketPartSize) { + return -1; + } + + memcpy(name, Buffer::Data(name_val), kTicketPartSize); + memcpy(iv, Buffer::Data(iv_val), kTicketPartSize); + } + + HMAC_Init_ex(hctx, + Buffer::Data(hmac), + Buffer::Length(hmac), + EVP_sha256(), + nullptr); + + const unsigned char* aes_key = + reinterpret_cast(Buffer::Data(aes)); + if (enc) { + EVP_EncryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } else { + EVP_DecryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } + + return r; +} + + + + void SecureContext::CtxGetter(Local property, const PropertyCallbackInfo& info) { HandleScope scope(info.GetIsolate()); diff --git a/src/node_crypto.h b/src/node_crypto.h index 3a00b519323d52..edacaa1b0095b9 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -68,6 +68,13 @@ class SecureContext : public BaseObject { static const int kMaxSessionSize = 10 * 1024; + // See TicketKeyCallback + static const int kTicketKeyReturnIndex = 0; + static const int kTicketKeyHMACIndex = 1; + static const int kTicketKeyAESIndex = 2; + static const int kTicketKeyNameIndex = 3; + static const int kTicketKeyIVIndex = 4; + protected: static const int64_t kExternalSize = sizeof(SSL_CTX); @@ -92,12 +99,21 @@ class SecureContext : public BaseObject { static void SetTicketKeys(const v8::FunctionCallbackInfo& args); static void SetFreeListLength( const v8::FunctionCallbackInfo& args); + static void EnableTicketKeyCallback( + const v8::FunctionCallbackInfo& args); static void CtxGetter(v8::Local property, const v8::PropertyCallbackInfo& info); template static void GetCertificate(const v8::FunctionCallbackInfo& args); + static int TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc); + SecureContext(Environment* env, v8::Local wrap) : BaseObject(env, wrap), ca_store_(nullptr), From 581850b10c84beb127f7c0d4bee7faafcc5cb0bb Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Fri, 7 Aug 2015 18:14:54 -0700 Subject: [PATCH 07/11] tls: fix check for reused session When TLS Session Ticket is renewed by server - no Certificate record is to the client. We are prepared for empty certificate in this case, but this relies on the session reuse check, which was implemented incorrectly and was returning false when the TLS Session Ticket was renewed. Use session reuse check provided by OpenSSL instead. Fix: https://github.com/nodejs/io.js/issues/2304 PR-URL: https://github.com/nodejs/io.js/pull/2312 Reviewed-By: Shigeki Ohtsu --- lib/_tls_wrap.js | 13 +---- .../parallel/test-https-resume-after-renew.js | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 test/parallel/test-https-resume-after-renew.js diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 182346904c9bf4..b5d1899480bf46 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -584,17 +584,6 @@ TLSSocket.prototype._start = function() { this._handle.start(); }; -TLSSocket.prototype._isSessionResumed = function _isSessionResumed(session) { - if (!session) - return false; - - var next = this.getSession(); - if (!next) - return false; - - return next.equals(session); -}; - TLSSocket.prototype.setServername = function(name) { this._handle.setServername(name); }; @@ -1011,7 +1000,7 @@ exports.connect = function(/* [port, host], options, cb */) { // Verify that server's identity matches it's certificate's names // Unless server has resumed our existing session - if (!verifyError && !socket._isSessionResumed(options.session)) { + if (!verifyError && !socket.isSessionReused()) { var cert = socket.getPeerCertificate(); verifyError = options.checkServerIdentity(hostname, cert); } diff --git a/test/parallel/test-https-resume-after-renew.js b/test/parallel/test-https-resume-after-renew.js new file mode 100644 index 00000000000000..23626ccb40cb51 --- /dev/null +++ b/test/parallel/test-https-resume-after-renew.js @@ -0,0 +1,56 @@ +'use strict'; +var common = require('../common'); +var fs = require('fs'); +var https = require('https'); +var crypto = require('crypto'); + +var options = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'), + ca: fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem') +}; + +var server = https.createServer(options, function(req, res) { + res.end('hello'); +}); + +var aes = new Buffer(16); +aes.fill('S'); +var hmac = new Buffer(16); +hmac.fill('H'); + +server._sharedCreds.context.enableTicketKeyCallback(); +server._sharedCreds.context.onticketkeycallback = function(name, iv, enc) { + if (enc) { + var newName = new Buffer(16); + var newIV = crypto.randomBytes(16); + newName.fill('A'); + } else { + // Renew + return [ 2, hmac, aes ]; + } + + return [ 1, hmac, aes, newName, newIV ]; +}; + +server.listen(common.PORT, function() { + var addr = this.address(); + + function doReq(callback) { + https.request({ + method: 'GET', + port: addr.port, + servername: 'agent1', + ca: options.ca + }, function(res) { + res.resume(); + res.once('end', callback); + }).end(); + } + + doReq(function() { + doReq(function() { + server.close(); + }); + }); +}); From ee9dd683e5aa8aafd59b8ab81d3f1eb2575e4947 Mon Sep 17 00:00:00 2001 From: Christopher Monsanto Date: Wed, 10 Jun 2015 04:25:04 -0400 Subject: [PATCH 08/11] util: display constructor when inspecting objects This commit modifies util.inspect(obj) to additionally show the name of the function that constructed the object. This often reveals useful information about the object's prototype. In other words, instead of > new Cls {} we have > new Cls Cls {} This also works with exotic objects: > class ArrayCls extends Array {} > new ArrayCls(1, 2, 3) ArrayCls [ 1, 2, 3 ] The names of "trivial" constructors like Object and Array are not shown, unless there is a mismatch between the object representation and the prototype: > Object.create([]) Array {} This feature is inspired by browser devtools. PR-URL: https://github.com/nodejs/io.js/pull/1935 Reviewed-By: Roman Reiss Reviewed-By: Jeremiah Senkpiel --- lib/util.js | 31 ++++++++++++++++++--- test/parallel/test-sys.js | 2 +- test/parallel/test-util-inspect.js | 43 +++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 136a66a6c1c0e8..c5d7bea7db352d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -167,6 +167,22 @@ function arrayToHash(array) { } +function getConstructorOf(obj) { + while (obj) { + var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); + if (descriptor !== undefined && + typeof descriptor.value === 'function' && + descriptor.value.name !== '') { + return descriptor.value; + } + + obj = Object.getPrototypeOf(obj); + } + + return null; +} + + function inspectPromise(p) { Debug = Debug || require('vm').runInDebugContext('Debug'); var mirror = Debug.MakeMirror(p, true); @@ -260,14 +276,17 @@ function formatValue(ctx, value, recurseTimes) { } } + var constructor = getConstructorOf(value); var base = '', empty = false, braces, formatter; if (Array.isArray(value)) { + if (constructor === Array) + constructor = null; braces = ['[', ']']; empty = value.length === 0; formatter = formatArray; } else if (value instanceof Set) { - braces = ['Set {', '}']; + braces = ['{', '}']; // With `showHidden`, `length` will display as a hidden property for // arrays. For consistency's sake, do the same for `size`, even though this // property isn't selected by Object.getOwnPropertyNames(). @@ -276,7 +295,7 @@ function formatValue(ctx, value, recurseTimes) { empty = value.size === 0; formatter = formatSet; } else if (value instanceof Map) { - braces = ['Map {', '}']; + braces = ['{', '}']; // Ditto. if (ctx.showHidden) keys.unshift('size'); @@ -286,9 +305,11 @@ function formatValue(ctx, value, recurseTimes) { // Only create a mirror if the object superficially looks like a Promise. var promiseInternals = value instanceof Promise && inspectPromise(value); if (promiseInternals) { - braces = ['Promise {', '}']; + braces = ['{', '}']; formatter = formatPromise; } else { + if (constructor === Object) + constructor = null; braces = ['{', '}']; empty = true; // No other data than keys. formatter = formatObject; @@ -336,6 +357,10 @@ function formatValue(ctx, value, recurseTimes) { base = ' ' + '[Boolean: ' + formatted + ']'; } + // Add constructor name if available + if (base === '' && constructor) + braces[0] = constructor.name + ' ' + braces[0]; + if (empty === true) { return braces[0] + base + braces[1]; } diff --git a/test/parallel/test-sys.js b/test/parallel/test-sys.js index bbc8c092002af6..9367e55c687f2f 100644 --- a/test/parallel/test-sys.js +++ b/test/parallel/test-sys.js @@ -17,7 +17,7 @@ assert.equal(new Date('2010-02-14T12:48:40+01:00').toString(), assert.equal("'\\n\\u0001'", common.inspect('\n\u0001')); assert.equal('[]', common.inspect([])); -assert.equal('{}', common.inspect(Object.create([]))); +assert.equal('Array {}', common.inspect(Object.create([]))); assert.equal('[ 1, 2 ]', common.inspect([1, 2])); assert.equal('[ 1, [ 2, 3 ] ]', common.inspect([1, [2, 3]])); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index f583005ce96f75..fcde1c74c3984e 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -61,7 +61,7 @@ assert.ok(ex.indexOf('[message]') != -1); // GH-1941 // should not throw: -assert.equal(util.inspect(Object.create(Date.prototype)), '{}'); +assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}'); // GH-1944 assert.doesNotThrow(function() { @@ -306,3 +306,44 @@ checkAlignment(function() { }()); checkAlignment(new Set(big_array)); checkAlignment(new Map(big_array.map(function(y) { return [y, null]; }))); + + +// Test display of constructors + +class ObjectSubclass {} +class ArraySubclass extends Array {} +class SetSubclass extends Set {} +class MapSubclass extends Map {} +class PromiseSubclass extends Promise {} + +var x = new ObjectSubclass(); +x.foo = 42; +assert.equal(util.inspect(x), + 'ObjectSubclass { foo: 42 }'); +assert.equal(util.inspect(new ArraySubclass(1, 2, 3)), + 'ArraySubclass [ 1, 2, 3 ]'); +assert.equal(util.inspect(new SetSubclass([1, 2, 3])), + 'SetSubclass { 1, 2, 3 }'); +assert.equal(util.inspect(new MapSubclass([['foo', 42]])), + 'MapSubclass { \'foo\' => 42 }'); +assert.equal(util.inspect(new PromiseSubclass(function() {})), + 'PromiseSubclass { }'); + +// Corner cases. +var x = { constructor: 42 }; +assert.equal(util.inspect(x), '{ constructor: 42 }'); + +var x = {}; +Object.defineProperty(x, 'constructor', { + get: function() { + throw new Error('should not access constructor'); + }, + enumerable: true +}); +assert.equal(util.inspect(x), '{ constructor: [Getter] }'); + +var x = new (function() {}); +assert.equal(util.inspect(x), '{}'); + +var x = Object.create(null); +assert.equal(util.inspect(x), '{}'); From 3a48c7220ea104ce2a6789a0a0fe9a5977dc180a Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Sat, 8 Aug 2015 23:56:08 -0600 Subject: [PATCH 09/11] build: update manifest to include Windows 10 Windows 10 wasn't listed in the executable manifest. This caused problems with trying to detect Windows 10 via `os.release()`. PR-URL: https://github.com/nodejs/io.js/pull/2332 Reviewed-By: Roman Reiss --- src/res/node.exe.extra.manifest | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/res/node.exe.extra.manifest b/src/res/node.exe.extra.manifest index c4cc80a141d9dd..e2e9f175473305 100644 --- a/src/res/node.exe.extra.manifest +++ b/src/res/node.exe.extra.manifest @@ -2,6 +2,8 @@ + + From 4750a2af19c7d345cf1d6d39e115b56e75a0b44c Mon Sep 17 00:00:00 2001 From: Nathan Woltman Date: Fri, 31 Jul 2015 17:47:49 -0400 Subject: [PATCH 10/11] path: remove dead code in favor of unit tests Remove dead code paths that are created by assertions that will never trigger. They may only trigger if either the `splitDeviceRe` or `splitPathRe` regular expressions are modified. If at some point they are modified, current unit tests will catch most of the resulting errors and this commit adds extra tests to catch the remaining errors. PR-URL: https://github.com/nodejs/io.js/pull/2282 Reviewed-By: Roman Reiss --- lib/path.js | 12 +----------- test/parallel/test-path-parse-format.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/path.js b/lib/path.js index e0c5bcaa1c1050..78c61579ec0fc8 100644 --- a/lib/path.js +++ b/lib/path.js @@ -76,7 +76,7 @@ function win32SplitPath(filename) { // Separate device+slash from tail var result = splitDeviceRe.exec(filename), device = (result[1] || '') + (result[2] || ''), - tail = result[3] || ''; + tail = result[3]; // Split the tail into dir, basename and extension var result2 = splitTailRe.exec(tail), dir = result2[1], @@ -386,9 +386,6 @@ win32.parse = function(pathString) { assertPath(pathString); var allParts = win32SplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); - } return { root: allParts[0], dir: allParts[0] + allParts[1].slice(0, -1), @@ -590,13 +587,6 @@ posix.parse = function(pathString) { assertPath(pathString); var allParts = posixSplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); - } - allParts[1] = allParts[1] || ''; - allParts[2] = allParts[2] || ''; - allParts[3] = allParts[3] || ''; - return { root: allParts[0], dir: allParts[0] + allParts[1].slice(0, -1), diff --git a/test/parallel/test-path-parse-format.js b/test/parallel/test-path-parse-format.js index 677bf3241f0bae..e90fe217de93ec 100644 --- a/test/parallel/test-path-parse-format.js +++ b/test/parallel/test-path-parse-format.js @@ -9,6 +9,7 @@ var winPaths = [ '\\foo\\C:', 'file', '.\\file', + '', // unc '\\\\server\\share\\file_path', @@ -32,7 +33,8 @@ var unixPaths = [ 'file', '.\\file', './file', - 'C:\\foo' + 'C:\\foo', + '' ]; var unixSpecialCaseFormatTests = [ @@ -52,8 +54,6 @@ var errors = [ message: /Path must be a string. Received 1/}, {method: 'parse', input: [], message: /Path must be a string. Received undefined/}, - // {method: 'parse', input: [''], - // message: /Invalid path/}, // omitted because it's hard to trigger! {method: 'format', input: [null], message: /Parameter 'pathObject' must be an object, not/}, {method: 'format', input: [''], @@ -93,8 +93,13 @@ function checkErrors(path) { } function checkParseFormat(path, paths) { - paths.forEach(function(element, index, array) { + paths.forEach(function(element) { var output = path.parse(element); + assert.strictEqual(typeof output.root, 'string'); + assert.strictEqual(typeof output.dir, 'string'); + assert.strictEqual(typeof output.base, 'string'); + assert.strictEqual(typeof output.ext, 'string'); + assert.strictEqual(typeof output.name, 'string'); assert.strictEqual(path.format(output), element); assert.strictEqual(output.dir, output.dir ? path.dirname(element) : ''); assert.strictEqual(output.base, path.basename(element)); From bed03ea143f3d260e192137d67fc5efa21a26731 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 25 Jun 2015 18:54:25 -0700 Subject: [PATCH 11/11] test: clarify dropMembership() call According to docs, dropMembership() is automatically called by the kernel when the socket is closed, and most apps will never need to call it. It's called here as a sanity check only so let's note that with a comment. Reviewed-By: Colin Ihrig Reviewed-By: Ben Noordhuis PR-URL: https://github.com/nodejs/io.js/pull/2062 --- test/internet/test-dgram-multicast-multi-process.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/internet/test-dgram-multicast-multi-process.js b/test/internet/test-dgram-multicast-multi-process.js index 05acb844e54490..34db38331dc7b6 100644 --- a/test/internet/test-dgram-multicast-multi-process.js +++ b/test/internet/test-dgram-multicast-multi-process.js @@ -3,7 +3,6 @@ var common = require('../common'), assert = require('assert'), dgram = require('dgram'), util = require('util'), - assert = require('assert'), Buffer = require('buffer').Buffer, fork = require('child_process').fork, LOCAL_BROADCAST_HOST = '224.0.0.114', @@ -183,10 +182,9 @@ if (process.argv[2] === 'child') { process.send({ message: buf.toString() }); if (receivedMessages.length == messages.length) { + // .dropMembership() not strictly needed but here as a sanity check listenSocket.dropMembership(LOCAL_BROADCAST_HOST); - - process.nextTick(function() { // TODO should be changed to below. - // listenSocket.dropMembership(LOCAL_BROADCAST_HOST, function() { + process.nextTick(function() { listenSocket.close(); }); }