From acf97ef77326e5cb72988228b2447abd819ada94 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 18:55:36 +0200 Subject: [PATCH 01/47] [cleanup] index.js --- index.js | 11 ++++------- package.json | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 7a5f653..9bf3864 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,17 @@ -// builtin + var fs = require('fs'); var path = require('path'); -// vendor -var resv = require('resolve'); - // core modules replaced by their browser capable counterparts var core = {}; // load core modules from builtin dir -fs.readdirSync(__dirname + '/builtin/').forEach(function(file) { - core[path.basename(file, '.js')] = path.join(__dirname, '/builtin/', file); +fs.readdirSync(path.resolve(__dirname, 'builtin')).forEach(function(file) { + core[path.basename(file, '.js')] = path.resolve(__dirname, 'builtin', file); }); // manually resolve modules that would otherwise resolve as core -core['punycode'] = path.join(__dirname, '/node_modules/punycode', 'punycode.js'); +core['punycode'] = path.resolve(__dirname, 'node_modules', 'punycode', 'punycode.js'); // manually add core which are provided by modules core['http'] = require.resolve('http-browserify'); diff --git a/package.json b/package.json index cb57ef2..d4ae74d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "author": "Alex Gorbatchev ", "license": "MIT", "dependencies": { - "resolve": "0.3.x", "punycode": "1.2.x", "console-browserify": "0.1.x", "vm-browserify": "0.0.x", From cc9683b36c84c47325319699689c3fb282d62ba8 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 21:00:55 +0200 Subject: [PATCH 02/47] [test] setup local testling --- package.json | 6 ++++++ test/browserify-transform.js | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 test/browserify-transform.js diff --git a/package.json b/package.json index d4ae74d..aa65de2 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "1.0.7", "description": "Builtins that were extracted from node-browser-resolve on which browserify depends", "main": "index.js", + "scripts": { + "test": "tape test/node/* && browserify --transform ./test/browserify-transform.js test/browser/* | testling" + }, "repository": { "type": "git", "url": "git://github.com/alexgorbatchev/node-browser-builtins.git" @@ -22,5 +25,8 @@ "buffer-browserify": "0.1.x", "zlib-browserify": "0.0.x", "constants-browserify": "0.0.x" + }, + "devDependencies": { + "tape": "1.0.x" } } diff --git a/test/browserify-transform.js b/test/browserify-transform.js new file mode 100644 index 0000000..0a0df1a --- /dev/null +++ b/test/browserify-transform.js @@ -0,0 +1,42 @@ + +// This transformer changes the require calls so they don't point to the +// browserify buildins (older version of this module). + +var path = require('path'); +var util = require('util'); +var stream = require('stream'); +var buildins = require('../index.js'); + +function RequireRedirect() { + if (!(this instanceof RequireRedirect)) return new RequireRedirect(); + stream.Transform.call(this); + this.buffers = []; +} +module.exports = RequireRedirect; +util.inherits(RequireRedirect, stream.Transform); + +RequireRedirect.prototype._transform = function (chunk, encoding, done) { + this.buffers.push(chunk); + done(null); +}; + +// NOTE: this is an incomplete require RegExp, but for internal use here +// its fine. +var REQUIRE_REGEX = /require\((?:"|')([^"']+)(?:"|')\)/g; + +var RELATIVE_DIR = path.resolve(__dirname, '..'); + +RequireRedirect.prototype._flush = function (done) { + var file = Buffer.concat(this.buffers).toString(); + + file = file.replace(REQUIRE_REGEX, function (source, name) { + if (buildins.hasOwnProperty(name)) { + return "require('" + buildins[name] + "')"; + } else { + return source; + } + }); + + this.push(file); + done(null); +}; From 6d424b280c2a7da303da21a4d0e398700c4076d5 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 21:02:06 +0200 Subject: [PATCH 03/47] [test] add simple test for assert module --- test/browser/assert-simple.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/browser/assert-simple.js diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js new file mode 100644 index 0000000..0cf7bec --- /dev/null +++ b/test/browser/assert-simple.js @@ -0,0 +1,29 @@ + +var test = require('tape'); + +var assert = require('assert'); + +test('assert and assert.ok is the same', function (t) { + t.equal(assert, assert.ok); + t.end(); +}); + +test('assert.ok - throw', function (t) { + try { + assert.ok(false); + t.ok(false, 'catch not reached'); + } catch (e) { + t.ok(true, 'catch reached'); + } + t.end(); +}); + +test('assert.ok - pass', function (t) { + try { + assert.ok(true); + t.ok(true, 'catch not reached'); + } catch (e) { + t.ok(false, 'catch reached'); + } + t.end(); +}); From 943ce4189dd40aa4d2d8f3fe8875ea08fe65a5f8 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 21:03:25 +0200 Subject: [PATCH 04/47] [test] add simple object key test for index.js --- test/node/node-test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/node/node-test.js diff --git a/test/node/node-test.js b/test/node/node-test.js new file mode 100644 index 0000000..cee9c12 --- /dev/null +++ b/test/node/node-test.js @@ -0,0 +1,35 @@ + +var test = require('tap').test; +var builtins = require('../../index.js'); + +test('test that all the modules are set', function (t) { + t.deepEqual(Object.keys(builtins).sort(), [ + 'assert', + 'buffer', + 'child_process', + 'console', + 'constants', + 'crypto', + 'dgram', + 'events', + 'fs', + 'http', + 'https', + 'net', + 'path', + 'process', + 'punycode', + 'querystring', + 'stream', + 'string_decoder', + 'sys', + 'timers', + 'tls', + 'tty', + 'url', + 'util', + 'vm', + 'zlib' + ]); + t.end(); +}); From ba776b13721a27e6b687292434f915671973e448 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 21:37:47 +0200 Subject: [PATCH 05/47] [feature] child_process now throw when required --- builtin/child_process.js | 4 ++-- test/browser/child-process-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/browser/child-process-simple.js diff --git a/builtin/child_process.js b/builtin/child_process.js index 919ec29..ed9d454 100644 --- a/builtin/child_process.js +++ b/builtin/child_process.js @@ -1,2 +1,2 @@ -exports.spawn = function () {}; -exports.exec = function () {}; + +throw new Error('child_process is not implemented'); diff --git a/test/browser/child-process-simple.js b/test/browser/child-process-simple.js new file mode 100644 index 0000000..ab4b7b4 --- /dev/null +++ b/test/browser/child-process-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require child_process should throw', function (t) { + try { + require('child_process'); + t.ok(false, 'child_process did not throw'); + } catch (e) { + t.ok(true, 'child_process did throw'); + } + t.end(); +}); From 5d6987f30947fe4bd2dd0065f68891124fdbc0cb Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Mon, 19 Aug 2013 21:39:05 +0200 Subject: [PATCH 06/47] [feature] cluster now throw when required --- builtin/cluster.js | 2 ++ test/browser/cluster-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 builtin/cluster.js create mode 100644 test/browser/cluster-simple.js diff --git a/builtin/cluster.js b/builtin/cluster.js new file mode 100644 index 0000000..59d51ef --- /dev/null +++ b/builtin/cluster.js @@ -0,0 +1,2 @@ + +throw new Error('cluster is not implemented'); diff --git a/test/browser/cluster-simple.js b/test/browser/cluster-simple.js new file mode 100644 index 0000000..7ba3a78 --- /dev/null +++ b/test/browser/cluster-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require cluster should throw', function (t) { + try { + require('cluster'); + t.ok(false, 'cluster did not throw'); + } catch (e) { + t.ok(true, 'cluster did throw'); + } + t.end(); +}); From c35be09e38c22486d1122bdf0f39f74f785c19db Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 08:55:14 +0200 Subject: [PATCH 07/47] [feature] dgram now throw when required --- builtin/dgram.js | 2 ++ test/browser/dgram-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 test/browser/dgram-simple.js diff --git a/builtin/dgram.js b/builtin/dgram.js index e69de29..0a574e7 100644 --- a/builtin/dgram.js +++ b/builtin/dgram.js @@ -0,0 +1,2 @@ + +throw new Error('dgram is not implemented'); diff --git a/test/browser/dgram-simple.js b/test/browser/dgram-simple.js new file mode 100644 index 0000000..1ce08b0 --- /dev/null +++ b/test/browser/dgram-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require dgram should throw', function (t) { + try { + require('dgram'); + t.ok(false, 'dgram did not throw'); + } catch (e) { + t.ok(true, 'dgram did throw'); + } + t.end(); +}); From c1bc56a540ff9ffffed3abeb01de75a260b2d536 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 08:58:38 +0200 Subject: [PATCH 08/47] [feature] dns now throw when required --- builtin/dns.js | 2 ++ test/browser/dns-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 builtin/dns.js create mode 100644 test/browser/dns-simple.js diff --git a/builtin/dns.js b/builtin/dns.js new file mode 100644 index 0000000..f71011e --- /dev/null +++ b/builtin/dns.js @@ -0,0 +1,2 @@ + +throw new Error('dns is not implemented'); diff --git a/test/browser/dns-simple.js b/test/browser/dns-simple.js new file mode 100644 index 0000000..09fee33 --- /dev/null +++ b/test/browser/dns-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require dns should throw', function (t) { + try { + require('dns'); + t.ok(false, 'dns did not throw'); + } catch (e) { + t.ok(true, 'dns did throw'); + } + t.end(); +}); From e08cda5523e72840dadcb80c33ce11ebec3c3ecf Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 09:35:59 +0200 Subject: [PATCH 09/47] [feature] update events and add tests This is failing because of an incomplete util module --- builtin/events.js | 272 +++++++++++++++--------- test/browser/events-listeners.js | 48 +++++ test/browser/events-remove-listeners.js | 93 ++++++++ 3 files changed, 310 insertions(+), 103 deletions(-) create mode 100644 test/browser/events-listeners.js create mode 100644 test/browser/events-remove-listeners.js diff --git a/builtin/events.js b/builtin/events.js index d468b73..d341913 100644 --- a/builtin/events.js +++ b/builtin/events.js @@ -1,53 +1,57 @@ -if (!process.EventEmitter) process.EventEmitter = function () {}; -var EventEmitter = exports.EventEmitter = process.EventEmitter; -var isArray = typeof Array.isArray === 'function' - ? Array.isArray - : function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]' - } -; -function indexOf (xs, x) { - if (xs.indexOf) return xs.indexOf(x); - for (var i = 0; i < xs.length; i++) { - if (x === xs[i]) return i; - } - return -1; +var util = require('util'); + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; } +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; -// By default EventEmitters will print a warning if more than -// 10 listeners are added to it. This is a useful default which -// helps finding memory leaks. -// // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. -var defaultMaxListeners = 10; EventEmitter.prototype.setMaxListeners = function(n) { - if (!this._events) this._events = {}; - this._events.maxListeners = n; + if (!util.isNumber(n) || n < 0) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; }; - EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + // If there is no 'error' event listener then throw. if (type === 'error') { - if (!this._events || !this._events.error || - (isArray(this._events.error) && !this._events.error.length)) - { - if (arguments[1] instanceof Error) { - throw arguments[1]; // Unhandled 'error' event + if (!this._events.error || + (util.isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event } else { - throw new Error("Uncaught, unspecified 'error' event."); + throw TypeError('Uncaught, unspecified "error" event.'); } return false; } } - if (!this._events) return false; - var handler = this._events[type]; - if (!handler) return false; + handler = this._events[type]; + + if (util.isUndefined(handler)) + return false; - if (typeof handler == 'function') { + if (util.isFunction(handler)) { switch (arguments.length) { // fast cases case 1: @@ -61,67 +65,70 @@ EventEmitter.prototype.emit = function(type) { break; // slower default: - var args = Array.prototype.slice.call(arguments, 1); + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; handler.apply(this, args); } - return true; + } else if (util.isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; - } else if (isArray(handler)) { - var args = Array.prototype.slice.call(arguments, 1); - - var listeners = handler.slice(); - for (var i = 0, l = listeners.length; i < l; i++) { + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) listeners[i].apply(this, args); - } - return true; - - } else { - return false; } + + return true; }; -// EventEmitter is defined in src/node_events.cc -// EventEmitter.prototype.emit() is also defined there. EventEmitter.prototype.addListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('addListener only takes instances of Function'); - } + var m; - if (!this._events) this._events = {}; + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, listener); + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + util.isFunction(listener.listener) ? + listener.listener : listener); - if (!this._events[type]) { + if (!this._events[type]) // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; - } else if (isArray(this._events[type])) { - - // Check for listener leak - if (!this._events[type].warned) { - var m; - if (this._events.maxListeners !== undefined) { - m = this._events.maxListeners; - } else { - m = defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - console.trace(); - } - } - + else if (util.isObject(this._events[type])) // If we've already got an array, just append. this._events[type].push(listener); - } else { + else // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (util.isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!util.isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } } return this; @@ -130,65 +137,124 @@ EventEmitter.prototype.addListener = function(type, listener) { EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { - var self = this; - self.on(type, function g() { - self.removeListener(type, g); + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); + + function g() { + this.removeListener(type, g); listener.apply(this, arguments); - }); + } + + g.listener = listener; + this.on(type, g); return this; }; +// emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('removeListener only takes instances of Function'); - } + var list, position, length, i; - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events || !this._events[type]) return this; + if (!util.isFunction(listener)) + throw TypeError('listener must be a function'); - var list = this._events[type]; + if (!this._events || !this._events[type]) + return this; - if (isArray(list)) { - var i = indexOf(list, listener); - if (i < 0) return this; - list.splice(i, 1); - if (list.length == 0) - delete this._events[type]; - } else if (this._events[type] === listener) { + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (util.isFunction(list.listener) && list.listener === listener)) { delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (util.isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); this._events = {}; return this; } - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; + listeners = this._events[type]; + + if (util.isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + return this; }; EventEmitter.prototype.listeners = function(type) { - if (!this._events) this._events = {}; - if (!this._events[type]) this._events[type] = []; - if (!isArray(this._events[type])) { - this._events[type] = [this._events[type]]; - } - return this._events[type]; + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (util.isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; }; EventEmitter.listenerCount = function(emitter, type) { var ret; if (!emitter._events || !emitter._events[type]) ret = 0; - else if (typeof emitter._events[type] === 'function') + else if (util.isFunction(emitter._events[type])) ret = 1; else ret = emitter._events[type].length; return ret; -}; +}; \ No newline at end of file diff --git a/test/browser/events-listeners.js b/test/browser/events-listeners.js new file mode 100644 index 0000000..82f14dd --- /dev/null +++ b/test/browser/events-listeners.js @@ -0,0 +1,48 @@ + +var test = require('tape'); + +var events = require('events'); + +function listener() {} +function listener2() {} + +test('listeners with only one item', function (t) { + var e1 = new events.EventEmitter(); + + e1.on('foo', listener); + var fooListeners = e1.listeners('foo'); + t.deepEqual(e1.listeners('foo'), [listener]); + + e1.removeAllListeners('foo'); + t.deepEqual(e1.listeners('foo'), []); + t.deepEqual(fooListeners, [listener]); + + t.end(); +}); + +test('listeners is a copy', function (t) { + var e2 = new events.EventEmitter(); + + e2.on('foo', listener); + var e2ListenersCopy = e2.listeners('foo'); + t.deepEqual(e2ListenersCopy, [listener]); + t.deepEqual(e2.listeners('foo'), [listener]); + + e2ListenersCopy.push(listener2); + t.deepEqual(e2.listeners('foo'), [listener]); + t.deepEqual(e2ListenersCopy, [listener, listener2]); + + t.end(); +}); + +test('listeners with two items', function (t) { + var e3 = new events.EventEmitter(); + + e3.on('foo', listener); + var e3ListenersCopy = e3.listeners('foo'); + e3.on('foo', listener2); + t.deepEqual(e3.listeners('foo'), [listener, listener2]); + t.deepEqual(e3ListenersCopy, [listener]); + + t.end(); +}); \ No newline at end of file diff --git a/test/browser/events-remove-listeners.js b/test/browser/events-remove-listeners.js new file mode 100644 index 0000000..7543529 --- /dev/null +++ b/test/browser/events-remove-listeners.js @@ -0,0 +1,93 @@ + +var test = require('tape'); + +var events = require('events'); + +var count = 0; + +function listener1() { + console.log('listener1'); + count++; +} + +function listener2() { + console.log('listener2'); + count++; +} + +function listener3() { + console.log('listener3'); + count++; +} + +test('removeListener emits and listeners array becomes empty', function (t) { + var e1 = new events.EventEmitter(); + + var emitted = false; + e1.on('hello', listener1); + e1.on('removeListener', function(name, cb) { + t.equal(name, 'hello'); + t.equal(cb, listener1); + emitted = true; + }); + + e1.removeListener('hello', listener1); + t.deepEqual([], e1.listeners('hello')); + + t.ok(emitted, 'removeListener did fire'); + t.end(); +}); + +test('removeing inactive handler dose nothing', function (t) { + var e2 = new events.EventEmitter(); + + var emitted = false; + e2.on('hello', listener1); + e2.on('removeListener', function () { + emitted = true; + }); + e2.removeListener('hello', listener2); + t.deepEqual([listener1], e2.listeners('hello')); + + t.ok(!emitted, 'removeListener did not fire'); + t.end(); +}); + +test('removeListener only removes one handler', function (t) { + var e3 = new events.EventEmitter(); + + var emitted = false; + e3.on('hello', listener1); + e3.on('hello', listener2); + e3.on('removeListener', function(name, cb) { + emitted = true; + t.equal(name, 'hello'); + t.equal(cb, listener1); + }); + e3.removeListener('hello', listener1); + t.deepEqual([listener2], e3.listeners('hello')); + + t.ok(emitted, 'removeListener did fired'); + t.end(); +}); + +test('regression: removing listener with in removeListener', function (t) { + var e4 = new events.EventEmitter(); + + function remove1() { t.ok(false); } + function remove2() { t.ok(false); } + + var fired = 0; + e4.on('removeListener', function(name, cb) { + fired += 1; + if (cb !== remove1) return; + this.removeListener('quux', remove2); + this.emit('quux'); + }); + e4.on('quux', remove1); + e4.on('quux', remove2); + e4.removeListener('quux', remove1); + + t.equal(fired, 2); + t.end(); +}); From 5fa8c3f463353c48c1c4f006fbeef817471a4689 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 12:43:55 +0200 Subject: [PATCH 10/47] [feature] update util and add tests This is failing because of a missing WIP _shims.js file --- builtin/util.js | 698 ++++++++++++++++++++++------------- test/browser/util-inspect.js | 22 ++ test/browser/util-is.js | 62 ++++ 3 files changed, 519 insertions(+), 263 deletions(-) create mode 100644 test/browser/util-inspect.js create mode 100644 test/browser/util-is.js diff --git a/builtin/util.js b/builtin/util.js index f460aa3..ea2ef9d 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -1,250 +1,461 @@ -var events = require('events'); -exports.isArray = isArray; -exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; -exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; - - -exports.print = function () {}; -exports.puts = function () {}; -exports.debug = function() {}; - -exports.inspect = function(obj, showHidden, depth, colors) { - var seen = []; - - var stylize = function(str, styleType) { - // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics - var styles = - { 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'white' : [37, 39], - 'grey' : [90, 39], - 'black' : [30, 39], - 'blue' : [34, 39], - 'cyan' : [36, 39], - 'green' : [32, 39], - 'magenta' : [35, 39], - 'red' : [31, 39], - 'yellow' : [33, 39] }; - - var style = - { 'special': 'cyan', - 'number': 'blue', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' }[styleType]; - - if (style) { - return '\u001b[' + styles[style][0] + 'm' + str + - '\u001b[' + styles[style][1] + 'm'; +var shims = require('./_shims.js'); + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; } else { - return str; + str += ' ' + inspect(x); } + } + return str; +}; + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor }; - if (! colors) { - stylize = function(str, styleType) { return str; }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); - function format(value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value !== exports && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - return value.inspect(recurseTimes); + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); } + return ret; + } - // Primitive types cannot have properties - switch (typeof value) { - case 'undefined': - return stylize('undefined', 'undefined'); + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } - case 'string': - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return stylize(simple, 'string'); + // Look up the keys of the object. + var keys = shims.keys(value); + var visibleKeys = arrayToHash(keys); - case 'number': - return stylize('' + value, 'number'); + if (ctx.showHidden) { + keys = shims.getOwnPropertyNames(value); + } - case 'boolean': - return stylize('' + value, 'boolean'); + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return stylize('null', 'null'); + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } - // Look up the keys of the object. - var visible_keys = Object_keys(value); - var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; + var base = '', array = false, braces = ['{', '}']; - // Functions without properties can be shortcutted. - if (typeof value === 'function' && keys.length === 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - var name = value.name ? ': ' + value.name : ''; - return stylize('[Function' + name + ']', 'special'); - } - } + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } - // Dates without properties can be shortcutted - if (isDate(value) && keys.length === 0) { - return stylize(value.toUTCString(), 'date'); - } + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } - var base, type, braces; - // Determine the object type - if (isArray(value)) { - type = 'Array'; - braces = ['[', ']']; + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } else { - type = 'Object'; - braces = ['{', '}']; + return ctx.stylize('[Object]', 'special'); } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } - // Make functions say that they are functions - if (typeof value === 'function') { - var n = value.name ? ': ' + value.name : ''; - base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); } else { - base = ''; + output.push(''); } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + value.toUTCString(); + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); } + }); + return output; +} - if (keys.length === 0) { - return braces[0] + base + braces[1]; - } - if (recurseTimes < 0) { - if (isRegExp(value)) { - return stylize('' + value, 'regexp'); - } else { - return stylize('[Object]', 'special'); - } +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); } - - seen.push(value); - - var output = keys.map(function(key) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = stylize('[Getter/Setter]', 'special'); - } else { - str = stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = stylize('[Setter]', 'special'); - } - } - } - if (visible_keys.indexOf(key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (seen.indexOf(value[key]) < 0) { - if (recurseTimes === null) { - str = format(value[key]); - } else { - str = format(value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (isArray(value)) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = stylize('[Circular]', 'special'); - } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); } - if (typeof name === 'undefined') { - if (type === 'Array' && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = stylize(name, 'name'); + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = stylize(name, 'string'); + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); } } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } - return name + ': ' + str; - }); + return name + ': ' + str; +} - seen.pop(); - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } - if (length > 50) { - output = braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} - } else { - output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - return output; - } - return format(obj, (typeof depth === 'undefined' ? 2 : depth)); -}; +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return shims.isArray(ar); +} +exports.isArray = isArray; +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; -function isArray(ar) { - return Array.isArray(ar) || - (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]'); +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; } +exports.isNullOrUndefined = isNullOrUndefined; +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; function isRegExp(re) { - typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'; + return isObject(re) && objectToString(re) === '[object RegExp]'; } +exports.isRegExp = isRegExp; +function isObject(arg) { + return typeof arg === 'object' && arg; +} +exports.isObject = isObject; function isDate(d) { - return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; + return isObject(d) && objectToString(d) === '[object Date]'; } +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && objectToString(e) === '[object Error]'; +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +function isBuffer(arg) { + return arg instanceof Buffer; +} +exports.isBuffer = isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); } + var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; @@ -257,50 +468,29 @@ function timestamp() { return [d.getDate(), months[d.getMonth()], time].join(' '); } -exports.log = function (msg) {}; - -exports.pump = null; -var Object_keys = Object.keys || function (obj) { - var res = []; - for (var key in obj) res.push(key); - return res; +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); }; -var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { - var res = []; - for (var key in obj) { - if (Object.hasOwnProperty.call(obj, key)) res.push(key); - } - return res; -}; - -var Object_create = Object.create || function (prototype, properties) { - // from es5-shim - var object; - if (prototype === null) { - object = { '__proto__' : null }; - } - else { - if (typeof prototype !== 'object') { - throw new TypeError( - 'typeof prototype[' + (typeof prototype) + '] != \'object\'' - ); - } - var Type = function () {}; - Type.prototype = prototype; - object = new Type(); - object.__proto__ = prototype; - } - if (typeof properties !== 'undefined' && Object.defineProperties) { - Object.defineProperties(object, properties); - } - return object; -}; +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ exports.inherits = function(ctor, superCtor) { ctor.super_ = superCtor; - ctor.prototype = Object_create(superCtor.prototype, { + ctor.prototype = shims.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, @@ -310,36 +500,18 @@ exports.inherits = function(ctor, superCtor) { }); }; -var formatRegExp = /%[sdj%]/g; -exports.format = function(f) { - if (typeof f !== 'string') { - var objects = []; - for (var i = 0; i < arguments.length; i++) { - objects.push(exports.inspect(arguments[i])); - } - return objects.join(' '); - } +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; - var i = 1; - var args = arguments; - var len = args.length; - var str = String(f).replace(formatRegExp, function(x) { - if (x === '%%') return '%'; - if (i >= len) return x; - switch (x) { - case '%s': return String(args[i++]); - case '%d': return Number(args[i++]); - case '%j': return JSON.stringify(args[i++]); - default: - return x; - } - }); - for(var x = args[i]; i < len; x = args[++i]){ - if (x === null || typeof x !== 'object') { - str += ' ' + x; - } else { - str += ' ' + exports.inspect(x); - } + var keys = shims.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; } - return str; + return origin; }; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} diff --git a/test/browser/util-inspect.js b/test/browser/util-inspect.js new file mode 100644 index 0000000..c063637 --- /dev/null +++ b/test/browser/util-inspect.js @@ -0,0 +1,22 @@ + +var test = require('tape'); + +var util = require('util'); + +test('util.inspect - test for sparse array', function (t) { + var a = ['foo', 'bar', 'baz']; + t.equal(util.inspect(a), '[ \'foo\', \'bar\', \'baz\' ]'); + delete a[1]; + t.equal(util.inspect(a), '[ \'foo\', , \'baz\' ]'); + t.equal(util.inspect(a, true), '[ \'foo\', , \'baz\', [length]: 3 ]'); + t.equal(util.inspect(new Array(5)), '[ , , , , ]'); + t.end(); +}); + +test('util.inspect - exceptions should print the error message, not \'{}\'', function (t) { + t.equal(util.inspect(new Error()), '[Error]'); + t.equal(util.inspect(new Error('FAIL')), '[Error: FAIL]'); + t.equal(util.inspect(new TypeError('FAIL')), '[TypeError: FAIL]'); + t.equal(util.inspect(new SyntaxError('FAIL')), '[SyntaxError: FAIL]'); + t.end(); +}); diff --git a/test/browser/util-is.js b/test/browser/util-is.js new file mode 100644 index 0000000..a136b99 --- /dev/null +++ b/test/browser/util-is.js @@ -0,0 +1,62 @@ + +var test = require('tape'); + +var util = require('util'); + +test('util.isArray', function (t) { + t.equal(true, util.isArray([])); + t.equal(true, util.isArray(Array())); + t.equal(true, util.isArray(new Array())); + t.equal(true, util.isArray(new Array(5))); + t.equal(true, util.isArray(new Array('with', 'some', 'entries'))); + t.equal(false, util.isArray({})); + t.equal(false, util.isArray({ push: function() {} })); + t.equal(false, util.isArray(/regexp/)); + t.equal(false, util.isArray(new Error())); + t.equal(false, util.isArray(Object.create(Array.prototype))); + t.end(); +}); + +test('util.isRegExp', function (t) { + t.equal(true, util.isRegExp(/regexp/)); + t.equal(true, util.isRegExp(RegExp())); + t.equal(true, util.isRegExp(new RegExp())); + t.equal(false, util.isRegExp({})); + t.equal(false, util.isRegExp([])); + t.equal(false, util.isRegExp(new Date())); + t.equal(false, util.isRegExp(Object.create(RegExp.prototype))); + t.end(); +}); + +test('util.isDate', function (t) { + t.equal(true, util.isDate(new Date())); + t.equal(true, util.isDate(new Date(0))); + t.equal(false, util.isDate(Date())); + t.equal(false, util.isDate({})); + t.equal(false, util.isDate([])); + t.equal(false, util.isDate(new Error())); + t.equal(false, util.isDate(Object.create(Date.prototype))); + t.end(); +}); + +test('util.isError', function (t) { + t.equal(true, util.isError(new Error())); + t.equal(true, util.isError(new TypeError())); + t.equal(true, util.isError(new SyntaxError())); + t.equal(false, util.isError({})); + t.equal(false, util.isError({ name: 'Error', message: '' })); + t.equal(false, util.isError([])); + t.equal(false, util.isError(Object.create(Error.prototype))); + t.end(); +}); + +test('util._extend', function (t) { + t.deepEqual(util._extend({a:1}), {a:1}); + t.deepEqual(util._extend({a:1}, []), {a:1}); + t.deepEqual(util._extend({a:1}, null), {a:1}); + t.deepEqual(util._extend({a:1}, true), {a:1}); + t.deepEqual(util._extend({a:1}, false), {a:1}); + t.deepEqual(util._extend({a:1}, {b:2}), {a:1, b:2}); + t.deepEqual(util._extend({a:1, b:2}, {b:3}), {a:1, b:3}); + t.end(); +}); From f155fe12fb8e938895c5deef62b5c66b56ac7daa Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 12:51:03 +0200 Subject: [PATCH 11/47] [cleanup] update assert and add deepEqual circular test --- builtin/assert.js | 91 ++++++++++++++--------------------- test/browser/assert-simple.js | 18 +++++++ 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/builtin/assert.js b/builtin/assert.js index 3007829..f546b0e 100644 --- a/builtin/assert.js +++ b/builtin/assert.js @@ -1,19 +1,8 @@ // UTILITY var util = require('util'); -var Buffer = require("buffer").Buffer; +var shims = require('./_shims.js'); var pSlice = Array.prototype.slice; -function objectKeys(object) { - if (Object.keys) return Object.keys(object); - var result = []; - for (var name in object) { - if (Object.prototype.hasOwnProperty.call(object, name)) { - result.push(name); - } - } - return result; -} - // 1. The assert module provides functions that throw // AssertionError's when particular conditions are not met. The // assert module must conform to the following interface. @@ -27,53 +16,41 @@ var assert = module.exports = ok; assert.AssertionError = function AssertionError(options) { this.name = 'AssertionError'; - this.message = options.message; this.actual = options.actual; this.expected = options.expected; this.operator = options.operator; - var stackStartFunction = options.stackStartFunction || fail; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, stackStartFunction); - } + this.message = options.message || getMessage(this); }; // assert.AssertionError instanceof Error util.inherits(assert.AssertionError, Error); function replacer(key, value) { - if (value === undefined) { + if (util.isUndefined(value)) { return '' + value; } - if (typeof value === 'number' && (isNaN(value) || !isFinite(value))) { + if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { return value.toString(); } - if (typeof value === 'function' || value instanceof RegExp) { + if (util.isFunction(value) || util.isRegExp(value)) { return value.toString(); } return value; } function truncate(s, n) { - if (typeof s == 'string') { + if (util.isString(s)) { return s.length < n ? s : s.slice(0, n); } else { return s; } } -assert.AssertionError.prototype.toString = function() { - if (this.message) { - return [this.name + ':', this.message].join(' '); - } else { - return [ - this.name + ':', - truncate(JSON.stringify(this.actual, replacer), 128), - this.operator, - truncate(JSON.stringify(this.expected, replacer), 128) - ].join(' '); - } -}; +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} // At present only the three keys mentioned above are used and // understood by the spec. Implementations or sub modules can pass @@ -102,12 +79,12 @@ assert.fail = fail; // 4. Pure assertion tests whether a value is truthy, as determined // by !!guard. // assert.ok(guard, message_opt); -// This statement is equivalent to assert.equal(true, guard, +// This statement is equivalent to assert.equal(true, !!guard, // message_opt);. To test strictly for the value true, use // assert.strictEqual(true, guard, message_opt);. function ok(value, message) { - if (!!!value) fail(value, true, message, '==', assert.ok); + if (!value) fail(value, true, message, '==', assert.ok); } assert.ok = ok; @@ -142,7 +119,7 @@ function _deepEqual(actual, expected) { if (actual === expected) { return true; - } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) { @@ -153,15 +130,25 @@ function _deepEqual(actual, expected) { // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. - } else if (actual instanceof Date && expected instanceof Date) { + } else if (util.isDate(actual) && util.isDate(expected)) { return actual.getTime() === expected.getTime(); - // 7.3. Other pairs that do not both pass typeof value == 'object', + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', // equivalence is determined by ==. - } else if (typeof actual != 'object' && typeof expected != 'object') { + } else if (!util.isObject(actual) && !util.isObject(expected)) { return actual == expected; - // 7.4. For all other Object pairs, including Array objects, equivalence is + // 7.5 For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every @@ -172,16 +159,12 @@ function _deepEqual(actual, expected) { } } -function isUndefinedOrNull(value) { - return value === null || value === undefined; -} - function isArguments(object) { return Object.prototype.toString.call(object) == '[object Arguments]'; } function objEquiv(a, b) { - if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) return false; // an identical 'prototype' property. if (a.prototype !== b.prototype) return false; @@ -196,8 +179,8 @@ function objEquiv(a, b) { return _deepEqual(a, b); } try { - var ka = objectKeys(a), - kb = objectKeys(b), + var ka = shims.keys(a), + kb = shims.keys(b), key, i; } catch (e) {//happens when one is a string literal and the other isn't return false; @@ -255,7 +238,7 @@ function expectedException(actual, expected) { return false; } - if (expected instanceof RegExp) { + if (Object.prototype.toString.call(expected) == '[object RegExp]') { return expected.test(actual); } else if (actual instanceof expected) { return true; @@ -269,7 +252,7 @@ function expectedException(actual, expected) { function _throws(shouldThrow, block, expected, message) { var actual; - if (typeof expected === 'string') { + if (util.isString(expected)) { message = expected; expected = null; } @@ -284,11 +267,11 @@ function _throws(shouldThrow, block, expected, message) { (message ? ' ' + message : '.'); if (shouldThrow && !actual) { - fail('Missing expected exception' + message); + fail(actual, expected, 'Missing expected exception' + message); } if (!shouldThrow && expectedException(actual, expected)) { - fail('Got unwanted exception' + message); + fail(actual, expected, 'Got unwanted exception' + message); } if ((shouldThrow && actual && expected && @@ -305,8 +288,8 @@ assert.throws = function(block, /*optional*/error, /*optional*/message) { }; // EXTENSION! This is annoying to write outside this module. -assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { +assert.doesNotThrow = function(block, /*optional*/message) { _throws.apply(this, [false].concat(pSlice.call(arguments))); }; -assert.ifError = function(err) { if (err) {throw err;}}; +assert.ifError = function(err) { if (err) {throw err;}}; \ No newline at end of file diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js index 0cf7bec..5e26233 100644 --- a/test/browser/assert-simple.js +++ b/test/browser/assert-simple.js @@ -27,3 +27,21 @@ test('assert.ok - pass', function (t) { } t.end(); }); + +test('assert.deepEqual Make sure deepEqual doesn\'t loop forever on circular refs', function (t) { + var b = {}; + b.b = b; + + var c = {}; + c.b = c; + + var gotError = false; + try { + assert.deepEqual(b, c); + } catch (e) { + gotError = true; + } + + t.ok(gotError); + t.end(); +}); From 4009189f2fa7a95786d32cbe0d75c85a961eaa63 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 12:54:21 +0200 Subject: [PATCH 12/47] [feature] fs now throw when required --- builtin/fs.js | 3 ++- test/browser/fs-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/browser/fs-simple.js diff --git a/builtin/fs.js b/builtin/fs.js index 5c9c044..d149fa3 100644 --- a/builtin/fs.js +++ b/builtin/fs.js @@ -1 +1,2 @@ -// nothing to see here... no file methods for the browser + +throw new Error('fs is not implemented'); diff --git a/test/browser/fs-simple.js b/test/browser/fs-simple.js new file mode 100644 index 0000000..7bf3a25 --- /dev/null +++ b/test/browser/fs-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require fs should throw', function (t) { + try { + require('fs'); + t.ok(false, 'fs did not throw'); + } catch (e) { + t.ok(true, 'fs did throw'); + } + t.end(); +}); From a4280302c9fc71125f2597f92a8ca857412901ad Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 12:58:21 +0200 Subject: [PATCH 13/47] [feature] net now throw when required --- builtin/net.js | 3 ++- test/browser/net-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/browser/net-simple.js diff --git a/builtin/net.js b/builtin/net.js index 65b3dba..21ab9ee 100644 --- a/builtin/net.js +++ b/builtin/net.js @@ -1 +1,2 @@ -// todo + +throw new Error('net is not implemented'); diff --git a/test/browser/net-simple.js b/test/browser/net-simple.js new file mode 100644 index 0000000..f077e99 --- /dev/null +++ b/test/browser/net-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require net should throw', function (t) { + try { + require('net'); + t.ok(false, 'net did not throw'); + } catch (e) { + t.ok(true, 'net did throw'); + } + t.end(); +}); From 254a6e4ac86770c38e2a9aec245814f09bb0fd8d Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 13:21:32 +0200 Subject: [PATCH 14/47] [cleanup] update path and add tests --- builtin/path.js | 146 ++++++++++++----------- test/browser/path-simple.js | 223 ++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 67 deletions(-) create mode 100644 test/browser/path-simple.js diff --git a/builtin/path.js b/builtin/path.js index 14b960a..aadb390 100644 --- a/builtin/path.js +++ b/builtin/path.js @@ -1,10 +1,6 @@ -function filter (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - if (fn(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} + +var util = require('util'); +var shims = require('./_shims.js'); // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array @@ -13,9 +9,9 @@ function filter (xs, fn) { function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; - for (var i = parts.length; i >= 0; i--) { + for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; - if (last == '.') { + if (last === '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); @@ -36,35 +32,39 @@ function normalizeArray(parts, allowAboveRoot) { return parts; } -// Regex to split a filename into [*, dir, basename, ext] -// posix version -var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; // path.resolve([from ...], to) // posix version exports.resolve = function() { -var resolvedPath = '', - resolvedAbsolute = false; + var resolvedPath = '', + resolvedAbsolute = false; -for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) - ? arguments[i] - : process.cwd(); + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); - // Skip empty and invalid entries - if (typeof path !== 'string' || !path) { - continue; - } + // Skip empty and invalid entries + if (!util.isString(path)) { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; -} + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } -// At this point the path should be resolved to a full absolute path, but -// handle relative paths to be safe (might happen when process.cwd() fails) + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) -// Normalize the path -resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + // Normalize the path + resolvedPath = normalizeArray(shims.filter(resolvedPath.split('/'), function(p) { return !!p; }), !resolvedAbsolute).join('/'); @@ -74,11 +74,11 @@ resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { // path.normalize(path) // posix version exports.normalize = function(path) { -var isAbsolute = path.charAt(0) === '/', - trailingSlash = path.slice(-1) === '/'; + var isAbsolute = exports.isAbsolute(path), + trailingSlash = path.substr(-1) === '/'; -// Normalize the path -path = normalizeArray(filter(path.split('/'), function(p) { + // Normalize the path + path = normalizeArray(shims.filter(path.split('/'), function(p) { return !!p; }), !isAbsolute).join('/'); @@ -88,51 +88,29 @@ path = normalizeArray(filter(path.split('/'), function(p) { if (path && trailingSlash) { path += '/'; } - + return (isAbsolute ? '/' : '') + path; }; +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; // posix version exports.join = function() { var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - return p && typeof p === 'string'; + return exports.normalize(shims.filter(paths, function(p, index) { + if (!util.isString(p)) { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; }).join('/')); }; -exports.dirname = function(path) { - var dir = splitPathRe.exec(path)[1] || ''; - var isWindows = false; - if (!dir) { - // No dirname - return '.'; - } else if (dir.length === 1 || - (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { - // It is just a slash or a drive letter with a slash - return dir; - } else { - // It is a full dirname, strip trailing slash - return dir.substring(0, dir.length - 1); - } -}; - - -exports.basename = function(path, ext) { - var f = splitPathRe.exec(path)[2] || ''; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPathRe.exec(path)[3] || ''; -}; - +// path.relative(from, to) +// posix version exports.relative = function(from, to) { from = exports.resolve(from).substr(1); to = exports.resolve(to).substr(1); @@ -175,3 +153,37 @@ exports.relative = function(from, to) { }; exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; diff --git a/test/browser/path-simple.js b/test/browser/path-simple.js new file mode 100644 index 0000000..1763fb4 --- /dev/null +++ b/test/browser/path-simple.js @@ -0,0 +1,223 @@ + +var test = require('tape'); + +var path = require('path'); + +test('path.basename', function (t) { + t.equal(path.basename(''), ''); + t.equal(path.basename('/dir/basename.ext'), 'basename.ext'); + t.equal(path.basename('/basename.ext'), 'basename.ext'); + t.equal(path.basename('basename.ext'), 'basename.ext'); + t.equal(path.basename('basename.ext/'), 'basename.ext'); + t.equal(path.basename('basename.ext//'), 'basename.ext'); + + t.equal(path.basename('\\dir\\basename.ext'), '\\dir\\basename.ext'); + t.equal(path.basename('\\basename.ext'), '\\basename.ext'); + t.equal(path.basename('basename.ext'), 'basename.ext'); + t.equal(path.basename('basename.ext\\'), 'basename.ext\\'); + t.equal(path.basename('basename.ext\\\\'), 'basename.ext\\\\'); + + t.end(); +}); + +test('path.extname', function (t) { + t.equal(path.dirname('/a/b/'), '/a'); + t.equal(path.dirname('/a/b'), '/a'); + t.equal(path.dirname('/a'), '/'); + t.equal(path.dirname(''), '.'); + t.equal(path.dirname('/'), '/'); + t.equal(path.dirname('////'), '/'); + + t.equal(path.extname(''), ''); + t.equal(path.extname('/path/to/file'), ''); + t.equal(path.extname('/path/to/file.ext'), '.ext'); + t.equal(path.extname('/path.to/file.ext'), '.ext'); + t.equal(path.extname('/path.to/file'), ''); + t.equal(path.extname('/path.to/.file'), ''); + t.equal(path.extname('/path.to/.file.ext'), '.ext'); + t.equal(path.extname('/path/to/f.ext'), '.ext'); + t.equal(path.extname('/path/to/..ext'), '.ext'); + t.equal(path.extname('file'), ''); + t.equal(path.extname('file.ext'), '.ext'); + t.equal(path.extname('.file'), ''); + t.equal(path.extname('.file.ext'), '.ext'); + t.equal(path.extname('/file'), ''); + t.equal(path.extname('/file.ext'), '.ext'); + t.equal(path.extname('/.file'), ''); + t.equal(path.extname('/.file.ext'), '.ext'); + t.equal(path.extname('.path/file.ext'), '.ext'); + t.equal(path.extname('file.ext.ext'), '.ext'); + t.equal(path.extname('file.'), '.'); + t.equal(path.extname('.'), ''); + t.equal(path.extname('./'), ''); + t.equal(path.extname('.file.ext'), '.ext'); + t.equal(path.extname('.file'), ''); + t.equal(path.extname('.file.'), '.'); + t.equal(path.extname('.file..'), '.'); + t.equal(path.extname('..'), ''); + t.equal(path.extname('../'), ''); + t.equal(path.extname('..file.ext'), '.ext'); + t.equal(path.extname('..file'), '.file'); + t.equal(path.extname('..file.'), '.'); + t.equal(path.extname('..file..'), '.'); + t.equal(path.extname('...'), '.'); + t.equal(path.extname('...ext'), '.ext'); + t.equal(path.extname('....'), '.'); + t.equal(path.extname('file.ext/'), '.ext'); + t.equal(path.extname('file.ext//'), '.ext'); + t.equal(path.extname('file/'), ''); + t.equal(path.extname('file//'), ''); + t.equal(path.extname('file./'), '.'); + t.equal(path.extname('file.//'), '.'); + + t.equal(path.extname('.\\'), ''); + t.equal(path.extname('..\\'), '.\\'); + t.equal(path.extname('file.ext\\'), '.ext\\'); + t.equal(path.extname('file.ext\\\\'), '.ext\\\\'); + t.equal(path.extname('file\\'), ''); + t.equal(path.extname('file\\\\'), ''); + t.equal(path.extname('file.\\'), '.\\'); + t.equal(path.extname('file.\\\\'), '.\\\\'); + + t.end(); +}); + +test('path.join', function (t) { + var joinTests = + // arguments result + [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], + [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], + [['/foo', '../../../bar'], '/bar'], + [['foo', '../../../bar'], '../../bar'], + [['foo/', '../../../bar'], '../../bar'], + [['foo/x', '../../../bar'], '../bar'], + [['foo/x', './bar'], 'foo/x/bar'], + [['foo/x/', './bar'], 'foo/x/bar'], + [['foo/x/', '.', 'bar'], 'foo/x/bar'], + [['./'], './'], + [['.', './'], './'], + [['.', '.', '.'], '.'], + [['.', './', '.'], '.'], + [['.', '/./', '.'], '.'], + [['.', '/////./', '.'], '.'], + [['.'], '.'], + [['', '.'], '.'], + [['', 'foo'], 'foo'], + [['foo', '/bar'], 'foo/bar'], + [['', '/foo'], '/foo'], + [['', '', '/foo'], '/foo'], + [['', '', 'foo'], 'foo'], + [['foo', ''], 'foo'], + [['foo/', ''], 'foo/'], + [['foo', '', '/bar'], 'foo/bar'], + [['./', '..', '/foo'], '../foo'], + [['./', '..', '..', '/foo'], '../../foo'], + [['.', '..', '..', '/foo'], '../../foo'], + [['', '..', '..', '/foo'], '../../foo'], + [['/'], '/'], + [['/', '.'], '/'], + [['/', '..'], '/'], + [['/', '..', '..'], '/'], + [[''], '.'], + [['', ''], '.'], + [[' /foo'], ' /foo'], + [[' ', 'foo'], ' /foo'], + [[' ', '.'], ' '], + [[' ', '/'], ' /'], + [[' ', ''], ' '], + [['/', 'foo'], '/foo'], + [['/', '/foo'], '/foo'], + [['/', '//foo'], '/foo'], + [['/', '', '/foo'], '/foo'], + [['', '/', 'foo'], '/foo'], + [['', '/', '/foo'], '/foo'] + ]; + + // Run the join tests. + joinTests.forEach(function(test) { + var actual = path.join.apply(path, test[0]); + var expected = test[1]; + t.equal(actual, expected); + }); + + var joinThrowTests = [true, false, 7, null, {}, undefined, [], NaN]; + joinThrowTests.forEach(function(test) { + t.throws(function() { + path.join(test); + }, TypeError); + t.throws(function() { + path.resolve(test); + }, TypeError); + }); + + t.end(); +}); + +test('path.normalize', function (t) { + t.equal(path.normalize('./fixtures///b/../b/c.js'), + 'fixtures/b/c.js'); + t.equal(path.normalize('/foo/../../../bar'), '/bar'); + t.equal(path.normalize('a//b//../b'), 'a/b'); + t.equal(path.normalize('a//b//./c'), 'a/b/c'); + t.equal(path.normalize('a//b//.'), 'a/b'); + + t.end(); +}); + +test('path.resolve', function (t) { + var resolveTests = + // arguments result + [[['/var/lib', '../', 'file/'], '/var/file'], + [['/var/lib', '/../', 'file/'], '/file'], + [['a/b/c/', '../../..'], process.cwd()], + [['.'], process.cwd()], + [['/some/dir', '.', '/absolute/'], '/absolute']]; + + resolveTests.forEach(function(test) { + var actual = path.resolve.apply(path, test[0]); + var expected = test[1]; + t.equal(actual, expected); + }); + + t.end(); +}); + +test('path.isAbsolute', function (t) { + t.equal(path.isAbsolute('/home/foo'), true); + t.equal(path.isAbsolute('/home/foo/..'), true); + t.equal(path.isAbsolute('bar/'), false); + t.equal(path.isAbsolute('./baz'), false); + + t.end(); +}); + +test('path.relative', function (t) { + var relativeTests = + // arguments result + [['/var/lib', '/var', '..'], + ['/var/lib', '/bin', '../../bin'], + ['/var/lib', '/var/lib', ''], + ['/var/lib', '/var/apache', '../apache'], + ['/var/', '/var/lib', 'lib'], + ['/', '/var/lib', 'var/lib']]; + + relativeTests.forEach(function(test) { + var actual = path.relative(test[0], test[1]); + var expected = test[2]; + t.equal(actual, expected); + }); + + t.end(); +}); + +test('path.sep', function (t) { + t.equal(path.sep, '/'); + + t.end(); +}); + +test('path.delimiter', function (t) { + t.equal(path.delimiter, ':'); + + t.end(); +}); From 3e7e72c96fb45234db377fffa5e0e29fff4b0217 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 13:25:06 +0200 Subject: [PATCH 15/47] [feature] domain now throw when required --- builtin/domain.js | 2 ++ test/browser/domain-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 builtin/domain.js create mode 100644 test/browser/domain-simple.js diff --git a/builtin/domain.js b/builtin/domain.js new file mode 100644 index 0000000..70081ef --- /dev/null +++ b/builtin/domain.js @@ -0,0 +1,2 @@ + +throw new Error('domain is not implemented'); diff --git a/test/browser/domain-simple.js b/test/browser/domain-simple.js new file mode 100644 index 0000000..89bb271 --- /dev/null +++ b/test/browser/domain-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require domain should throw', function (t) { + try { + require('domain'); + t.ok(false, 'domain did not throw'); + } catch (e) { + t.ok(true, 'domain did throw'); + } + t.end(); +}); From 76f1d463a1fcdb918621cce54b30e0ead57d0a12 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 13:30:42 +0200 Subject: [PATCH 16/47] [feature] add os-browserify dependency --- index.js | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 9bf3864..871cc53 100644 --- a/index.js +++ b/index.js @@ -21,5 +21,6 @@ core['console'] = require.resolve('console-browserify'); core['zlib'] = require.resolve('zlib-browserify'); core['buffer'] = require.resolve('buffer-browserify'); core['constants'] = require.resolve('constants-browserify'); +core['os'] = path.resolve(require.resolve('os-browserify'), '..', 'browser.js'); module.exports = core; diff --git a/package.json b/package.json index aa65de2..383c37f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "http-browserify": "0.1.x", "buffer-browserify": "0.1.x", "zlib-browserify": "0.0.x", - "constants-browserify": "0.0.x" + "constants-browserify": "0.0.x", + "os-browserify": "0.1.0" }, "devDependencies": { "tape": "1.0.x" From e6ae663de1efb87d232893903efaf7c5ac5c03d4 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 15:13:00 +0200 Subject: [PATCH 17/47] [feature] update querystring and add tests Note this requires the PR-23 in buffer-browserify fix --- builtin/querystring.js | 430 ++++++++++------------------- test/browser/querystring-simple.js | 234 ++++++++++++++++ 2 files changed, 385 insertions(+), 279 deletions(-) create mode 100644 test/browser/querystring-simple.js diff --git a/builtin/querystring.js b/builtin/querystring.js index aff8065..9901892 100644 --- a/builtin/querystring.js +++ b/builtin/querystring.js @@ -1,317 +1,189 @@ +// Query String Utilities + +var QueryString = exports; +var util = require('util'); +var shims = require('./_shims.js'); +var Buffer = require('buffer').Buffer; + +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} -/** - * Object#toString() ref for stringify(). - */ -var toString = Object.prototype.toString; +function charCode(c) { + return c.charCodeAt(0); +} -/** - * Array#indexOf shim. - */ -var indexOf = typeof Array.prototype.indexOf === 'function' - ? function(arr, el) { return arr.indexOf(el); } - : function(arr, el) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] === el) return i; - } - return -1; - }; +// a safe fast alternative to decodeURIComponent +QueryString.unescapeBuffer = function(s, decodeSpaces) { + var out = new Buffer(s.length); + var state = 'CHAR'; // states: CHAR, HEX0, HEX1 + var n, m, hexchar; + + for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) { + var c = s.charCodeAt(inIndex); + switch (state) { + case 'CHAR': + switch (c) { + case charCode('%'): + n = 0; + m = 0; + state = 'HEX0'; + break; + case charCode('+'): + if (decodeSpaces) c = charCode(' '); + // pass thru + default: + out[outIndex++] = c; + break; + } + break; + + case 'HEX0': + state = 'HEX1'; + hexchar = c; + if (charCode('0') <= c && c <= charCode('9')) { + n = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + n = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + n = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = c; + state = 'CHAR'; + break; + } + break; + + case 'HEX1': + state = 'CHAR'; + if (charCode('0') <= c && c <= charCode('9')) { + m = c - charCode('0'); + } else if (charCode('a') <= c && c <= charCode('f')) { + m = c - charCode('a') + 10; + } else if (charCode('A') <= c && c <= charCode('F')) { + m = c - charCode('A') + 10; + } else { + out[outIndex++] = charCode('%'); + out[outIndex++] = hexchar; + out[outIndex++] = c; + break; + } + out[outIndex++] = 16 * n + m; + break; + } + } -/** - * Array.isArray shim. - */ + // TODO support returning arbitrary buffers. -var isArray = Array.isArray || function(arr) { - return toString.call(arr) == '[object Array]'; + return out.slice(0, outIndex - 1); }; -/** - * Object.keys shim. - */ -var objectKeys = Object.keys || function(obj) { - var ret = []; - for (var key in obj) ret.push(key); - return ret; +QueryString.unescape = function(s, decodeSpaces) { + return QueryString.unescapeBuffer(s, decodeSpaces).toString(); }; -/** - * Array#forEach shim. - */ - -var forEach = typeof Array.prototype.forEach === 'function' - ? function(arr, fn) { return arr.forEach(fn); } - : function(arr, fn) { - for (var i = 0; i < arr.length; i++) fn(arr[i]); - }; - -/** - * Array#reduce shim. - */ -var reduce = function(arr, fn, initial) { - if (typeof arr.reduce === 'function') return arr.reduce(fn, initial); - var res = initial; - for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]); - return res; +QueryString.escape = function(str) { + return encodeURIComponent(str); }; -/** - * Cache non-integer test regexp. - */ +var stringifyPrimitive = function(v) { + if (util.isString(v)) + return v; + if (util.isBoolean(v)) + return v ? 'true' : 'false'; + if (util.isNumber(v)) + return isFinite(v) ? v : ''; + return ''; +}; -var isint = /^[0-9]+$/; -function promote(parent, key) { - if (parent[key].length == 0) return parent[key] = {}; - var t = {}; - for (var i in parent[key]) t[i] = parent[key][i]; - parent[key] = t; - return t; -} +QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (util.isNull(obj)) { + obj = undefined; + } -function parse(parts, parent, key, val) { - var part = parts.shift(); - // end - if (!part) { - if (isArray(parent[key])) { - parent[key].push(val); - } else if ('object' == typeof parent[key]) { - parent[key] = val; - } else if ('undefined' == typeof parent[key]) { - parent[key] = val; - } else { - parent[key] = [parent[key], val]; - } - // array - } else { - var obj = parent[key] = parent[key] || []; - if (']' == part) { - if (isArray(obj)) { - if ('' != val) obj.push(val); - } else if ('object' == typeof obj) { - obj[objectKeys(obj).length] = val; + if (util.isObject(obj)) { + return shims.keys(obj).map(function(k) { + var ks = QueryString.escape(stringifyPrimitive(k)) + eq; + if (util.isArray(obj[k])) { + return obj[k].map(function(v) { + return ks + QueryString.escape(stringifyPrimitive(v)); + }).join(sep); } else { - obj = parent[key] = [parent[key], val]; + return ks + QueryString.escape(stringifyPrimitive(obj[k])); } - // prop - } else if (~indexOf(part, ']')) { - part = part.substr(0, part.length - 1); - if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); - // key - } else { - if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); - } - } -} - -/** - * Merge parent key/val pair. - */ + }).join(sep); -function merge(parent, key, val){ - if (~indexOf(key, ']')) { - var parts = key.split('[') - , len = parts.length - , last = len - 1; - parse(parts, parent, 'base', val); - // optimize - } else { - if (!isint.test(key) && isArray(parent.base)) { - var t = {}; - for (var k in parent.base) t[k] = parent.base[k]; - parent.base = t; - } - set(parent.base, key, val); } - return parent; -} - -/** - * Parse the given obj. - */ - -function parseObject(obj){ - var ret = { base: {} }; - forEach(objectKeys(obj), function(name){ - merge(ret, name, obj[name]); - }); - return ret.base; -} - -/** - * Parse the given str. - */ - -function parseString(str){ - return reduce(String(str).split('&'), function(ret, pair){ - var eql = indexOf(pair, '=') - , brace = lastBraceInKey(pair) - , key = pair.substr(0, brace || eql) - , val = pair.substr(brace || eql, pair.length) - , val = val.substr(indexOf(val, '=') + 1, val.length); - - // ?foo - if ('' == key) key = pair, val = ''; - if ('' == key) return ret; - - return merge(ret, decode(key), decode(val)); - }, { base: {} }).base; -} - -/** - * Parse the given query `str` or `obj`, returning an object. - * - * @param {String} str | {Object} obj - * @return {Object} - * @api public - */ - -exports.parse = function(str){ - if (null == str || '' == str) return {}; - return 'object' == typeof str - ? parseObject(str) - : parseString(str); + if (!name) return ''; + return QueryString.escape(stringifyPrimitive(name)) + eq + + QueryString.escape(stringifyPrimitive(obj)); }; -/** - * Turn the given `obj` into a query string - * - * @param {Object} obj - * @return {String} - * @api public - */ +// Parse a key=val string. +QueryString.parse = QueryString.decode = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; -var stringify = exports.stringify = function(obj, prefix) { - if (isArray(obj)) { - return stringifyArray(obj, prefix); - } else if ('[object Object]' == toString.call(obj)) { - return stringifyObject(obj, prefix); - } else if ('string' == typeof obj) { - return stringifyString(obj, prefix); - } else { - return prefix + '=' + encodeURIComponent(String(obj)); + if (!util.isString(qs) || qs.length === 0) { + return obj; } -}; - -/** - * Stringify the given `str`. - * - * @param {String} str - * @param {String} prefix - * @return {String} - * @api private - */ - -function stringifyString(str, prefix) { - if (!prefix) throw new TypeError('stringify expects an object'); - return prefix + '=' + encodeURIComponent(str); -} -/** - * Stringify the given `arr`. - * - * @param {Array} arr - * @param {String} prefix - * @return {String} - * @api private - */ + var regexp = /\+/g; + qs = qs.split(sep); -function stringifyArray(arr, prefix) { - var ret = []; - if (!prefix) throw new TypeError('stringify expects an object'); - for (var i = 0; i < arr.length; i++) { - ret.push(stringify(arr[i], prefix + '[' + i + ']')); + var maxKeys = 1000; + if (options && util.isNumber(options.maxKeys)) { + maxKeys = options.maxKeys; } - return ret.join('&'); -} -/** - * Stringify the given `obj`. - * - * @param {Object} obj - * @param {String} prefix - * @return {String} - * @api private - */ + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } -function stringifyObject(obj, prefix) { - var ret = [] - , keys = objectKeys(obj) - , key; + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; - for (var i = 0, len = keys.length; i < len; ++i) { - key = keys[i]; - if (null == obj[key]) { - ret.push(encodeURIComponent(key) + '='); + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); } else { - ret.push(stringify(obj[key], prefix - ? prefix + '[' + encodeURIComponent(key) + ']' - : encodeURIComponent(key))); + kstr = x; + vstr = ''; } - } - - return ret.join('&'); -} -/** - * Set `obj`'s `key` to `val` respecting - * the weird and wonderful syntax of a qs, - * where "foo=bar&foo=baz" becomes an array. - * - * @param {Object} obj - * @param {String} key - * @param {String} val - * @api private - */ - -function set(obj, key, val) { - var v = obj[key]; - if (undefined === v) { - obj[key] = val; - } else if (isArray(v)) { - v.push(val); - } else { - obj[key] = [v, val]; - } -} - -/** - * Locate last brace in `str` within the key. - * - * @param {String} str - * @return {Number} - * @api private - */ + try { + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + } catch (e) { + k = QueryString.unescape(kstr, true); + v = QueryString.unescape(vstr, true); + } -function lastBraceInKey(str) { - var len = str.length - , brace - , c; - for (var i = 0; i < len; ++i) { - c = str[i]; - if (']' == c) brace = false; - if ('[' == c) brace = true; - if ('=' == c && !brace) return i; + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (util.isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } } -} -/** - * Decode `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -function decode(str) { - try { - return decodeURIComponent(str.replace(/\+/g, ' ')); - } catch (err) { - return str; - } -} + return obj; +}; \ No newline at end of file diff --git a/test/browser/querystring-simple.js b/test/browser/querystring-simple.js new file mode 100644 index 0000000..f30cc7e --- /dev/null +++ b/test/browser/querystring-simple.js @@ -0,0 +1,234 @@ + +var test = require('tape'); + +var qs = require('querystring'); + +// folding block, commented to pass gjslint +// {{{ +// [ wonkyQS, canonicalQS, obj ] +var qsTestCases = [ + ['foo=918854443121279438895193', + 'foo=918854443121279438895193', + {'foo': '918854443121279438895193'}], + ['foo=bar', 'foo=bar', {'foo': 'bar'}], + ['foo=bar&foo=quux', 'foo=bar&foo=quux', {'foo': ['bar', 'quux']}], + ['foo=1&bar=2', 'foo=1&bar=2', {'foo': '1', 'bar': '2'}], + ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', + 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', + {'my weird field': 'q1!2"\'w$5&7/z8)?' }], + ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', {'foo=baz': 'bar'}], + ['foo=baz=bar', 'foo=baz%3Dbar', {'foo': 'baz=bar'}], + ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + { 'str': 'foo', + 'arr': ['1', '2', '3'], + 'somenull': '', + 'undef': ''}], + [' foo = bar ', '%20foo%20=%20bar%20', {' foo ': ' bar '}], + ['foo=%zx', 'foo=%25zx', {'foo': '%zx'}], + ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', {'foo': '\ufffd' }], + // See: https://github.com/joyent/node/issues/1707 + ['hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', + 'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', + { hasOwnProperty: 'x', + toString: 'foo', + valueOf: 'bar', + __defineGetter__: 'baz' }], + // See: https://github.com/joyent/node/issues/3058 + ['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }] +]; + +// [ wonkyQS, canonicalQS, obj ] +var qsColonTestCases = [ + ['foo:bar', 'foo:bar', {'foo': 'bar'}], + ['foo:bar;foo:quux', 'foo:bar;foo:quux', {'foo': ['bar', 'quux']}], + ['foo:1&bar:2;baz:quux', + 'foo:1%26bar%3A2;baz:quux', + {'foo': '1&bar:2', 'baz': 'quux'}], + ['foo%3Abaz:bar', 'foo%3Abaz:bar', {'foo:baz': 'bar'}], + ['foo:baz:bar', 'foo:baz%3Abar', {'foo': 'baz:bar'}] +]; + +// [wonkyObj, qs, canonicalObj] +var extendedFunction = function() {}; +extendedFunction.prototype = {a: 'b'}; +var qsWeirdObjects = [ + [{regexp: /./g}, 'regexp=', {'regexp': ''}], + [{regexp: new RegExp('.', 'g')}, 'regexp=', {'regexp': ''}], + [{fn: function() {}}, 'fn=', {'fn': ''}], + [{fn: new Function('')}, 'fn=', {'fn': ''}], + [{math: Math}, 'math=', {'math': ''}], + [{e: extendedFunction}, 'e=', {'e': ''}], + [{d: new Date()}, 'd=', {'d': ''}], + [{d: Date}, 'd=', {'d': ''}], + [{f: new Boolean(false), t: new Boolean(true)}, 'f=&t=', {'f': '', 't': ''}], + [{f: false, t: true}, 'f=false&t=true', {'f': 'false', 't': 'true'}], + [{n: null}, 'n=', {'n': ''}], + [{nan: NaN}, 'nan=', {'nan': ''}], + [{inf: Infinity}, 'inf=', {'inf': ''}] +]; +// }}} + +var qsNoMungeTestCases = [ + ['', {}], + ['foo=bar&foo=baz', {'foo': ['bar', 'baz']}], + ['blah=burp', {'blah': 'burp'}], + ['gragh=1&gragh=3&goo=2', {'gragh': ['1', '3'], 'goo': '2'}], + ['frappucino=muffin&goat%5B%5D=scone&pond=moose', + {'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose'}], + ['trololol=yes&lololo=no', {'trololol': 'yes', 'lololo': 'no'}] +]; + +test('qs.parse - simple example', function (t) { + t.strictEqual('918854443121279438895193', + qs.parse('id=918854443121279438895193').id); + t.end(); +}); + +test('qs.parse - test that the canonical qs is parsed properly', function (t) { + qsTestCases.forEach(function(testCase) { + t.deepEqual(testCase[2], qs.parse(testCase[0])); + }); + t.end(); +}); + +test('qs.parse - test that the colon test cases can do the same', function (t) { + qsColonTestCases.forEach(function(testCase) { + t.deepEqual(testCase[2], qs.parse(testCase[0], ';', ':')); + }); + t.end(); +}); + +test('qs.parse - test the weird objects, that they get parsed properly', function (t) { + qsWeirdObjects.forEach(function(testCase) { + t.deepEqual(testCase[2], qs.parse(testCase[1])); + }); + t.end(); +}); + +test('qs.stringify - simple example', function (t) { + qsNoMungeTestCases.forEach(function(testCase) { + t.deepEqual(testCase[0], qs.stringify(testCase[1], '&', '=', false)); + }); + t.end(); +}) + +test('qs.parse - test the nested qs-in-qs case', function (t) { + var f = qs.parse('a=b&q=x%3Dy%26y%3Dz'); + f.q = qs.parse(f.q); + t.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }); + t.end(); +}); + +test('qs.parse - nested in colon', function (t) { + var f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':'); + f.q = qs.parse(f.q, ';', ':'); + t.deepEqual(f, { a: 'b', q: { x: 'y', y: 'z' } }); + t.end(); +}); + +test('qs.stringify - now test stringifying', function (t) { + qsTestCases.forEach(function(testCase) { + t.equal(testCase[1], qs.stringify(testCase[2])); + }); + + qsColonTestCases.forEach(function(testCase) { + t.equal(testCase[1], qs.stringify(testCase[2], ';', ':')); + }); + + qsWeirdObjects.forEach(function(testCase) { + t.equal(testCase[1], qs.stringify(testCase[0])); + }); + + t.end(); +}); + +test('qs.stringify - nested', function (t) { + var f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }) + }); + t.equal(f, 'a=b&q=x%3Dy%26y%3Dz'); + t.end(); +}); + +test('qs.parse - do not throw on undefined input', function (t) { + t.doesNotThrow(function() { + qs.parse(undefined); + }); + t.end(); +}); + +test('qs.stringify - nested in colon', function (t) { + var f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }, ';', ':') + }, ';', ':'); + t.equal(f, 'a:b;q:x%3Ay%3By%3Az'); + t.end(); +}); + +test('qs.parse - on nothing', function (t) { + t.deepEqual({}, qs.parse()); + t.end(); +}); + +// Test limiting +test('qs.parse - test limiting', function (t) { + t.equal( + Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length, + 1); + t.end(); +}); + +test('qs.parse - Test removing limit', function (t) { + function testUnlimitedKeys() { + var query = {}, + url; + + for (var i = 0; i < 2000; i++) query[i] = i; + + url = qs.stringify(query); + + t.equal( + Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length, + 2000); + } + testUnlimitedKeys(); + + t.end(); +}); + +test('qs.unescapeBuffer - basic', function (t) { + var b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' + + '%0d%ac%a2%2f%9d%eb%d8%a2%e6'); + // + t.equal(0xd3, b[0]); + t.equal(0xf2, b[1]); + t.equal(0x55, b[2]); + t.equal(0x67, b[3]); + t.equal(0x1f, b[4]); + t.equal(0x36, b[5]); + t.equal(0x76, b[6]); + t.equal(0x24, b[7]); + t.equal(0x5e, b[8]); + t.equal(0x98, b[9]); + t.equal(0xcb, b[10]); + t.equal(0x0d, b[11]); + t.equal(0xac, b[12]); + t.equal(0xa2, b[13]); + t.equal(0x2f, b[14]); + t.equal(0x9d, b[15]); + t.equal(0xeb, b[16]); + t.equal(0xd8, b[17]); + t.equal(0xa2, b[18]); + t.equal(0xe6, b[19]); + + t.end(); +}); From ce9422db96e631fdc51ca4b980bd57dfed89318c Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 16:03:53 +0200 Subject: [PATCH 18/47] [feature] readline now throw when required --- builtin/readline.js | 2 ++ test/browser/readline-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 builtin/readline.js create mode 100644 test/browser/readline-simple.js diff --git a/builtin/readline.js b/builtin/readline.js new file mode 100644 index 0000000..98021f7 --- /dev/null +++ b/builtin/readline.js @@ -0,0 +1,2 @@ + +throw new Error('readline is not implemented'); diff --git a/test/browser/readline-simple.js b/test/browser/readline-simple.js new file mode 100644 index 0000000..8a672d7 --- /dev/null +++ b/test/browser/readline-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require readline should throw', function (t) { + try { + require('readline'); + t.ok(false, 'readline did not throw'); + } catch (e) { + t.ok(true, 'readline did throw'); + } + t.end(); +}); From baadf04c8a4c6f9c406c295f67624dce850634ec Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 16:04:17 +0200 Subject: [PATCH 19/47] [feature] repl now throw when required --- builtin/repl.js | 2 ++ test/browser/repl-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 builtin/repl.js create mode 100644 test/browser/repl-simple.js diff --git a/builtin/repl.js b/builtin/repl.js new file mode 100644 index 0000000..75ec6b9 --- /dev/null +++ b/builtin/repl.js @@ -0,0 +1,2 @@ + +throw new Error('repl is not implemented'); diff --git a/test/browser/repl-simple.js b/test/browser/repl-simple.js new file mode 100644 index 0000000..bd4f59c --- /dev/null +++ b/test/browser/repl-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require repl should throw', function (t) { + try { + require('repl'); + t.ok(false, 'repl did not throw'); + } catch (e) { + t.ok(true, 'repl did throw'); + } + t.end(); +}); From 001da8ac2f74b50c3ee62156bf0e17bc41c6bf80 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 16:05:46 +0200 Subject: [PATCH 20/47] [fix] update string_decoder and add tests --- builtin/string_decoder.js | 10 ++++ test/browser/string-decoder-simple.js | 76 +++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 test/browser/string-decoder-simple.js diff --git a/builtin/string_decoder.js b/builtin/string_decoder.js index f9a0982..0c27320 100644 --- a/builtin/string_decoder.js +++ b/builtin/string_decoder.js @@ -1,5 +1,15 @@ + +var Buffer = require('buffer').Buffer; + +function assertEncoding(encoding) { + if (encoding && !Buffer.isEncoding(encoding)) { + throw new Error('Unknown encoding: ' + encoding); + } +} + var StringDecoder = exports.StringDecoder = function(encoding) { this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); + assertEncoding(encoding); switch (this.encoding) { case 'utf8': // CESU-8 represents each of Surrogate Pair by 3-bytes diff --git a/test/browser/string-decoder-simple.js b/test/browser/string-decoder-simple.js new file mode 100644 index 0000000..0b3756b --- /dev/null +++ b/test/browser/string-decoder-simple.js @@ -0,0 +1,76 @@ + +var test = require('tape'); + +var Buffer = require('buffer').Buffer; +var StringDecoder = require('string_decoder').StringDecoder; + +var decoder = new StringDecoder('utf8'); + +test('decoder.write - one byte char', function (t) { + var buffer = new Buffer('$'); + t.deepEqual('$', decoder.write(buffer)); + t.end(); +}); + +test('decoder.write - two byte char', function (t) { + var buffer = new Buffer('¢'); + t.deepEqual('', decoder.write(buffer.slice(0, 1))); + t.deepEqual('¢', decoder.write(buffer.slice(1, 2))); + t.end(); +}); + +test('decoder.write - three byte char', function (t) { + var buffer = new Buffer('€'); + t.deepEqual('', decoder.write(buffer.slice(0, 1))); + t.deepEqual('', decoder.write(buffer.slice(1, 2))); + t.deepEqual('€', decoder.write(buffer.slice(2, 3))); + t.end(); +}); + + +test('decoder.write - four byte char', function (t) { + var buffer = new Buffer([0xF0, 0xA4, 0xAD, 0xA2]); + var s = ''; + s += decoder.write(buffer.slice(0, 1)); + s += decoder.write(buffer.slice(1, 2)); + s += decoder.write(buffer.slice(2, 3)); + s += decoder.write(buffer.slice(3, 4)); + t.ok(s.length > 0); + t.end(); +}); + +test('decoder.write - A mixed ascii and non-ascii string', function (t) { + // Test stolen from deps/v8/test/cctest/test-strings.cc + // U+02E4 -> CB A4 + // U+0064 -> 64 + // U+12E4 -> E1 8B A4 + // U+0030 -> 30 + // U+3045 -> E3 81 85 + var expected = '\u02e4\u0064\u12e4\u0030\u3045'; + var buffer = new Buffer([0xCB, 0xA4, 0x64, 0xE1, 0x8B, 0xA4, + 0x30, 0xE3, 0x81, 0x85]); + var charLengths = [0, 0, 1, 2, 2, 2, 3, 4, 4, 4, 5, 5]; + + // Split the buffer into 3 segments + // |----|------|-------| + // 0 i j buffer.length + // Scan through every possible 3 segment combination + // and make sure that the string is always parsed. + for (var j = 2; j < buffer.length; j++) { + for (var i = 1; i < j; i++) { + var decoder = new StringDecoder('utf8'); + + var sum = decoder.write(buffer.slice(0, i)); + + // just check that we've received the right amount + // after the first write + t.equal(charLengths[i], sum.length); + + sum += decoder.write(buffer.slice(i, j)); + sum += decoder.write(buffer.slice(j, buffer.length)); + t.equal(expected, sum); + } + } + + t.end(); +}); From 7fb38dc23cccf3bb0f8988da5e8dc745da22ebd6 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 16:55:53 +0200 Subject: [PATCH 21/47] [feature] timers.setImmediate and timer tests --- builtin/process.js | 35 +---------------- builtin/timers.js | 66 ++++++++++++++++++++++++++++++- test/browser/timers-simple.js | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 34 deletions(-) create mode 100644 test/browser/timers-simple.js diff --git a/builtin/process.js b/builtin/process.js index 8fcb100..4637076 100644 --- a/builtin/process.js +++ b/builtin/process.js @@ -1,38 +1,7 @@ var process = module.exports = {}; -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; - } - - if (canPost) { - var queue = []; - window.addEventListener('message', function (ev) { - if (ev.source === window && ev.data === 'browserify-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - - return function nextTick(fn) { - queue.push(fn); - window.postMessage('browserify-tick', '*'); - }; - } - - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); +var timer = require('timer'); +process.nextTick = timer.setImmediate; process.title = 'browser'; process.browser = true; diff --git a/builtin/timers.js b/builtin/timers.js index 15169bc..3e58f54 100644 --- a/builtin/timers.js +++ b/builtin/timers.js @@ -22,6 +22,11 @@ try { exports.clearTimeout = clearTimeout; exports.clearInterval = clearInterval; + if (window.setImmediate) { + exports.setImmediate = window.setImmediate; + exports.clearImmediate = window.clearImmediate; + } + // Chrome and PhantomJS seems to depend on `this` pseudo variable being a // `window` and throws invalid invocation exception otherwise. If this code // runs in such JS runtime next line will throw and `catch` clause will @@ -31,9 +36,68 @@ try { function bind(f, context) { return function () { return f.apply(context, arguments) }; } - + exports.setTimeout = bind(setTimeout, window); exports.setInterval = bind(setInterval, window); exports.clearTimeout = bind(clearTimeout, window); exports.clearInterval = bind(clearInterval, window); + + if (window.setImmediate) { + exports.setImmediate = bind(window.setImmediate, window); + exports.clearImmediate = bind(window.clearImmediate, window); + } +} + +exports.unref = function unref() {}; +exports.ref = function ref() {}; + +if (!exports.setImmediate) { + var currentKey = 0, queue = {}, active = false; + + exports.setImmediate = (function () { + function drain() { + active = false; + for (var key in queue) { + if (queue.hasOwnProperty(currentKey, key)) { + var fn = queue[key]; + delete queue[key]; + fn(); + } + } + } + + if (typeof window !== 'undefined' && + window.postMessage && window.addEventListener) { + window.addEventListener('message', function (ev) { + if (ev.source === window && ev.data === 'browserify-tick') { + ev.stopPropagation(); + drain(); + } + }, true); + + return function setImmediate(fn) { + var id = ++currentKey; + queue[id] = fn; + if (!active) { + active = true; + window.postMessage('browserify-tick', '*'); + } + return id; + }; + } else { + return function setImmediate(fn) { + var id = ++currentKey; + queue[id] = fn; + if (!active) { + active = true; + setTimeout(drain, 0); + } + return id; + }; + } + })(); + + exports.clearImmediate = function clearImmediate(id) { + delete queue[id]; + }; } diff --git a/test/browser/timers-simple.js b/test/browser/timers-simple.js new file mode 100644 index 0000000..4c58eec --- /dev/null +++ b/test/browser/timers-simple.js @@ -0,0 +1,74 @@ + +var test = require('tape'); + +var timers = require('timers'); + +test('timers.setTimeout - no clear', function (t) { + timers.setTimeout(function () { + t.ok(true, 'callback called'); + t.end(); + }, 1); +}); + +test('timers.setTimeout - clear', function (t) { + var id = timers.setTimeout(function () { + t.ok(false, 'callback called'); + }, 1); + clearTimeout(id); + t.end(); +}); + +test('timers.setInterval - no clear', function (t) { + var id = timers.setInterval(function () { + timers.clearInterval(id); + t.ok(true, 'callback called'); + t.end(); + }, 1); +}); + +test('timers.setInterval - clear', function (t) { + var id = timers.setInterval(function () { + t.ok(false, 'callback called'); + }, 1); + timers.clearInterval(id); + t.end(); +}); + +test('timers.setImmediate - is async', function (t) { + var sync = true; + timers.setImmediate(function () { + t.ok(!sync, 'setImmediate is async'); + t.end(); + }); + sync = false; +}); + +test('timers.setImmediate - no clear', function (t) { + var aCalled = 0, bCalled = 0; + timers.setImmediate(function () { + aCalled += 1; + if (aCalled && bCalled) done(); + }); + timers.setImmediate(function () { + bCalled += 1; + if (aCalled && bCalled) done(); + }); + + function done() { + t.equal(aCalled, 1); + t.equal(bCalled, 1); + t.end(); + } +}); + +test('timers.setImmediate - clear', function (t) { + var aId = timers.setImmediate(function () { + t.ok(false, 'callback called'); + }); + var bId = timers.setImmediate(function () { + t.ok(false, 'callback called'); + }); + timers.clearImmediate(aId); + timers.clearImmediate(bId); + t.end(); +}); From 55b10fc882e293b878aa419784b025bed08dde58 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 17:06:56 +0200 Subject: [PATCH 22/47] [feature] tls now throw when required --- builtin/tls.js | 3 ++- test/browser/tls-simple.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/browser/tls-simple.js diff --git a/builtin/tls.js b/builtin/tls.js index 65b3dba..27fa241 100644 --- a/builtin/tls.js +++ b/builtin/tls.js @@ -1 +1,2 @@ -// todo + +throw new Error('tls is not implemented'); diff --git a/test/browser/tls-simple.js b/test/browser/tls-simple.js new file mode 100644 index 0000000..3e37f60 --- /dev/null +++ b/test/browser/tls-simple.js @@ -0,0 +1,12 @@ + +var test = require('tape'); + +test('require tls should throw', function (t) { + try { + require('tls'); + t.ok(false, 'tls did not throw'); + } catch (e) { + t.ok(true, 'tls did throw'); + } + t.end(); +}); From bdc431073b31ef3eadb7edc4b1730ac8da6d225e Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 17:07:43 +0200 Subject: [PATCH 23/47] [feature] tty.isatty returns false the reset throws --- builtin/tty.js | 14 ++++++++++++-- test/browser/tty-simple.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test/browser/tty-simple.js diff --git a/builtin/tty.js b/builtin/tty.js index a674e0f..6cb78f7 100644 --- a/builtin/tty.js +++ b/builtin/tty.js @@ -1,2 +1,12 @@ -exports.isatty = function () {}; -exports.setRawMode = function () {}; + +exports.isatty = function () { return false; }; + +function ReadStream() { + throw new Error('tty.ReadStream is not implemented'); +} +exports.ReadStream = ReadStream; + +function WriteStream() { + throw new Error('tty.ReadStream is not implemented'); +} +exports.WriteStream = WriteStream; diff --git a/test/browser/tty-simple.js b/test/browser/tty-simple.js new file mode 100644 index 0000000..03ce51c --- /dev/null +++ b/test/browser/tty-simple.js @@ -0,0 +1,29 @@ + +var test = require('tape'); + +var tty = require('tty'); + +test('tty.isatty return false', function (t) { + t.equal(tty.isatty(), false); + t.end(); +}); + +test('tty.ReadStream', function (t) { + try { + new tty.ReadStream(); + t.ok(false, 'tty.ReadStream did not throw'); + } catch (e) { + t.ok(true, 'tty.ReadStream did throw'); + } + t.end(); +}); + +test('tty.WriteStream', function (t) { + try { + new tty.WriteStream(); + t.ok(false, 'tty.WriteStream did not throw'); + } catch (e) { + t.ok(true, 'tty.WriteStream did throw'); + } + t.end(); +}); From d9636aa0a7f64fc38eac8e43fe58a09b094d1aa4 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 17:17:26 +0200 Subject: [PATCH 24/47] [dist] upgrade buffer-browserify to 0.2.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 383c37f..db295a0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "vm-browserify": "0.0.x", "crypto-browserify": "1.0.x", "http-browserify": "0.1.x", - "buffer-browserify": "0.1.x", + "buffer-browserify": "0.2.x", "zlib-browserify": "0.0.x", "constants-browserify": "0.0.x", "os-browserify": "0.1.0" From e032f428a278cda81114607d4bd6f7d6797ce10d Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 17:34:06 +0200 Subject: [PATCH 25/47] [feature] update url and add tests Note punycode related test has been removed --- builtin/url.js | 580 ++++++++------- test/browser/url-simple.js | 1403 ++++++++++++++++++++++++++++++++++++ 2 files changed, 1728 insertions(+), 255 deletions(-) create mode 100644 test/browser/url-simple.js diff --git a/builtin/url.js b/builtin/url.js index d623925..a0ce69b 100644 --- a/builtin/url.js +++ b/builtin/url.js @@ -1,22 +1,28 @@ + var punycode = { encode : function (s) { return s } }; +var util = require('util'); +var shims = require('./_shims.js'); exports.parse = urlParse; exports.resolve = urlResolve; exports.resolveObject = urlResolveObject; exports.format = urlFormat; -function arrayIndexOf(array, subject) { - for (var i = 0, j = array.length; i < j; i++) { - if(array[i] == subject) return i; - } - return -1; -} - -var objectKeys = Object.keys || function objectKeys(object) { - if (object !== Object(object)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in object) if (object.hasOwnProperty(key)) keys[keys.length] = key; - return keys; +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; } // Reference: RFC 3986, RFC 1808, RFC 2396 @@ -24,23 +30,26 @@ var objectKeys = Object.keys || function objectKeys(object) { // define these here so at least they only have to be // compiled once on the first module load. var protocolPattern = /^([a-z0-9.+-]+:)/i, - portPattern = /:[0-9]+$/, + portPattern = /:[0-9]*$/, + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + // RFC 2396: characters not allowed for various reasons. - unwise = ['{', '}', '|', '\\', '^', '~', '[', ']', '`'].concat(delims), + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), + // Allowed by RFCs, but cause of XSS attacks. Always escape these. - autoEscape = ['\''], + autoEscape = ['\''].concat(unwise), // Characters that are never ever allowed in a hostname. // Note that any invalid chars are also handled, but these // are the ones that are *expected* to be seen, so we fast-path // them. - nonHostChars = ['%', '/', '?', ';', '#'] - .concat(unwise).concat(autoEscape), - nonAuthChars = ['/', '@', '?', '#'].concat(delims), + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], hostnameMaxLen = 255, - hostnamePartPattern = /^[a-zA-Z0-9][a-z0-9A-Z_-]{0,62}$/, - hostnamePartStart = /^([a-zA-Z0-9][a-z0-9A-Z_-]{0,62})(.*)$/, + hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/, // protocols that can allow "unsafe" and "unwise" chars. unsafeProtocol = { 'javascript': true, @@ -51,18 +60,6 @@ var protocolPattern = /^([a-z0-9.+-]+:)/i, 'javascript': true, 'javascript:': true }, - // protocols that always have a path component. - pathedProtocol = { - 'http': true, - 'https': true, - 'ftp': true, - 'gopher': true, - 'file': true, - 'http:': true, - 'ftp:': true, - 'gopher:': true, - 'file:': true - }, // protocols that always contain a // bit. slashedProtocol = { 'http': true, @@ -79,28 +76,29 @@ var protocolPattern = /^([a-z0-9.+-]+:)/i, querystring = require('querystring'); function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url && typeof(url) === 'object' && url.href) return url; + if (url && util.isObject(url) && url instanceof Url) return url; + + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} - if (typeof url !== 'string') { +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { + if (!util.isString(url)) { throw new TypeError("Parameter 'url' must be a string, not " + typeof url); } - var out = {}, - rest = url; - - // cut off any delimiters. - // This is to support parse stuff like "" - for (var i = 0, l = rest.length; i < l; i++) { - if (arrayIndexOf(delims, rest.charAt(i)) === -1) break; - } - if (i !== 0) rest = rest.substr(i); + var rest = url; + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim(); var proto = protocolPattern.exec(rest); if (proto) { proto = proto[0]; var lowerProto = proto.toLowerCase(); - out.protocol = lowerProto; + this.protocol = lowerProto; rest = rest.substr(proto.length); } @@ -112,71 +110,85 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { var slashes = rest.substr(0, 2) === '//'; if (slashes && !(proto && hostlessProtocol[proto])) { rest = rest.substr(2); - out.slashes = true; + this.slashes = true; } } if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) { + // there's a hostname. // the first instance of /, ?, ;, or # ends the host. - // don't enforce full RFC correctness, just be unstupid about it. - + // // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the first @ sign, unless some non-auth character + // to the left of the last @ sign, unless some host-ending character // comes *before* the @-sign. // URLs are obnoxious. - var atSign = arrayIndexOf(rest, '@'); - if (atSign !== -1) { - // there *may be* an auth - var hasAuth = true; - for (var i = 0, l = nonAuthChars.length; i < l; i++) { - var index = arrayIndexOf(rest, nonAuthChars[i]); - if (index !== -1 && index < atSign) { - // not a valid auth. Something like http://foo.com/bar@baz/ - hasAuth = false; - break; - } - } - if (hasAuth) { - // pluck off the auth portion. - out.auth = rest.substr(0, atSign); - rest = rest.substr(atSign + 1); - } + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1; + for (var i = 0; i < hostEndingChars.length; i++) { + var hec = rest.indexOf(hostEndingChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign; + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@'); + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd); } - var firstNonHost = -1; - for (var i = 0, l = nonHostChars.length; i < l; i++) { - var index = arrayIndexOf(rest, nonHostChars[i]); - if (index !== -1 && - (firstNonHost < 0 || index < firstNonHost)) firstNonHost = index; + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign); + rest = rest.slice(atSign + 1); + this.auth = decodeURIComponent(auth); } - if (firstNonHost !== -1) { - out.host = rest.substr(0, firstNonHost); - rest = rest.substr(firstNonHost); - } else { - out.host = rest; - rest = ''; + // the host is the remaining to the left of the first non-host char + hostEnd = -1; + for (var i = 0; i < nonHostChars.length; i++) { + var hec = rest.indexOf(nonHostChars[i]); + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) + hostEnd = hec; } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) + hostEnd = rest.length; + + this.host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); // pull out port. - var p = parseHost(out.host); - var keys = objectKeys(p); - for (var i = 0, l = keys.length; i < l; i++) { - var key = keys[i]; - out[key] = p[key]; - } + this.parseHost(); // we've indicated that there is a hostname, // so even if it's empty, it has to be present. - out.hostname = out.hostname || ''; + this.hostname = this.hostname || ''; + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; // validate a little. - if (out.hostname.length > hostnameMaxLen) { - out.hostname = ''; - } else { - var hostparts = out.hostname.split(/\./); + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); for (var i = 0, l = hostparts.length; i < l; i++) { var part = hostparts[i]; if (!part) continue; @@ -204,32 +216,48 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { if (notHost.length) { rest = '/' + notHost.join('.') + rest; } - out.hostname = validParts.join('.'); + this.hostname = validParts.join('.'); break; } } } } - // hostnames are always lower case. - out.hostname = out.hostname.toLowerCase(); - - // IDNA Support: Returns a puny coded representation of "domain". - // It only converts the part of the domain name that - // has non ASCII characters. I.e. it dosent matter if - // you call it with a domain that already is in ASCII. - var domainArray = out.hostname.split('.'); - var newOut = []; - for (var i = 0; i < domainArray.length; ++i) { - var s = domainArray[i]; - newOut.push(s.match(/[^A-Za-z0-9_-]/) ? - 'xn--' + punycode.encode(s) : s); + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (!ipv6Hostname) { + // IDNA Support: Returns a puny coded representation of "domain". + // It only converts the part of the domain name that + // has non ASCII characters. I.e. it dosent matter if + // you call it with a domain that already is in ASCII. + var domainArray = this.hostname.split('.'); + var newOut = []; + for (var i = 0; i < domainArray.length; ++i) { + var s = domainArray[i]; + newOut.push(s.match(/[^A-Za-z0-9_-]/) ? + 'xn--' + punycode.encode(s) : s); + } + this.hostname = newOut.join('.'); } - out.hostname = newOut.join('.'); - out.host = (out.hostname || '') + - ((out.port) ? ':' + out.port : ''); - out.href += out.host; + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2); + if (rest[0] !== '/') { + rest = '/' + rest; + } + } } // now rest is set to the post-host stuff. @@ -247,55 +275,46 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { } rest = rest.split(ae).join(esc); } - - // Now make sure that delims never appear in a url. - var chop = rest.length; - for (var i = 0, l = delims.length; i < l; i++) { - var c = arrayIndexOf(rest, delims[i]); - if (c !== -1) { - chop = Math.min(c, chop); - } - } - rest = rest.substr(0, chop); } // chop off from the tail first. - var hash = arrayIndexOf(rest, '#'); + var hash = rest.indexOf('#'); if (hash !== -1) { // got a fragment string. - out.hash = rest.substr(hash); + this.hash = rest.substr(hash); rest = rest.slice(0, hash); } - var qm = arrayIndexOf(rest, '?'); + var qm = rest.indexOf('?'); if (qm !== -1) { - out.search = rest.substr(qm); - out.query = rest.substr(qm + 1); + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); if (parseQueryString) { - out.query = querystring.parse(out.query); + this.query = querystring.parse(this.query); } rest = rest.slice(0, qm); } else if (parseQueryString) { // no query string, but parseQueryString still requested - out.search = ''; - out.query = {}; + this.search = ''; + this.query = {}; } - if (rest) out.pathname = rest; - if (slashedProtocol[proto] && - out.hostname && !out.pathname) { - out.pathname = '/'; + if (rest) this.pathname = rest; + if (slashedProtocol[lowerProto] && + this.hostname && !this.pathname) { + this.pathname = '/'; } //to support http.request - if (out.pathname || out.search) { - out.path = (out.pathname ? out.pathname : '') + - (out.search ? out.search : ''); + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; } // finally, reconstruct the href based on what has been validated. - out.href = urlFormat(out); - return out; -} + this.href = this.format(); + return this; +}; // format a parsed object into a url string function urlFormat(obj) { @@ -303,39 +322,49 @@ function urlFormat(obj) { // If it's an obj, this is a no-op. // this way, you can call url_format() on strings // to clean up potentially wonky urls. - if (typeof(obj) === 'string') obj = urlParse(obj); + if (util.isString(obj)) obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); +} - var auth = obj.auth || ''; +Url.prototype.format = function() { + var auth = this.auth || ''; if (auth) { - auth = auth.split('@').join('%40'); - for (var i = 0, l = nonAuthChars.length; i < l; i++) { - var nAC = nonAuthChars[i]; - auth = auth.split(nAC).join(encodeURIComponent(nAC)); - } + auth = encodeURIComponent(auth); + auth = auth.replace(/%3A/i, ':'); auth += '@'; } - var protocol = obj.protocol || '', - host = (obj.host !== undefined) ? auth + obj.host : - obj.hostname !== undefined ? ( - auth + obj.hostname + - (obj.port ? ':' + obj.port : '') - ) : - false, - pathname = obj.pathname || '', - query = obj.query && - ((typeof obj.query === 'object' && - objectKeys(obj.query).length) ? - querystring.stringify(obj.query) : - '') || '', - search = obj.search || (query && ('?' + query)) || '', - hash = obj.hash || ''; + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', + host = false, + query = ''; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; + } + } + + if (this.query && + util.isObject(this.query) && + shims.keys(this.query).length) { + query = querystring.stringify(this.query); + } + + var search = this.search || (query && ('?' + query)) || ''; if (protocol && protocol.substr(-1) !== ':') protocol += ':'; // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. // unless they had them to begin with. - if (obj.slashes || + if (this.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) { host = '//' + (host || ''); if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; @@ -346,40 +375,68 @@ function urlFormat(obj) { if (hash && hash.charAt(0) !== '#') hash = '#' + hash; if (search && search.charAt(0) !== '?') search = '?' + search; + pathname = pathname.replace(/[?#]/g, function(match) { + return encodeURIComponent(match); + }); + search = search.replace('#', '%23'); + return protocol + host + pathname + search + hash; -} +}; function urlResolve(source, relative) { - return urlFormat(urlResolveObject(source, relative)); + return urlParse(source, false, true).resolve(relative); } +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + function urlResolveObject(source, relative) { if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +Url.prototype.resolveObject = function(relative) { + if (util.isString(relative)) { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } - source = urlParse(urlFormat(source), false, true); - relative = urlParse(urlFormat(relative), false, true); + var result = new Url(); + shims.keys(this).forEach(function(k) { + result[k] = this[k]; + }, this); // hash is always overridden, no matter what. - source.hash = relative.hash; + // even href="" will remove it. + result.hash = relative.hash; + // if the relative url is empty, then there's nothing left to do here. if (relative.href === '') { - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } // hrefs like //foo/bar always cut to the protocol. if (relative.slashes && !relative.protocol) { - relative.protocol = source.protocol; + // take everything except the protocol from relative + shims.keys(relative).forEach(function(k) { + if (k !== 'protocol') + result[k] = relative[k]; + }); + //urlParse appends trailing / to urls like http://www.example.com - if (slashedProtocol[relative.protocol] && - relative.hostname && !relative.pathname) { - relative.path = relative.pathname = '/'; + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; } - relative.href = urlFormat(relative); - return relative; + + result.href = result.format(); + return result; } - if (relative.protocol && relative.protocol !== source.protocol) { + if (relative.protocol && relative.protocol !== result.protocol) { // if it's a known url protocol, then changing // the protocol does weird things // first, if it's not file:, then we MUST have a host, @@ -389,10 +446,14 @@ function urlResolveObject(source, relative) { // because that's known to be hostless. // anything else is assumed to be absolute. if (!slashedProtocol[relative.protocol]) { - relative.href = urlFormat(relative); - return relative; + shims.keys(relative).forEach(function(k) { + result[k] = relative[k]; + }); + result.href = result.format(); + return result; } - source.protocol = relative.protocol; + + result.protocol = relative.protocol; if (!relative.host && !hostlessProtocol[relative.protocol]) { var relPath = (relative.pathname || '').split('/'); while (relPath.length && !(relative.host = relPath.shift())); @@ -400,72 +461,72 @@ function urlResolveObject(source, relative) { if (!relative.hostname) relative.hostname = ''; if (relPath[0] !== '') relPath.unshift(''); if (relPath.length < 2) relPath.unshift(''); - relative.pathname = relPath.join('/'); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; } - source.pathname = relative.pathname; - source.search = relative.search; - source.query = relative.query; - source.host = relative.host || ''; - source.auth = relative.auth; - source.hostname = relative.hostname || relative.host; - source.port = relative.port; - //to support http.request - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; } - source.slashes = source.slashes || relative.slashes; - source.href = urlFormat(source); - return source; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; } - var isSourceAbs = (source.pathname && source.pathname.charAt(0) === '/'), + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), isRelAbs = ( - relative.host !== undefined || + relative.host || relative.pathname && relative.pathname.charAt(0) === '/' ), mustEndAbs = (isRelAbs || isSourceAbs || - (source.host && relative.pathname)), + (result.host && relative.pathname)), removeAllDots = mustEndAbs, - srcPath = source.pathname && source.pathname.split('/') || [], + srcPath = result.pathname && result.pathname.split('/') || [], relPath = relative.pathname && relative.pathname.split('/') || [], - psychotic = source.protocol && - !slashedProtocol[source.protocol]; + psychotic = result.protocol && !slashedProtocol[result.protocol]; // if the url is a non-slashed url, then relative // links like ../.. should be able // to crawl up to the hostname, as well. This is strange. - // source.protocol has already been set by now. + // result.protocol has already been set by now. // Later on, put the first path part into the host field. if (psychotic) { - - delete source.hostname; - delete source.port; - if (source.host) { - if (srcPath[0] === '') srcPath[0] = source.host; - else srcPath.unshift(source.host); + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); } - delete source.host; + result.host = ''; if (relative.protocol) { - delete relative.hostname; - delete relative.port; + relative.hostname = null; + relative.port = null; if (relative.host) { if (relPath[0] === '') relPath[0] = relative.host; else relPath.unshift(relative.host); } - delete relative.host; + relative.host = null; } mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); } if (isRelAbs) { // it's absolute. - source.host = (relative.host || relative.host === '') ? - relative.host : source.host; - source.hostname = (relative.hostname || relative.hostname === '') ? - relative.hostname : source.hostname; - source.search = relative.search; - source.query = relative.query; + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; srcPath = relPath; // fall through to the dot-handling below. } else if (relPath.length) { @@ -474,53 +535,55 @@ function urlResolveObject(source, relative) { if (!srcPath) srcPath = []; srcPath.pop(); srcPath = srcPath.concat(relPath); - source.search = relative.search; - source.query = relative.query; - } else if ('search' in relative) { + result.search = relative.search; + result.query = relative.query; + } else if (!util.isNullOrUndefined(relative.search)) { // just pull out the search. // like href='?foo'. // Put this after the other two cases because it simplifies the booleans if (psychotic) { - source.hostname = source.host = srcPath.shift(); + result.hostname = result.host = srcPath.shift(); //occationaly the auth can get stuck only in host //this especialy happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = source.host && arrayIndexOf(source.host, '@') > 0 ? - source.host.split('@') : false; + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - source.auth = authInHost.shift(); - source.host = source.hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - source.search = relative.search; - source.query = relative.query; + result.search = relative.search; + result.query = relative.query; //to support http.request - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); } - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } + if (!srcPath.length) { // no path at all. easy. // we've already handled the other stuff above. - delete source.pathname; + result.pathname = null; //to support http.request - if (!source.search) { - source.path = '/' + source.search; + if (result.search) { + result.path = '/' + result.search; } else { - delete source.path; + result.path = null; } - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } + // if a url ENDs in . or .., then it must get a trailing slash. // however, if it ends in anything else non-slashy, // then it must NOT get a trailing slash. var last = srcPath.slice(-1)[0]; var hasTrailingSlash = ( - (source.host || relative.host) && (last === '.' || last === '..') || + (result.host || relative.host) && (last === '.' || last === '..') || last === ''); // strip single dots, resolve double dots to parent dir @@ -560,45 +623,52 @@ function urlResolveObject(source, relative) { // put the host back if (psychotic) { - source.hostname = source.host = isAbsolute ? '' : + result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; //occationaly the auth can get stuck only in host //this especialy happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = source.host && arrayIndexOf(source.host, '@') > 0 ? - source.host.split('@') : false; + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - source.auth = authInHost.shift(); - source.host = source.hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - mustEndAbs = mustEndAbs || (source.host && srcPath.length); + mustEndAbs = mustEndAbs || (result.host && srcPath.length); if (mustEndAbs && !isAbsolute) { srcPath.unshift(''); } - source.pathname = srcPath.join('/'); - //to support request.http - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); } - source.auth = relative.auth || source.auth; - source.slashes = source.slashes || relative.slashes; - source.href = urlFormat(source); - return source; -} -function parseHost(host) { - var out = {}; + //to support request.http + if (!util.isNull(result.pathname) || !util.isNull(result.search)) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function() { + var host = this.host; var port = portPattern.exec(host); if (port) { port = port[0]; - out.port = port.substr(1); + if (port !== ':') { + this.port = port.substr(1); + } host = host.substr(0, host.length - port.length); } - if (host) out.hostname = host; - return out; -} + if (host) this.hostname = host; +}; \ No newline at end of file diff --git a/test/browser/url-simple.js b/test/browser/url-simple.js new file mode 100644 index 0000000..bb2679b --- /dev/null +++ b/test/browser/url-simple.js @@ -0,0 +1,1403 @@ + +var test = require('tape'); + +var url = require('url'); + +// URLs to parse, and expected data +// { url : parsed } +var parseTests = { + '//some_path' : { + 'href': '//some_path', + 'pathname': '//some_path', + 'path': '//some_path' + }, + + 'HTTP://www.example.com/' : { + 'href': 'http://www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'HTTP://www.example.com' : { + 'href': 'http://www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://www.ExAmPlE.com/' : { + 'href': 'http://www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://user:pw@www.ExAmPlE.com/' : { + 'href': 'http://user:pw@www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pw', + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://USER:PW@www.ExAmPlE.com/' : { + 'href': 'http://USER:PW@www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'auth': 'USER:PW', + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://user@www.example.com/' : { + 'href': 'http://user@www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user', + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://user%3Apw@www.example.com/' : { + 'href': 'http://user:pw@www.example.com/', + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pw', + 'host': 'www.example.com', + 'hostname': 'www.example.com', + 'pathname': '/', + 'path': '/' + }, + + 'http://x.com/path?that\'s#all, folks' : { + 'href': 'http://x.com/path?that%27s#all,%20folks', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x.com', + 'hostname': 'x.com', + 'search': '?that%27s', + 'query': 'that%27s', + 'pathname': '/path', + 'hash': '#all,%20folks', + 'path': '/path?that%27s' + }, + + 'HTTP://X.COM/Y' : { + 'href': 'http://x.com/Y', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x.com', + 'hostname': 'x.com', + 'pathname': '/Y', + 'path': '/Y' + }, + + // an unexpected invalid char in the hostname. + 'HtTp://x.y.cOm*a/b/c?d=e#f gi' : { + 'href': 'http://x.y.com/*a/b/c?d=e#f%20g%3Ch%3Ei', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x.y.com', + 'hostname': 'x.y.com', + 'pathname': '/*a/b/c', + 'search': '?d=e', + 'query': 'd=e', + 'hash': '#f%20g%3Ch%3Ei', + 'path': '/*a/b/c?d=e' + }, + + // make sure that we don't accidentally lcast the path parts. + 'HtTp://x.y.cOm*A/b/c?d=e#f gi' : { + 'href': 'http://x.y.com/*A/b/c?d=e#f%20g%3Ch%3Ei', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x.y.com', + 'hostname': 'x.y.com', + 'pathname': '/*A/b/c', + 'search': '?d=e', + 'query': 'd=e', + 'hash': '#f%20g%3Ch%3Ei', + 'path': '/*A/b/c?d=e' + }, + + 'http://x...y...#p': { + 'href': 'http://x...y.../#p', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x...y...', + 'hostname': 'x...y...', + 'hash': '#p', + 'pathname': '/', + 'path': '/' + }, + + 'http://x/p/"quoted"': { + 'href': 'http://x/p/%22quoted%22', + 'protocol': 'http:', + 'slashes': true, + 'host': 'x', + 'hostname': 'x', + 'pathname': '/p/%22quoted%22', + 'path': '/p/%22quoted%22' + }, + + ' Is a URL!': { + 'href': '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', + 'pathname': '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', + 'path': '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!' + }, + + 'http://www.narwhaljs.org/blog/categories?id=news' : { + 'href': 'http://www.narwhaljs.org/blog/categories?id=news', + 'protocol': 'http:', + 'slashes': true, + 'host': 'www.narwhaljs.org', + 'hostname': 'www.narwhaljs.org', + 'search': '?id=news', + 'query': 'id=news', + 'pathname': '/blog/categories', + 'path': '/blog/categories?id=news' + }, + + 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=' : { + 'href': 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', + 'protocol': 'http:', + 'slashes': true, + 'host': 'mt0.google.com', + 'hostname': 'mt0.google.com', + 'pathname': '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', + 'path': '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' : { + 'href': 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api' + + '&x=2&y=2&z=3&s=', + 'protocol': 'http:', + 'slashes': true, + 'host': 'mt0.google.com', + 'hostname': 'mt0.google.com', + 'search': '???&hl=en&src=api&x=2&y=2&z=3&s=', + 'query': '??&hl=en&src=api&x=2&y=2&z=3&s=', + 'pathname': '/vt/lyrs=m@114', + 'path': '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': + { + 'href': 'http://user:pass@mt0.google.com/vt/lyrs=m@114???' + + '&hl=en&src=api&x=2&y=2&z=3&s=', + 'protocol': 'http:', + 'slashes': true, + 'host': 'mt0.google.com', + 'auth': 'user:pass', + 'hostname': 'mt0.google.com', + 'search': '???&hl=en&src=api&x=2&y=2&z=3&s=', + 'query': '??&hl=en&src=api&x=2&y=2&z=3&s=', + 'pathname': '/vt/lyrs=m@114', + 'path': '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'file:///etc/passwd' : { + 'href': 'file:///etc/passwd', + 'slashes': true, + 'protocol': 'file:', + 'pathname': '/etc/passwd', + 'hostname': '', + 'host': '', + 'path': '/etc/passwd' + }, + + 'file://localhost/etc/passwd' : { + 'href': 'file://localhost/etc/passwd', + 'protocol': 'file:', + 'slashes': true, + 'pathname': '/etc/passwd', + 'hostname': 'localhost', + 'host': 'localhost', + 'path': '/etc/passwd' + }, + + 'file://foo/etc/passwd' : { + 'href': 'file://foo/etc/passwd', + 'protocol': 'file:', + 'slashes': true, + 'pathname': '/etc/passwd', + 'hostname': 'foo', + 'host': 'foo', + 'path': '/etc/passwd' + }, + + 'file:///etc/node/' : { + 'href': 'file:///etc/node/', + 'slashes': true, + 'protocol': 'file:', + 'pathname': '/etc/node/', + 'hostname': '', + 'host': '', + 'path': '/etc/node/' + }, + + 'file://localhost/etc/node/' : { + 'href': 'file://localhost/etc/node/', + 'protocol': 'file:', + 'slashes': true, + 'pathname': '/etc/node/', + 'hostname': 'localhost', + 'host': 'localhost', + 'path': '/etc/node/' + }, + + 'file://foo/etc/node/' : { + 'href': 'file://foo/etc/node/', + 'protocol': 'file:', + 'slashes': true, + 'pathname': '/etc/node/', + 'hostname': 'foo', + 'host': 'foo', + 'path': '/etc/node/' + }, + + 'http:/baz/../foo/bar' : { + 'href': 'http:/baz/../foo/bar', + 'protocol': 'http:', + 'pathname': '/baz/../foo/bar', + 'path': '/baz/../foo/bar' + }, + + 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag' : { + 'href': 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag', + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com:8000', + 'auth': 'user:pass', + 'port': '8000', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?baz=quux', + 'query': 'baz=quux', + 'pathname': '/foo/bar', + 'path': '/foo/bar?baz=quux' + }, + + '//user:pass@example.com:8000/foo/bar?baz=quux#frag' : { + 'href': '//user:pass@example.com:8000/foo/bar?baz=quux#frag', + 'slashes': true, + 'host': 'example.com:8000', + 'auth': 'user:pass', + 'port': '8000', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?baz=quux', + 'query': 'baz=quux', + 'pathname': '/foo/bar', + 'path': '/foo/bar?baz=quux' + }, + + '/foo/bar?baz=quux#frag' : { + 'href': '/foo/bar?baz=quux#frag', + 'hash': '#frag', + 'search': '?baz=quux', + 'query': 'baz=quux', + 'pathname': '/foo/bar', + 'path': '/foo/bar?baz=quux' + }, + + 'http:/foo/bar?baz=quux#frag' : { + 'href': 'http:/foo/bar?baz=quux#frag', + 'protocol': 'http:', + 'hash': '#frag', + 'search': '?baz=quux', + 'query': 'baz=quux', + 'pathname': '/foo/bar', + 'path': '/foo/bar?baz=quux' + }, + + 'mailto:foo@bar.com?subject=hello' : { + 'href': 'mailto:foo@bar.com?subject=hello', + 'protocol': 'mailto:', + 'host': 'bar.com', + 'auth' : 'foo', + 'hostname' : 'bar.com', + 'search': '?subject=hello', + 'query': 'subject=hello', + 'path': '?subject=hello' + }, + + 'javascript:alert(\'hello\');' : { + 'href': 'javascript:alert(\'hello\');', + 'protocol': 'javascript:', + 'pathname': 'alert(\'hello\');', + 'path': 'alert(\'hello\');' + }, + + 'xmpp:isaacschlueter@jabber.org' : { + 'href': 'xmpp:isaacschlueter@jabber.org', + 'protocol': 'xmpp:', + 'host': 'jabber.org', + 'auth': 'isaacschlueter', + 'hostname': 'jabber.org' + }, + + 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar' : { + 'href' : 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', + 'protocol' : 'http:', + 'slashes': true, + 'host' : '127.0.0.1:8080', + 'auth' : 'atpass:foo@bar', + 'hostname' : '127.0.0.1', + 'port' : '8080', + 'pathname': '/path', + 'search' : '?search=foo', + 'query' : 'search=foo', + 'hash' : '#bar', + 'path': '/path?search=foo' + }, + + 'svn+ssh://foo/bar': { + 'href': 'svn+ssh://foo/bar', + 'host': 'foo', + 'hostname': 'foo', + 'protocol': 'svn+ssh:', + 'pathname': '/bar', + 'path': '/bar', + 'slashes': true + }, + + 'dash-test://foo/bar': { + 'href': 'dash-test://foo/bar', + 'host': 'foo', + 'hostname': 'foo', + 'protocol': 'dash-test:', + 'pathname': '/bar', + 'path': '/bar', + 'slashes': true + }, + + 'dash-test:foo/bar': { + 'href': 'dash-test:foo/bar', + 'host': 'foo', + 'hostname': 'foo', + 'protocol': 'dash-test:', + 'pathname': '/bar', + 'path': '/bar' + }, + + 'dot.test://foo/bar': { + 'href': 'dot.test://foo/bar', + 'host': 'foo', + 'hostname': 'foo', + 'protocol': 'dot.test:', + 'pathname': '/bar', + 'path': '/bar', + 'slashes': true + }, + + 'dot.test:foo/bar': { + 'href': 'dot.test:foo/bar', + 'host': 'foo', + 'hostname': 'foo', + 'protocol': 'dot.test:', + 'pathname': '/bar', + 'path': '/bar' + }, + + 'http://bucket_name.s3.amazonaws.com/image.jpg': { + protocol: 'http:', + 'slashes': true, + slashes: true, + host: 'bucket_name.s3.amazonaws.com', + hostname: 'bucket_name.s3.amazonaws.com', + pathname: '/image.jpg', + href: 'http://bucket_name.s3.amazonaws.com/image.jpg', + 'path': '/image.jpg' + }, + + 'git+http://github.com/joyent/node.git': { + protocol: 'git+http:', + slashes: true, + host: 'github.com', + hostname: 'github.com', + pathname: '/joyent/node.git', + path: '/joyent/node.git', + href: 'git+http://github.com/joyent/node.git' + }, + + //if local1@domain1 is uses as a relative URL it may + //be parse into auth@hostname, but here there is no + //way to make it work in url.parse, I add the test to be explicit + 'local1@domain1': { + 'pathname': 'local1@domain1', + 'path': 'local1@domain1', + 'href': 'local1@domain1' + }, + + //While this may seem counter-intuitive, a browser will parse + // as a path. + 'www.example.com' : { + 'href': 'www.example.com', + 'pathname': 'www.example.com', + 'path': 'www.example.com' + }, + + // ipv6 support + '[fe80::1]': { + 'href': '[fe80::1]', + 'pathname': '[fe80::1]', + 'path': '[fe80::1]' + }, + + 'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]': { + 'protocol': 'coap:', + 'slashes': true, + 'host': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]', + 'hostname': 'fedc:ba98:7654:3210:fedc:ba98:7654:3210', + 'href': 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/', + 'pathname': '/', + 'path': '/' + }, + + 'coap://[1080:0:0:0:8:800:200C:417A]:61616/': { + 'protocol': 'coap:', + 'slashes': true, + 'host': '[1080:0:0:0:8:800:200c:417a]:61616', + 'port': '61616', + 'hostname': '1080:0:0:0:8:800:200c:417a', + 'href': 'coap://[1080:0:0:0:8:800:200c:417a]:61616/', + 'pathname': '/', + 'path': '/' + }, + + 'http://user:password@[3ffe:2a00:100:7031::1]:8080': { + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:password', + 'host': '[3ffe:2a00:100:7031::1]:8080', + 'port': '8080', + 'hostname': '3ffe:2a00:100:7031::1', + 'href': 'http://user:password@[3ffe:2a00:100:7031::1]:8080/', + 'pathname': '/', + 'path': '/' + }, + + 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature': { + 'protocol': 'coap:', + 'slashes': true, + 'auth': 'u:p', + 'host': '[::192.9.5.5]:61616', + 'port': '61616', + 'hostname': '::192.9.5.5', + 'href': 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature', + 'search': '?n=Temperature', + 'query': 'n=Temperature', + 'pathname': '/.well-known/r', + 'path': '/.well-known/r?n=Temperature' + }, + + // empty port + 'http://example.com:': { + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'href': 'http://example.com/', + 'pathname': '/', + 'path': '/' + }, + + 'http://example.com:/a/b.html': { + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'href': 'http://example.com/a/b.html', + 'pathname': '/a/b.html', + 'path': '/a/b.html' + }, + + 'http://example.com:?a=b': { + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'href': 'http://example.com/?a=b', + 'search': '?a=b', + 'query': 'a=b', + 'pathname': '/', + 'path': '/?a=b' + }, + + 'http://example.com:#abc': { + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'href': 'http://example.com/#abc', + 'hash': '#abc', + 'pathname': '/', + 'path': '/' + }, + + 'http://[fe80::1]:/a/b?a=b#abc': { + 'protocol': 'http:', + 'slashes': true, + 'host': '[fe80::1]', + 'hostname': 'fe80::1', + 'href': 'http://[fe80::1]/a/b?a=b#abc', + 'search': '?a=b', + 'query': 'a=b', + 'hash': '#abc', + 'pathname': '/a/b', + 'path': '/a/b?a=b' + }, + + 'http://-lovemonsterz.tumblr.com/rss': { + 'protocol': 'http:', + 'slashes': true, + 'host': '-lovemonsterz.tumblr.com', + 'hostname': '-lovemonsterz.tumblr.com', + 'href': 'http://-lovemonsterz.tumblr.com/rss', + 'pathname': '/rss', + 'path': '/rss', + }, + + 'http://-lovemonsterz.tumblr.com:80/rss': { + 'protocol': 'http:', + 'slashes': true, + 'port': '80', + 'host': '-lovemonsterz.tumblr.com:80', + 'hostname': '-lovemonsterz.tumblr.com', + 'href': 'http://-lovemonsterz.tumblr.com:80/rss', + 'pathname': '/rss', + 'path': '/rss', + }, + + 'http://user:pass@-lovemonsterz.tumblr.com/rss': { + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pass', + 'host': '-lovemonsterz.tumblr.com', + 'hostname': '-lovemonsterz.tumblr.com', + 'href': 'http://user:pass@-lovemonsterz.tumblr.com/rss', + 'pathname': '/rss', + 'path': '/rss', + }, + + 'http://user:pass@-lovemonsterz.tumblr.com:80/rss': { + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pass', + 'port': '80', + 'host': '-lovemonsterz.tumblr.com:80', + 'hostname': '-lovemonsterz.tumblr.com', + 'href': 'http://user:pass@-lovemonsterz.tumblr.com:80/rss', + 'pathname': '/rss', + 'path': '/rss', + }, + + 'http://_jabber._tcp.google.com/test': { + 'protocol': 'http:', + 'slashes': true, + 'host': '_jabber._tcp.google.com', + 'hostname': '_jabber._tcp.google.com', + 'href': 'http://_jabber._tcp.google.com/test', + 'pathname': '/test', + 'path': '/test', + }, + + 'http://user:pass@_jabber._tcp.google.com/test': { + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pass', + 'host': '_jabber._tcp.google.com', + 'hostname': '_jabber._tcp.google.com', + 'href': 'http://user:pass@_jabber._tcp.google.com/test', + 'pathname': '/test', + 'path': '/test', + }, + + 'http://_jabber._tcp.google.com:80/test': { + 'protocol': 'http:', + 'slashes': true, + 'port': '80', + 'host': '_jabber._tcp.google.com:80', + 'hostname': '_jabber._tcp.google.com', + 'href': 'http://_jabber._tcp.google.com:80/test', + 'pathname': '/test', + 'path': '/test', + }, + + 'http://user:pass@_jabber._tcp.google.com:80/test': { + 'protocol': 'http:', + 'slashes': true, + 'auth': 'user:pass', + 'port': '80', + 'host': '_jabber._tcp.google.com:80', + 'hostname': '_jabber._tcp.google.com', + 'href': 'http://user:pass@_jabber._tcp.google.com:80/test', + 'pathname': '/test', + 'path': '/test', + }, + + 'http://x:1/\' <>"`/{}|\\^~`/': { + protocol: 'http:', + slashes: true, + host: 'x:1', + port: '1', + hostname: 'x', + pathname: '/%27%20%3C%3E%22%60/%7B%7D%7C%5C%5E~%60/', + path: '/%27%20%3C%3E%22%60/%7B%7D%7C%5C%5E~%60/', + href: 'http://x:1/%27%20%3C%3E%22%60/%7B%7D%7C%5C%5E~%60/' + }, + + 'http://a@b@c/': { + protocol: 'http:', + slashes: true, + auth: 'a@b', + host: 'c', + hostname: 'c', + href: 'http://a%40b@c/', + path: '/', + pathname: '/' + }, + + 'http://a@b?@c': { + protocol: 'http:', + slashes: true, + auth: 'a', + host: 'b', + hostname: 'b', + href: 'http://a@b/?@c', + path: '/?@c', + pathname: '/', + search: '?@c', + query: '@c' + }, + + 'http://a\r" \t\n<\'b:b@c\r\nd/e?f':{ + protocol: 'http:', + slashes: true, + auth: 'a\r" \t\n<\'b:b', + host: 'c', + port: null, + hostname: 'c', + hash: null, + search: '?f', + query: 'f', + pathname: '%0D%0Ad/e', + path: '%0D%0Ad/e?f', + href: 'http://a%0D%22%20%09%0A%3C\'b:b@c/%0D%0Ad/e?f' + } + +}; + +test('url.parse and url.format - main test', function (t) { + for (var u in parseTests) { + var actual = url.parse(u), + spaced = url.parse(' \t ' + u + '\n\t'); + expected = parseTests[u]; + + Object.keys(actual).forEach(function (i) { + if (expected[i] === undefined && actual[i] === null) { + expected[i] = null; + } + }); + + t.deepEqual(actual, expected); + t.deepEqual(spaced, expected); + + var expected = parseTests[u].href, + actual = url.format(parseTests[u]); + + t.equal(actual, expected, + 'format(' + u + ') == ' + u + '\nactual:' + actual); + } + t.end(); +}); + +var parseTestsWithQueryString = { + '/foo/bar?baz=quux#frag' : { + 'href': '/foo/bar?baz=quux#frag', + 'hash': '#frag', + 'search': '?baz=quux', + 'query': { + 'baz': 'quux' + }, + 'pathname': '/foo/bar', + 'path': '/foo/bar?baz=quux' + }, + 'http://example.com' : { + 'href': 'http://example.com/', + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'query': {}, + 'search': '', + 'pathname': '/', + 'path': '/' + } +}; + +test('url.parse - with qoutes', function (t) { + for (var u in parseTestsWithQueryString) { + var actual = url.parse(u, true); + var expected = parseTestsWithQueryString[u]; + for (var i in actual) { + if (actual[i] === null && expected[i] === undefined) { + expected[i] = null; + } + } + + t.deepEqual(actual, expected); + } + + t.end(); +}); + +// some extra formatting tests, just to verify +// that it'll format slightly wonky content to a valid url. +var formatTests = { + 'http://example.com?' : { + 'href': 'http://example.com/?', + 'protocol': 'http:', + 'slashes': true, + 'host': 'example.com', + 'hostname': 'example.com', + 'search': '?', + 'query': {}, + 'pathname': '/' + }, + 'http://example.com?foo=bar#frag' : { + 'href': 'http://example.com/?foo=bar#frag', + 'protocol': 'http:', + 'host': 'example.com', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?foo=bar', + 'query': 'foo=bar', + 'pathname': '/' + }, + 'http://example.com?foo=@bar#frag' : { + 'href': 'http://example.com/?foo=@bar#frag', + 'protocol': 'http:', + 'host': 'example.com', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?foo=@bar', + 'query': 'foo=@bar', + 'pathname': '/' + }, + 'http://example.com?foo=/bar/#frag' : { + 'href': 'http://example.com/?foo=/bar/#frag', + 'protocol': 'http:', + 'host': 'example.com', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?foo=/bar/', + 'query': 'foo=/bar/', + 'pathname': '/' + }, + 'http://example.com?foo=?bar/#frag' : { + 'href': 'http://example.com/?foo=?bar/#frag', + 'protocol': 'http:', + 'host': 'example.com', + 'hostname': 'example.com', + 'hash': '#frag', + 'search': '?foo=?bar/', + 'query': 'foo=?bar/', + 'pathname': '/' + }, + 'http://example.com#frag=?bar/#frag' : { + 'href': 'http://example.com/#frag=?bar/#frag', + 'protocol': 'http:', + 'host': 'example.com', + 'hostname': 'example.com', + 'hash': '#frag=?bar/#frag', + 'pathname': '/' + }, + 'http://google.com" onload="alert(42)/' : { + 'href': 'http://google.com/%22%20onload=%22alert(42)/', + 'protocol': 'http:', + 'host': 'google.com', + 'pathname': '/%22%20onload=%22alert(42)/' + }, + 'http://a.com/a/b/c?s#h' : { + 'href': 'http://a.com/a/b/c?s#h', + 'protocol': 'http', + 'host': 'a.com', + 'pathname': 'a/b/c', + 'hash': 'h', + 'search': 's' + }, + 'xmpp:isaacschlueter@jabber.org' : { + 'href': 'xmpp:isaacschlueter@jabber.org', + 'protocol': 'xmpp:', + 'host': 'jabber.org', + 'auth': 'isaacschlueter', + 'hostname': 'jabber.org' + }, + 'http://atpass:foo%40bar@127.0.0.1/' : { + 'href': 'http://atpass:foo%40bar@127.0.0.1/', + 'auth': 'atpass:foo@bar', + 'hostname': '127.0.0.1', + 'protocol': 'http:', + 'pathname': '/' + }, + 'http://atslash%2F%40:%2F%40@foo/' : { + 'href': 'http://atslash%2F%40:%2F%40@foo/', + 'auth': 'atslash/@:/@', + 'hostname': 'foo', + 'protocol': 'http:', + 'pathname': '/' + }, + 'svn+ssh://foo/bar': { + 'href': 'svn+ssh://foo/bar', + 'hostname': 'foo', + 'protocol': 'svn+ssh:', + 'pathname': '/bar', + 'slashes': true + }, + 'dash-test://foo/bar': { + 'href': 'dash-test://foo/bar', + 'hostname': 'foo', + 'protocol': 'dash-test:', + 'pathname': '/bar', + 'slashes': true + }, + 'dash-test:foo/bar': { + 'href': 'dash-test:foo/bar', + 'hostname': 'foo', + 'protocol': 'dash-test:', + 'pathname': '/bar' + }, + 'dot.test://foo/bar': { + 'href': 'dot.test://foo/bar', + 'hostname': 'foo', + 'protocol': 'dot.test:', + 'pathname': '/bar', + 'slashes': true + }, + 'dot.test:foo/bar': { + 'href': 'dot.test:foo/bar', + 'hostname': 'foo', + 'protocol': 'dot.test:', + 'pathname': '/bar' + }, + // ipv6 support + 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature': { + 'href': 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature', + 'protocol': 'coap:', + 'auth': 'u:p', + 'hostname': '::1', + 'port': '61616', + 'pathname': '/.well-known/r', + 'search': 'n=Temperature' + }, + 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton': { + 'href': 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton', + 'protocol': 'coap', + 'host': '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616', + 'pathname': '/s/stopButton' + }, + + // encode context-specific delimiters in path and query, but do not touch + // other non-delimiter chars like `%`. + // + + // `#`,`?` in path + '/path/to/%%23%3F+=&.txt?foo=theA1#bar' : { + href : '/path/to/%%23%3F+=&.txt?foo=theA1#bar', + pathname: '/path/to/%#?+=&.txt', + query: { + foo: 'theA1' + }, + hash: "#bar" + }, + + // `#`,`?` in path + `#` in query + '/path/to/%%23%3F+=&.txt?foo=the%231#bar' : { + href : '/path/to/%%23%3F+=&.txt?foo=the%231#bar', + pathname: '/path/to/%#?+=&.txt', + query: { + foo: 'the#1' + }, + hash: "#bar" + }, + + // `?` and `#` in path and search + 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag': { + href: 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag', + protocol: 'http:', + hostname: 'ex.com', + hash: '#frag', + search: '?abc=the#1?&foo=bar', + pathname: '/foo?100%m#r', + }, + + // `?` and `#` in search only + 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag': { + href: 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag', + protocol: 'http:', + hostname: 'ex.com', + hash: '#frag', + search: '?abc=the#1?&foo=bar', + pathname: '/fooA100%mBr', + } +}; + +test('url.format - main test', function (t) { + for (var u in formatTests) { + var expect = formatTests[u].href; + delete formatTests[u].href; + var actual = url.format(u); + var actualObj = url.format(formatTests[u]); + t.equal(actual, expect, + 'wonky format(' + u + ') == ' + expect + + '\nactual:' + actual); + t.equal(actualObj, expect, + 'wonky format(' + JSON.stringify(formatTests[u]) + + ') == ' + expect + + '\nactual: ' + actualObj); + } + t.end(); +}); + +/* + [from, path, expected] +*/ +var relativeTests = [ + ['/foo/bar/baz', 'quux', '/foo/bar/quux'], + ['/foo/bar/baz', 'quux/asdf', '/foo/bar/quux/asdf'], + ['/foo/bar/baz', 'quux/baz', '/foo/bar/quux/baz'], + ['/foo/bar/baz', '../quux/baz', '/foo/quux/baz'], + ['/foo/bar/baz', '/bar', '/bar'], + ['/foo/bar/baz/', 'quux', '/foo/bar/baz/quux'], + ['/foo/bar/baz/', 'quux/baz', '/foo/bar/baz/quux/baz'], + ['/foo/bar/baz', '../../../../../../../../quux/baz', '/quux/baz'], + ['/foo/bar/baz', '../../../../../../../quux/baz', '/quux/baz'], + ['foo/bar', '../../../baz', '../../baz'], + ['foo/bar/', '../../../baz', '../baz'], + ['http://example.com/b//c//d;p?q#blarg', 'https:#hash2', 'https:///#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https:/p/a/t/h?s#hash2', + 'https://p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https://u:p@h.com/p/a/t/h?s#hash2', + 'https://u:p@h.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https:/a/b/c/d', + 'https://a/b/c/d'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:#hash2', + 'http://example.com/b//c//d;p?q#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:/p/a/t/h?s#hash2', + 'http://example.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http://u:p@h.com/p/a/t/h?s#hash2', + 'http://u:p@h.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:/a/b/c/d', + 'http://example.com/a/b/c/d'], + ['/foo/bar/baz', '/../etc/passwd', '/etc/passwd'] +]; + +test('url.relative - main tests', function (t) { + relativeTests.forEach(function(relativeTest) { + var a = url.resolve(relativeTest[0], relativeTest[1]), + e = relativeTest[2]; + t.equal(a, e, + 'resolve(' + [relativeTest[0], relativeTest[1]] + ') == ' + e + + '\n actual=' + a); + }); + + t.end(); +}) + +test('url.parse - will throw on none string', function (t) { + // https://github.com/joyent/node/issues/568 + [ + undefined, + null, + true, + false, + 0.0, + 0, + [], + {} + ].forEach(function(val) { + t.throws(function() { url.parse(val); }, TypeError); + }); + + t.end(); +}); + +// +// Tests below taken from Chiron +// http://code.google.com/p/chironjs/source/browse/trunk/src/test/http/url.js +// +// Copyright (c) 2002-2008 Kris Kowal +// used with permission under MIT License +// +// Changes marked with @isaacs + +var bases = [ + 'http://a/b/c/d;p?q', + 'http://a/b/c/d;p?q=1/2', + 'http://a/b/c/d;p=1/2?q', + 'fred:///s//a/b/c', + 'http:///s//a/b/c' +]; + +//[to, from, result] +var relativeTests2 = [ + // http://lists.w3.org/Archives/Public/uri/2004Feb/0114.html + ['../c', 'foo:a/b', 'foo:c'], + ['foo:.', 'foo:a', 'foo:'], + ['/foo/../../../bar', 'zz:abc', 'zz:/bar'], + ['/foo/../bar', 'zz:abc', 'zz:/bar'], + // @isaacs Disagree. Not how web browsers resolve this. + ['foo/../../../bar', 'zz:abc', 'zz:bar'], + // ['foo/../../../bar', 'zz:abc', 'zz:../../bar'], // @isaacs Added + ['foo/../bar', 'zz:abc', 'zz:bar'], + ['zz:.', 'zz:abc', 'zz:'], + ['/.', bases[0], 'http://a/'], + ['/.foo', bases[0], 'http://a/.foo'], + ['.foo', bases[0], 'http://a/b/c/.foo'], + + // http://gbiv.com/protocols/uri/test/rel_examples1.html + // examples from RFC 2396 + ['g:h', bases[0], 'g:h'], + ['g', bases[0], 'http://a/b/c/g'], + ['./g', bases[0], 'http://a/b/c/g'], + ['g/', bases[0], 'http://a/b/c/g/'], + ['/g', bases[0], 'http://a/g'], + ['//g', bases[0], 'http://g/'], + // changed with RFC 2396bis + //('?y', bases[0], 'http://a/b/c/d;p?y'], + ['?y', bases[0], 'http://a/b/c/d;p?y'], + ['g?y', bases[0], 'http://a/b/c/g?y'], + // changed with RFC 2396bis + //('#s', bases[0], CURRENT_DOC_URI + '#s'], + ['#s', bases[0], 'http://a/b/c/d;p?q#s'], + ['g#s', bases[0], 'http://a/b/c/g#s'], + ['g?y#s', bases[0], 'http://a/b/c/g?y#s'], + [';x', bases[0], 'http://a/b/c/;x'], + ['g;x', bases[0], 'http://a/b/c/g;x'], + ['g;x?y#s' , bases[0], 'http://a/b/c/g;x?y#s'], + // changed with RFC 2396bis + //('', bases[0], CURRENT_DOC_URI], + ['', bases[0], 'http://a/b/c/d;p?q'], + ['.', bases[0], 'http://a/b/c/'], + ['./', bases[0], 'http://a/b/c/'], + ['..', bases[0], 'http://a/b/'], + ['../', bases[0], 'http://a/b/'], + ['../g', bases[0], 'http://a/b/g'], + ['../..', bases[0], 'http://a/'], + ['../../', bases[0], 'http://a/'], + ['../../g' , bases[0], 'http://a/g'], + ['../../../g', bases[0], ('http://a/../g', 'http://a/g')], + ['../../../../g', bases[0], ('http://a/../../g', 'http://a/g')], + // changed with RFC 2396bis + //('/./g', bases[0], 'http://a/./g'], + ['/./g', bases[0], 'http://a/g'], + // changed with RFC 2396bis + //('/../g', bases[0], 'http://a/../g'], + ['/../g', bases[0], 'http://a/g'], + ['g.', bases[0], 'http://a/b/c/g.'], + ['.g', bases[0], 'http://a/b/c/.g'], + ['g..', bases[0], 'http://a/b/c/g..'], + ['..g', bases[0], 'http://a/b/c/..g'], + ['./../g', bases[0], 'http://a/b/g'], + ['./g/.', bases[0], 'http://a/b/c/g/'], + ['g/./h', bases[0], 'http://a/b/c/g/h'], + ['g/../h', bases[0], 'http://a/b/c/h'], + ['g;x=1/./y', bases[0], 'http://a/b/c/g;x=1/y'], + ['g;x=1/../y', bases[0], 'http://a/b/c/y'], + ['g?y/./x', bases[0], 'http://a/b/c/g?y/./x'], + ['g?y/../x', bases[0], 'http://a/b/c/g?y/../x'], + ['g#s/./x', bases[0], 'http://a/b/c/g#s/./x'], + ['g#s/../x', bases[0], 'http://a/b/c/g#s/../x'], + ['http:g', bases[0], ('http:g', 'http://a/b/c/g')], + ['http:', bases[0], ('http:', bases[0])], + // not sure where this one originated + ['/a/b/c/./../../g', bases[0], 'http://a/a/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples2.html + // slashes in base URI's query args + ['g', bases[1], 'http://a/b/c/g'], + ['./g', bases[1], 'http://a/b/c/g'], + ['g/', bases[1], 'http://a/b/c/g/'], + ['/g', bases[1], 'http://a/g'], + ['//g', bases[1], 'http://g/'], + // changed in RFC 2396bis + //('?y', bases[1], 'http://a/b/c/?y'], + ['?y', bases[1], 'http://a/b/c/d;p?y'], + ['g?y', bases[1], 'http://a/b/c/g?y'], + ['g?y/./x' , bases[1], 'http://a/b/c/g?y/./x'], + ['g?y/../x', bases[1], 'http://a/b/c/g?y/../x'], + ['g#s', bases[1], 'http://a/b/c/g#s'], + ['g#s/./x' , bases[1], 'http://a/b/c/g#s/./x'], + ['g#s/../x', bases[1], 'http://a/b/c/g#s/../x'], + ['./', bases[1], 'http://a/b/c/'], + ['../', bases[1], 'http://a/b/'], + ['../g', bases[1], 'http://a/b/g'], + ['../../', bases[1], 'http://a/'], + ['../../g' , bases[1], 'http://a/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples3.html + // slashes in path params + // all of these changed in RFC 2396bis + ['g', bases[2], 'http://a/b/c/d;p=1/g'], + ['./g', bases[2], 'http://a/b/c/d;p=1/g'], + ['g/', bases[2], 'http://a/b/c/d;p=1/g/'], + ['g?y', bases[2], 'http://a/b/c/d;p=1/g?y'], + [';x', bases[2], 'http://a/b/c/d;p=1/;x'], + ['g;x', bases[2], 'http://a/b/c/d;p=1/g;x'], + ['g;x=1/./y', bases[2], 'http://a/b/c/d;p=1/g;x=1/y'], + ['g;x=1/../y', bases[2], 'http://a/b/c/d;p=1/y'], + ['./', bases[2], 'http://a/b/c/d;p=1/'], + ['../', bases[2], 'http://a/b/c/'], + ['../g', bases[2], 'http://a/b/c/g'], + ['../../', bases[2], 'http://a/b/'], + ['../../g' , bases[2], 'http://a/b/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples4.html + // double and triple slash, unknown scheme + ['g:h', bases[3], 'g:h'], + ['g', bases[3], 'fred:///s//a/b/g'], + ['./g', bases[3], 'fred:///s//a/b/g'], + ['g/', bases[3], 'fred:///s//a/b/g/'], + ['/g', bases[3], 'fred:///g'], // may change to fred:///s//a/g + ['//g', bases[3], 'fred://g'], // may change to fred:///s//g + ['//g/x', bases[3], 'fred://g/x'], // may change to fred:///s//g/x + ['///g', bases[3], 'fred:///g'], + ['./', bases[3], 'fred:///s//a/b/'], + ['../', bases[3], 'fred:///s//a/'], + ['../g', bases[3], 'fred:///s//a/g'], + + ['../../', bases[3], 'fred:///s//'], + ['../../g' , bases[3], 'fred:///s//g'], + ['../../../g', bases[3], 'fred:///s/g'], + // may change to fred:///s//a/../../../g + ['../../../../g', bases[3], 'fred:///g'], + + // http://gbiv.com/protocols/uri/test/rel_examples5.html + // double and triple slash, well-known scheme + ['g:h', bases[4], 'g:h'], + ['g', bases[4], 'http:///s//a/b/g'], + ['./g', bases[4], 'http:///s//a/b/g'], + ['g/', bases[4], 'http:///s//a/b/g/'], + ['/g', bases[4], 'http:///g'], // may change to http:///s//a/g + ['//g', bases[4], 'http://g/'], // may change to http:///s//g + ['//g/x', bases[4], 'http://g/x'], // may change to http:///s//g/x + ['///g', bases[4], 'http:///g'], + ['./', bases[4], 'http:///s//a/b/'], + ['../', bases[4], 'http:///s//a/'], + ['../g', bases[4], 'http:///s//a/g'], + ['../../', bases[4], 'http:///s//'], + ['../../g' , bases[4], 'http:///s//g'], + // may change to http:///s//a/../../g + ['../../../g', bases[4], 'http:///s/g'], + // may change to http:///s//a/../../../g + ['../../../../g', bases[4], 'http:///g'], + + // from Dan Connelly's tests in http://www.w3.org/2000/10/swap/uripath.py + ['bar:abc', 'foo:xyz', 'bar:abc'], + ['../abc', 'http://example/x/y/z', 'http://example/x/abc'], + ['http://example/x/abc', 'http://example2/x/y/z', 'http://example/x/abc'], + ['../r', 'http://ex/x/y/z', 'http://ex/x/r'], + ['q/r', 'http://ex/x/y', 'http://ex/x/q/r'], + ['q/r#s', 'http://ex/x/y', 'http://ex/x/q/r#s'], + ['q/r#s/t', 'http://ex/x/y', 'http://ex/x/q/r#s/t'], + ['ftp://ex/x/q/r', 'http://ex/x/y', 'ftp://ex/x/q/r'], + ['', 'http://ex/x/y', 'http://ex/x/y'], + ['', 'http://ex/x/y/', 'http://ex/x/y/'], + ['', 'http://ex/x/y/pdq', 'http://ex/x/y/pdq'], + ['z/', 'http://ex/x/y/', 'http://ex/x/y/z/'], + ['#Animal', + 'file:/swap/test/animal.rdf', + 'file:/swap/test/animal.rdf#Animal'], + ['../abc', 'file:/e/x/y/z', 'file:/e/x/abc'], + ['/example/x/abc', 'file:/example2/x/y/z', 'file:/example/x/abc'], + ['../r', 'file:/ex/x/y/z', 'file:/ex/x/r'], + ['/r', 'file:/ex/x/y/z', 'file:/r'], + ['q/r', 'file:/ex/x/y', 'file:/ex/x/q/r'], + ['q/r#s', 'file:/ex/x/y', 'file:/ex/x/q/r#s'], + ['q/r#', 'file:/ex/x/y', 'file:/ex/x/q/r#'], + ['q/r#s/t', 'file:/ex/x/y', 'file:/ex/x/q/r#s/t'], + ['ftp://ex/x/q/r', 'file:/ex/x/y', 'ftp://ex/x/q/r'], + ['', 'file:/ex/x/y', 'file:/ex/x/y'], + ['', 'file:/ex/x/y/', 'file:/ex/x/y/'], + ['', 'file:/ex/x/y/pdq', 'file:/ex/x/y/pdq'], + ['z/', 'file:/ex/x/y/', 'file:/ex/x/y/z/'], + ['file://meetings.example.com/cal#m1', + 'file:/devel/WWW/2000/10/swap/test/reluri-1.n3', + 'file://meetings.example.com/cal#m1'], + ['file://meetings.example.com/cal#m1', + 'file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3', + 'file://meetings.example.com/cal#m1'], + ['./#blort', 'file:/some/dir/foo', 'file:/some/dir/#blort'], + ['./#', 'file:/some/dir/foo', 'file:/some/dir/#'], + // Ryan Lee + ['./', 'http://example/x/abc.efg', 'http://example/x/'], + + + // Graham Klyne's tests + // http://www.ninebynine.org/Software/HaskellUtils/Network/UriTest.xls + // 01-31 are from Connelly's cases + + // 32-49 + ['./q:r', 'http://ex/x/y', 'http://ex/x/q:r'], + ['./p=q:r', 'http://ex/x/y', 'http://ex/x/p=q:r'], + ['?pp/rr', 'http://ex/x/y?pp/qq', 'http://ex/x/y?pp/rr'], + ['y/z', 'http://ex/x/y?pp/qq', 'http://ex/x/y/z'], + ['local/qual@domain.org#frag', + 'mailto:local', + 'mailto:local/qual@domain.org#frag'], + ['more/qual2@domain2.org#frag', + 'mailto:local/qual1@domain1.org', + 'mailto:local/more/qual2@domain2.org#frag'], + ['y?q', 'http://ex/x/y?q', 'http://ex/x/y?q'], + ['/x/y?q', 'http://ex?p', 'http://ex/x/y?q'], + ['c/d', 'foo:a/b', 'foo:a/c/d'], + ['/c/d', 'foo:a/b', 'foo:/c/d'], + ['', 'foo:a/b?c#d', 'foo:a/b?c'], + ['b/c', 'foo:a', 'foo:b/c'], + ['../b/c', 'foo:/a/y/z', 'foo:/a/b/c'], + ['./b/c', 'foo:a', 'foo:b/c'], + ['/./b/c', 'foo:a', 'foo:/b/c'], + ['../../d', 'foo://a//b/c', 'foo://a/d'], + ['.', 'foo:a', 'foo:'], + ['..', 'foo:a', 'foo:'], + + // 50-57[cf. TimBL comments -- + // http://lists.w3.org/Archives/Public/uri/2003Feb/0028.html, + // http://lists.w3.org/Archives/Public/uri/2003Jan/0008.html) + ['abc', 'http://example/x/y%2Fz', 'http://example/x/abc'], + ['../../x%2Fabc', 'http://example/a/x/y/z', 'http://example/a/x%2Fabc'], + ['../x%2Fabc', 'http://example/a/x/y%2Fz', 'http://example/a/x%2Fabc'], + ['abc', 'http://example/x%2Fy/z', 'http://example/x%2Fy/abc'], + ['q%3Ar', 'http://ex/x/y', 'http://ex/x/q%3Ar'], + ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'], + ['/x%2Fabc', 'http://example/x/y/z', 'http://example/x%2Fabc'], + ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'], + + // 70-77 + ['local2@domain2', 'mailto:local1@domain1?query1', 'mailto:local2@domain2'], + ['local2@domain2?query2', + 'mailto:local1@domain1', + 'mailto:local2@domain2?query2'], + ['local2@domain2?query2', + 'mailto:local1@domain1?query1', + 'mailto:local2@domain2?query2'], + ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'], + ['local@domain?query2', 'mailto:?query1', 'mailto:local@domain?query2'], + ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'], + ['http://example/a/b?c/../d', 'foo:bar', 'http://example/a/b?c/../d'], + ['http://example/a/b#c/../d', 'foo:bar', 'http://example/a/b#c/../d'], + + // 82-88 + // @isaacs Disagree. Not how browsers do it. + // ['http:this', 'http://example.org/base/uri', 'http:this'], + // @isaacs Added + ['http:this', 'http://example.org/base/uri', 'http://example.org/base/this'], + ['http:this', 'http:base', 'http:this'], + ['.//g', 'f:/a', 'f://g'], + ['b/c//d/e', 'f://example.org/base/a', 'f://example.org/base/b/c//d/e'], + ['m2@example.ord/c2@example.org', + 'mid:m@example.ord/c@example.org', + 'mid:m@example.ord/m2@example.ord/c2@example.org'], + ['mini1.xml', + 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/', + 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml'], + ['../b/c', 'foo:a/y/z', 'foo:a/b/c'], + + //changeing auth + ['http://diff:auth@www.example.com', + 'http://asdf:qwer@www.example.com', + 'http://diff:auth@www.example.com/'] +]; + +test('url.resolve - main tests', function (t) { + relativeTests2.forEach(function(relativeTest) { + var a = url.resolve(relativeTest[1], relativeTest[0]), + e = relativeTest[2]; + t.equal(a, e, + 'resolve(' + [relativeTest[1], relativeTest[0]] + ') == ' + e + + '\n actual=' + a); + }); + + t.end(); +}); + +//if format and parse are inverse operations then +//resolveObject(parse(x), y) == parse(resolve(x, y)) + +test('url.resolveObject - inverse operations', function (t) { + //format: [from, path, expected] + relativeTests.forEach(function(relativeTest) { + var actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]), + expected = url.parse(relativeTest[2]); + + + t.deepEqual(actual, expected); + + expected = relativeTest[2]; + actual = url.format(actual); + + t.equal(actual, expected, + 'format(' + actual + ') == ' + expected + '\nactual:' + actual); + }); + + t.end(); +}); + +//format: [to, from, result] +// the test: ['.//g', 'f:/a', 'f://g'] is a fundamental problem +// url.parse('f:/a') does not have a host +// url.resolve('f:/a', './/g') does not have a host because you have moved +// down to the g directory. i.e. f: //g, however when this url is parsed +// f:// will indicate that the host is g which is not the case. +// it is unclear to me how to keep this information from being lost +// it may be that a pathname of ////g should collapse to /g but this seems +// to be a lot of work for an edge case. Right now I remove the test +test('url - fundamental host problem', function (t) { + if (relativeTests2[181][0] === './/g' && + relativeTests2[181][1] === 'f:/a' && + relativeTests2[181][2] === 'f://g') { + relativeTests2.splice(181, 1); + } + relativeTests2.forEach(function(relativeTest) { + var actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]), + expected = url.parse(relativeTest[2]); + + t.deepEqual(actual, expected); + + var expected = relativeTest[2], + actual = url.format(actual); + + t.equal(actual, expected, + 'format(' + relativeTest[1] + ') == ' + expected + + '\nactual:' + actual); + }); + + t.end(); +}); From bc5f517b4331f440e5a87e2939c5aac5f32e406e Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Tue, 20 Aug 2013 17:59:35 +0200 Subject: [PATCH 26/47] [test] add all assert tests --- test/browser/assert-simple.js | 309 ++++++++++++++++++++++++++++++++-- 1 file changed, 298 insertions(+), 11 deletions(-) diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js index 5e26233..52335c2 100644 --- a/test/browser/assert-simple.js +++ b/test/browser/assert-simple.js @@ -3,32 +3,256 @@ var test = require('tape'); var assert = require('assert'); -test('assert and assert.ok is the same', function (t) { - t.equal(assert, assert.ok); +function makeBlock(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + return f.apply(this, args); + }; +} + +test('assert.ok', function (t) { + t.throws(makeBlock(assert, false), assert.AssertionError, 'ok(false)'); + + t.doesNotThrow(makeBlock(assert, true), assert.AssertionError, 'ok(true)'); + + t.doesNotThrow(makeBlock(assert, 'test', 'ok(\'test\')')); + + t.throws(makeBlock(assert.ok, false), + assert.AssertionError, 'ok(false)'); + + t.doesNotThrow(makeBlock(assert.ok, true), + assert.AssertionError, 'ok(true)'); + + t.doesNotThrow(makeBlock(assert.ok, 'test'), 'ok(\'test\')'); + + t.end(); +}); + +test('assert.equal', function (t) { + t.throws(makeBlock(assert.equal, true, false), assert.AssertionError, 'equal'); + + t.doesNotThrow(makeBlock(assert.equal, null, null), 'equal'); + + t.doesNotThrow(makeBlock(assert.equal, undefined, undefined), 'equal'); + + t.doesNotThrow(makeBlock(assert.equal, null, undefined), 'equal'); + + t.doesNotThrow(makeBlock(assert.equal, true, true), 'equal'); + + t.doesNotThrow(makeBlock(assert.equal, 2, '2'), 'equal'); + + t.doesNotThrow(makeBlock(assert.notEqual, true, false), 'notEqual'); + + t.throws(makeBlock(assert.notEqual, true, true), + assert.AssertionError, 'notEqual'); + t.end(); +}); + +test('assert.strictEqual', function (t) { + t.throws(makeBlock(assert.strictEqual, 2, '2'), + assert.AssertionError, 'strictEqual'); + + t.throws(makeBlock(assert.strictEqual, null, undefined), + assert.AssertionError, 'strictEqual'); + + t.doesNotThrow(makeBlock(assert.notStrictEqual, 2, '2'), 'notStrictEqual'); + + t.end(); +}); + +test('assert.deepEqual - 7.2', function (t) { + t.doesNotThrow(makeBlock(assert.deepEqual, new Date(2000, 3, 14), + new Date(2000, 3, 14)), 'deepEqual date'); + + t.throws(makeBlock(assert.deepEqual, new Date(), new Date(2000, 3, 14)), + assert.AssertionError, + 'deepEqual date'); + + t.end(); +}); + +test('assert.deepEqual - 7.3', function (t) { + t.doesNotThrow(makeBlock(assert.deepEqual, /a/, /a/)); + t.doesNotThrow(makeBlock(assert.deepEqual, /a/g, /a/g)); + t.doesNotThrow(makeBlock(assert.deepEqual, /a/i, /a/i)); + t.doesNotThrow(makeBlock(assert.deepEqual, /a/m, /a/m)); + t.doesNotThrow(makeBlock(assert.deepEqual, /a/igm, /a/igm)); + t.throws(makeBlock(assert.deepEqual, /ab/, /a/)); + t.throws(makeBlock(assert.deepEqual, /a/g, /a/)); + t.throws(makeBlock(assert.deepEqual, /a/i, /a/)); + t.throws(makeBlock(assert.deepEqual, /a/m, /a/)); + t.throws(makeBlock(assert.deepEqual, /a/igm, /a/im)); + + var re1 = /a/; + re1.lastIndex = 3; + t.throws(makeBlock(assert.deepEqual, re1, /a/)); + + t.end(); +}); + +test('assert.deepEqual - 7.4', function (t) { + t.doesNotThrow(makeBlock(assert.deepEqual, 4, '4'), 'deepEqual == check'); + t.doesNotThrow(makeBlock(assert.deepEqual, true, 1), 'deepEqual == check'); + t.throws(makeBlock(assert.deepEqual, 4, '5'), + assert.AssertionError, + 'deepEqual == check'); + + t.end(); +}); + +test('assert.deepEqual - 7.5', function (t) { + // having the same number of owned properties && the same set of keys + t.doesNotThrow(makeBlock(assert.deepEqual, {a: 4}, {a: 4})); + t.doesNotThrow(makeBlock(assert.deepEqual, {a: 4, b: '2'}, {a: 4, b: '2'})); + t.doesNotThrow(makeBlock(assert.deepEqual, [4], ['4'])); + t.throws(makeBlock(assert.deepEqual, {a: 4}, {a: 4, b: true}), + assert.AssertionError); + t.doesNotThrow(makeBlock(assert.deepEqual, ['a'], {0: 'a'})); + //(although not necessarily the same order), + t.doesNotThrow(makeBlock(assert.deepEqual, {a: 4, b: '1'}, {b: '1', a: 4})); + var a1 = [1, 2, 3]; + var a2 = [1, 2, 3]; + a1.a = 'test'; + a1.b = true; + a2.b = true; + a2.a = 'test'; + t.throws(makeBlock(assert.deepEqual, Object.keys(a1), Object.keys(a2)), + assert.AssertionError); + t.doesNotThrow(makeBlock(assert.deepEqual, a1, a2)); + + t.end(); +}); + +test('assert.deepEqual - instances', function (t) { + // having an identical prototype property + var nbRoot = { + toString: function() { return this.first + ' ' + this.last; } + }; + + function nameBuilder(first, last) { + this.first = first; + this.last = last; + return this; + } + nameBuilder.prototype = nbRoot; + + function nameBuilder2(first, last) { + this.first = first; + this.last = last; + return this; + } + nameBuilder2.prototype = nbRoot; + + var nb1 = new nameBuilder('Ryan', 'Dahl'); + var nb2 = new nameBuilder2('Ryan', 'Dahl'); + + t.doesNotThrow(makeBlock(assert.deepEqual, nb1, nb2)); + + nameBuilder2.prototype = Object; + nb2 = new nameBuilder2('Ryan', 'Dahl'); + t.throws(makeBlock(assert.deepEqual, nb1, nb2), assert.AssertionError); + + // String literal + object blew up my implementation... + t.throws(makeBlock(assert.deepEqual, 'a', {}), assert.AssertionError); + t.end(); }); -test('assert.ok - throw', function (t) { +function thrower(errorConstructor) { + throw new errorConstructor('test'); +} + +test('assert - Testing the throwing', function (t) { + var aethrow = makeBlock(thrower, assert.AssertionError); + aethrow = makeBlock(thrower, assert.AssertionError); + + // the basic calls work + t.throws(makeBlock(thrower, assert.AssertionError), + assert.AssertionError, 'message'); + t.throws(makeBlock(thrower, assert.AssertionError), assert.AssertionError); + t.throws(makeBlock(thrower, assert.AssertionError)); + + // if not passing an error, catch all. + t.throws(makeBlock(thrower, TypeError)); + + // when passing a type, only catch errors of the appropriate type + var threw = false; + try { + assert.throws(makeBlock(thrower, TypeError), assert.AssertionError); + } catch (e) { + threw = true; + t.ok(e instanceof TypeError, 'type'); + } + t.equal(true, threw, + 'a.throws with an explicit error is eating extra errors', + assert.AssertionError); + threw = false; + + // doesNotThrow should pass through all errors + try { + assert.doesNotThrow(makeBlock(thrower, TypeError), assert.AssertionError); + } catch (e) { + threw = true; + t.ok(e instanceof TypeError); + } + t.equal(true, threw, + 'a.doesNotThrow with an explicit error is eating extra errors'); + + // key difference is that throwing our correct error makes an assertion error try { - assert.ok(false); - t.ok(false, 'catch not reached'); + assert.doesNotThrow(makeBlock(thrower, TypeError), TypeError); } catch (e) { - t.ok(true, 'catch reached'); + threw = true; + t.ok(e instanceof assert.AssertionError); } + t.equal(true, threw, + 'a.doesNotThrow is not catching type matching errors'); + t.end(); }); -test('assert.ok - pass', function (t) { +test('assert.ifError', function (t) { + t.throws(function() {assert.ifError(new Error('test error'))}); + t.doesNotThrow(function() {assert.ifError(null)}); + t.doesNotThrow(function() {assert.ifError()}); + + t.end(); +}); + +test('assert - make sure that validating using constructor really works', function (t) { + var threw = false; try { - assert.ok(true); - t.ok(true, 'catch not reached'); + assert.throws( + function() { + throw ({}); + }, + Array + ); } catch (e) { - t.ok(false, 'catch reached'); + threw = true; } + t.ok(threw, 'wrong constructor validation'); + t.end(); }); -test('assert.deepEqual Make sure deepEqual doesn\'t loop forever on circular refs', function (t) { +test('assert - use a RegExp to validate error message', function (t) { + t.throws(makeBlock(thrower, TypeError), /test/); + + t.end(); +}); + +test('assert - se a fn to validate error object', function (t) { + t.throws(makeBlock(thrower, TypeError), function(err) { + if ((err instanceof TypeError) && /test/.test(err)) { + return true; + } + }); + + t.end(); +}); + +test('assert - Make sure deepEqual doesn\'t loop forever on circular refs', function (t) { var b = {}; b.b = b; @@ -43,5 +267,68 @@ test('assert.deepEqual Make sure deepEqual doesn\'t loop forever on circular ref } t.ok(gotError); + + t.end(); +}); + + +test('assert - test assertion message', function (t) { + function testAssertionMessage(actual, expected) { + try { + assert.equal(actual, ''); + } catch (e) { + t.equal(e.toString(), + ['AssertionError:', expected, '==', '""'].join(' ')); + } + } + testAssertionMessage(undefined, '"undefined"'); + testAssertionMessage(null, 'null'); + testAssertionMessage(true, 'true'); + testAssertionMessage(false, 'false'); + testAssertionMessage(0, '0'); + testAssertionMessage(100, '100'); + testAssertionMessage(NaN, '"NaN"'); + testAssertionMessage(Infinity, '"Infinity"'); + testAssertionMessage(-Infinity, '"-Infinity"'); + testAssertionMessage('', '""'); + testAssertionMessage('foo', '"foo"'); + testAssertionMessage([], '[]'); + testAssertionMessage([1, 2, 3], '[1,2,3]'); + testAssertionMessage(/a/, '"/a/"'); + testAssertionMessage(/abc/gim, '"/abc/gim"'); + testAssertionMessage(function f() {}, '"function f() {}"'); + testAssertionMessage({}, '{}'); + testAssertionMessage({a: undefined, b: null}, '{"a":"undefined","b":null}'); + testAssertionMessage({a: NaN, b: Infinity, c: -Infinity}, + '{"a":"NaN","b":"Infinity","c":"-Infinity"}'); + + t.end(); +}); + +test('assert - regressions from node.js testcase', function (t) { + var threw = false; + + try { + assert.throws(function () { + assert.ifError(null); + }); + } catch (e) { + threw = true; + t.equal(e.message, 'Missing expected exception..'); + } + t.ok(threw); + + try { + assert.equal(1, 2); + } catch (e) { + t.equal(e.toString().split('\n')[0], 'AssertionError: 1 == 2'); + } + + try { + assert.equal(1, 2, 'oh no'); + } catch (e) { + t.equal(e.toString().split('\n')[0], 'AssertionError: oh no'); + } + t.end(); }); From 7accf5f5fef6cbe0378ad6775bb2b71cb4c18b10 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 12:29:04 +0200 Subject: [PATCH 27/47] [fix] shim forEach in util.js --- builtin/util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/util.js b/builtin/util.js index ea2ef9d..32c580c 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -126,7 +126,7 @@ function stylizeNoColor(str, styleType) { function arrayToHash(array) { var hash = {}; - array.forEach(function(val, idx) { + shims.forEach(array, function(val, idx) { hash[val] = true; }); @@ -274,7 +274,7 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { output.push(''); } } - keys.forEach(function(key) { + shims.forEach(keys, function(key) { if (!key.match(/^\d+$/)) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); From cfdf498decdf8493f89e62fa65be9fd9aefa5672 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 12:29:39 +0200 Subject: [PATCH 28/47] [feature] add _shims.js file - IE9 support confirmed --- builtin/_shims.js | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 builtin/_shims.js diff --git a/builtin/_shims.js b/builtin/_shims.js new file mode 100644 index 0000000..f08ae11 --- /dev/null +++ b/builtin/_shims.js @@ -0,0 +1,104 @@ + + +// +// The shims in this file are not fully implemented shims for the ES5 +// features, but do work for the particular usecases there is in +// the other modules. +// + +var toString = Object.prototype.toString; +var hasOwnProperty = Object.prototype.hasOwnProperty; + +// Array.isArray is supported in IE9 +function isArray(xs) { + return toString.call(xs) === '[object Array]'; +} +exports.isArray = typeof Array.isArray === 'function' ? Array.isArray : isArray; + +// Array.prototype.indexOf is supported in IE9 +exports.indexOf = function indexOf(xs, x) { + if (xs.indexOf) return xs.indexOf(x); + for (var i = 0; i < xs.length; i++) { + if (x === xs[i]) return i; + } + return -1; +}; + +// Array.prototype.filter is supported in IE9 +exports.filter = function filter(xs, fn) { + if (xs.filter) return xs.filter(fn); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (fn(xs[i], i, xs)) res.push(xs[i]); + } + return res; +}; + +// Array.prototype.forEach is supported in IE9 +exports.forEach = function forEach(xs, fn) { + if (xs.forEach) return xs.forEach(fn); + for (var i = 0; i < xs.length; i++) { + fn(xs[i], i, xs); + } +}; + +// Object.create is supported in IE9 +function create(prototype, properties) { + var object; + if (prototype === null) { + object = { '__proto__' : null }; + } + else { + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; +} +exports.create = typeof Object.create === 'function' ? Object.create : create; + +// Object.keys and Object.getOwnPropertyNames is supported in IE9 however +// they do show a description and number property on Error objects +function keysShim(object) { + var result = []; + for (var name in object) { + if (hasOwnProperty.call(object, name)) { + result.push(name); + } + } + return result; +} + +var keys = typeof Object.keys === 'function' ? Object.keys : keysShim; +var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ? + Object.getOwnPropertyNames : keysShim; + +if (new Error().hasOwnProperty('description')) { + var ERROR_PROPERTY_FILTER = function (obj, array) { + if (toString.call(obj) === '[object Error]') { + array = exports.filter(array, function (name) { + return name !== 'description' && name !== 'number' && name !== 'message'; + }); + } + return array; + }; + + exports.keys = function (object) { + return ERROR_PROPERTY_FILTER(object, keys(object)); + }; + exports.getOwnPropertyNames = function (object) { + return ERROR_PROPERTY_FILTER(object, getOwnPropertyNames(object)); + }; +} else { + exports.keys = keys; + exports.getOwnPropertyNames = getOwnPropertyNames; +} From 40773ecdc873b5c6c81427cd7a0eb9bc15559a9e Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 13:31:10 +0200 Subject: [PATCH 29/47] [fix] add IE8 support for util.js --- builtin/_shims.js | 60 ++++++++++++++++++++++++++++++++++++++++- builtin/util.js | 8 +++--- test/browser/util-is.js | 9 ++++--- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index f08ae11..2ca91fa 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -42,6 +42,30 @@ exports.forEach = function forEach(xs, fn) { } }; +// Array.prototype.reduce is supported in IE9 +exports.reduce = function reduce(array, callback, opt_initialValue) { + if (array.reduce) return array.reduce(callback, opt_initialValue); + var value, isValueSet = false; + + if (2 < arguments.length) { + value = opt_initialValue; + isValueSet = true; + } + for (var i = 0, l = array.length; l > i; ++i) { + if (array.hasOwnProperty(i)) { + if (isValueSet) { + value = callback(value, array[i], i, array); + } + else { + value = array[i]; + isValueSet = true; + } + } + } + + return value; +} + // Object.create is supported in IE9 function create(prototype, properties) { var object; @@ -78,9 +102,20 @@ function keysShim(object) { return result; } +// getOwnPropertyNames is almost the same as Object.keys one key feature +// is that it returns hidden properties, since that can't be implemented, +// this feature gets reduced so it just shows the length property on arrays +function propertyShim(object) { + var result = keysShim(object); + if (exports.isArray(object) && exports.indexOf(object, 'length') === -1) { + result.push('length'); + } + return result; +} + var keys = typeof Object.keys === 'function' ? Object.keys : keysShim; var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ? - Object.getOwnPropertyNames : keysShim; + Object.getOwnPropertyNames : propertyShim; if (new Error().hasOwnProperty('description')) { var ERROR_PROPERTY_FILTER = function (obj, array) { @@ -102,3 +137,26 @@ if (new Error().hasOwnProperty('description')) { exports.keys = keys; exports.getOwnPropertyNames = getOwnPropertyNames; } + +// Object.getOwnPropertyDescriptor - supported in IE8 but only on dom elements +function valueObject(value, key) { + return { value: value[key] }; +} + +if (typeof Object.getOwnPropertyDescriptor === 'function') { + try { + Object.getOwnPropertyDescriptor({'a': 1}, 'a'); + exports.getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + } catch (e) { + // IE8 dom element issue - use a try catch and default to valueObject + exports.getOwnPropertyDescriptor = function (value, key) { + try { + return Object.getOwnPropertyDescriptor(value, key); + } catch (e) { + return valueObject(value, key); + } + }; + } +} else { + exports.getOwnPropertyDescriptor = valueObject; +} diff --git a/builtin/util.js b/builtin/util.js index 32c580c..5dbd6a0 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -274,6 +274,7 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { output.push(''); } } + shims.forEach(keys, function(key) { if (!key.match(/^\d+$/)) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, @@ -286,7 +287,7 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + desc = shims.getOwnPropertyDescriptor(value, key) || { value: value[key] }; if (desc.get) { if (desc.set) { str = ctx.stylize('[Getter/Setter]', 'special'); @@ -298,11 +299,12 @@ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { str = ctx.stylize('[Setter]', 'special'); } } + if (!hasOwnProperty(visibleKeys, key)) { name = '[' + key + ']'; } if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { + if (shims.indexOf(ctx.seen, desc.value) < 0) { if (isNull(recurseTimes)) { str = formatValue(ctx, desc.value, null); } else { @@ -345,7 +347,7 @@ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { function reduceToSingleString(output, base, braces) { var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { + var length = shims.reduce(output, function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; diff --git a/test/browser/util-is.js b/test/browser/util-is.js index a136b99..34d287d 100644 --- a/test/browser/util-is.js +++ b/test/browser/util-is.js @@ -2,6 +2,7 @@ var test = require('tape'); var util = require('util'); +var shims = require('../../builtin/_shims.js'); test('util.isArray', function (t) { t.equal(true, util.isArray([])); @@ -13,7 +14,7 @@ test('util.isArray', function (t) { t.equal(false, util.isArray({ push: function() {} })); t.equal(false, util.isArray(/regexp/)); t.equal(false, util.isArray(new Error())); - t.equal(false, util.isArray(Object.create(Array.prototype))); + t.equal(false, util.isArray(shims.create(Array.prototype))); t.end(); }); @@ -24,7 +25,7 @@ test('util.isRegExp', function (t) { t.equal(false, util.isRegExp({})); t.equal(false, util.isRegExp([])); t.equal(false, util.isRegExp(new Date())); - t.equal(false, util.isRegExp(Object.create(RegExp.prototype))); + t.equal(false, util.isRegExp(shims.create(RegExp.prototype))); t.end(); }); @@ -35,7 +36,7 @@ test('util.isDate', function (t) { t.equal(false, util.isDate({})); t.equal(false, util.isDate([])); t.equal(false, util.isDate(new Error())); - t.equal(false, util.isDate(Object.create(Date.prototype))); + t.equal(false, util.isDate(shims.create(Date.prototype))); t.end(); }); @@ -46,7 +47,7 @@ test('util.isError', function (t) { t.equal(false, util.isError({})); t.equal(false, util.isError({ name: 'Error', message: '' })); t.equal(false, util.isError([])); - t.equal(false, util.isError(Object.create(Error.prototype))); + t.equal(false, util.isError(shims.create(Error.prototype))); t.end(); }); From b9b8e1f92d8ef984cbe17355605ce0d8e96f1309 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 13:51:21 +0200 Subject: [PATCH 30/47] [fix] add IE8 support for assert.js --- builtin/_shims.js | 12 ++++++++++++ test/browser/assert-simple.js | 5 +++-- test/browser/util-is.js | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index 2ca91fa..ad6b97b 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -92,7 +92,15 @@ exports.create = typeof Object.create === 'function' ? Object.create : create; // Object.keys and Object.getOwnPropertyNames is supported in IE9 however // they do show a description and number property on Error objects +function notObject(object) { + return ((typeof object != "object" && typeof object != "function") || object === null); +} + function keysShim(object) { + if (notObject(object)) { + throw new TypeError("Object.keys called on a non-object"); + } + var result = []; for (var name in object) { if (hasOwnProperty.call(object, name)) { @@ -106,6 +114,10 @@ function keysShim(object) { // is that it returns hidden properties, since that can't be implemented, // this feature gets reduced so it just shows the length property on arrays function propertyShim(object) { + if (notObject(object)) { + throw new TypeError("Object.getOwnPropertyNames called on a non-object"); + } + var result = keysShim(object); if (exports.isArray(object) && exports.indexOf(object, 'length') === -1) { result.push('length'); diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js index 52335c2..da1d6b9 100644 --- a/test/browser/assert-simple.js +++ b/test/browser/assert-simple.js @@ -1,5 +1,7 @@ + var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var assert = require('assert'); @@ -116,7 +118,7 @@ test('assert.deepEqual - 7.5', function (t) { a1.b = true; a2.b = true; a2.a = 'test'; - t.throws(makeBlock(assert.deepEqual, Object.keys(a1), Object.keys(a2)), + t.throws(makeBlock(assert.deepEqual, shims.keys(a1), shims.keys(a2)), assert.AssertionError); t.doesNotThrow(makeBlock(assert.deepEqual, a1, a2)); @@ -295,7 +297,6 @@ test('assert - test assertion message', function (t) { testAssertionMessage([], '[]'); testAssertionMessage([1, 2, 3], '[1,2,3]'); testAssertionMessage(/a/, '"/a/"'); - testAssertionMessage(/abc/gim, '"/abc/gim"'); testAssertionMessage(function f() {}, '"function f() {}"'); testAssertionMessage({}, '{}'); testAssertionMessage({a: undefined, b: null}, '{"a":"undefined","b":null}'); diff --git a/test/browser/util-is.js b/test/browser/util-is.js index 34d287d..5ba4a9d 100644 --- a/test/browser/util-is.js +++ b/test/browser/util-is.js @@ -1,8 +1,8 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var util = require('util'); -var shims = require('../../builtin/_shims.js'); test('util.isArray', function (t) { t.equal(true, util.isArray([])); From 5d45c6534b5f1e8da8af749767744bacdf71d244 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 14:24:34 +0200 Subject: [PATCH 31/47] [fix] add IE8 support for path.js --- builtin/path.js | 2 +- test/browser/path-simple.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/builtin/path.js b/builtin/path.js index aadb390..a4b6538 100644 --- a/builtin/path.js +++ b/builtin/path.js @@ -75,7 +75,7 @@ exports.resolve = function() { // posix version exports.normalize = function(path) { var isAbsolute = exports.isAbsolute(path), - trailingSlash = path.substr(-1) === '/'; + trailingSlash = shims.substr(path, -1) === '/'; // Normalize the path path = normalizeArray(shims.filter(path.split('/'), function(p) { diff --git a/test/browser/path-simple.js b/test/browser/path-simple.js index 1763fb4..8a5b484 100644 --- a/test/browser/path-simple.js +++ b/test/browser/path-simple.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var path = require('path'); @@ -134,14 +135,14 @@ test('path.join', function (t) { ]; // Run the join tests. - joinTests.forEach(function(test) { + shims.forEach(joinTests, function(test) { var actual = path.join.apply(path, test[0]); var expected = test[1]; t.equal(actual, expected); }); var joinThrowTests = [true, false, 7, null, {}, undefined, [], NaN]; - joinThrowTests.forEach(function(test) { + shims.forEach(joinThrowTests, function(test) { t.throws(function() { path.join(test); }, TypeError); @@ -173,7 +174,7 @@ test('path.resolve', function (t) { [['.'], process.cwd()], [['/some/dir', '.', '/absolute/'], '/absolute']]; - resolveTests.forEach(function(test) { + shims.forEach(resolveTests, function(test) { var actual = path.resolve.apply(path, test[0]); var expected = test[1]; t.equal(actual, expected); @@ -201,7 +202,7 @@ test('path.relative', function (t) { ['/var/', '/var/lib', 'lib'], ['/', '/var/lib', 'var/lib']]; - relativeTests.forEach(function(test) { + shims.forEach(relativeTests, function(test) { var actual = path.relative(test[0], test[1]); var expected = test[2]; t.equal(actual, expected); From d3d755ab9d37cfdca299a919408d2631530bbb52 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 14:25:01 +0200 Subject: [PATCH 32/47] [fix] add IE8 support for querystring.js --- builtin/_shims.js | 25 +++++++++++++++++++++++++ builtin/querystring.js | 4 ++-- test/browser/querystring-simple.js | 19 ++++++++++--------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index ad6b97b..452f419 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -42,6 +42,16 @@ exports.forEach = function forEach(xs, fn) { } }; +// Array.prototype.map is supported in IE9 +exports.map = function map(xs, fn) { + if (xs.map) return xs.map(fn); + var out = new Array(xs.length); + for (var i = 0; i < xs.length; i++) { + out[i] = fn(xs[i], i, xs); + } + return out; +}; + // Array.prototype.reduce is supported in IE9 exports.reduce = function reduce(array, callback, opt_initialValue) { if (array.reduce) return array.reduce(callback, opt_initialValue); @@ -64,6 +74,21 @@ exports.reduce = function reduce(array, callback, opt_initialValue) { } return value; +}; + +// String.prototype.substr - negative index don't work in IE8 +if ('ab'.substr(-1) !== 'b') { + exports.substr = function (str, start, length) { + // did we get a negative start, calculate how much it is from the beginning of the string + if (start < 0) start = str.length + start; + + // call the original function + return str.substr(start, length); + }; +} else { + exports.substr = function (str, start, length) { + return str.substr(start, length); + }; } // Object.create is supported in IE9 diff --git a/builtin/querystring.js b/builtin/querystring.js index 9901892..6297c9c 100644 --- a/builtin/querystring.js +++ b/builtin/querystring.js @@ -113,10 +113,10 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) { } if (util.isObject(obj)) { - return shims.keys(obj).map(function(k) { + return shims.map(shims.keys(obj), function(k) { var ks = QueryString.escape(stringifyPrimitive(k)) + eq; if (util.isArray(obj[k])) { - return obj[k].map(function(v) { + return shims.map(obj[k], function(v) { return ks + QueryString.escape(stringifyPrimitive(v)); }).join(sep); } else { diff --git a/test/browser/querystring-simple.js b/test/browser/querystring-simple.js index f30cc7e..2559240 100644 --- a/test/browser/querystring-simple.js +++ b/test/browser/querystring-simple.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var qs = require('querystring'); @@ -86,28 +87,28 @@ test('qs.parse - simple example', function (t) { }); test('qs.parse - test that the canonical qs is parsed properly', function (t) { - qsTestCases.forEach(function(testCase) { + shims.forEach(qsTestCases, function(testCase) { t.deepEqual(testCase[2], qs.parse(testCase[0])); }); t.end(); }); test('qs.parse - test that the colon test cases can do the same', function (t) { - qsColonTestCases.forEach(function(testCase) { + shims.forEach(qsColonTestCases, function(testCase) { t.deepEqual(testCase[2], qs.parse(testCase[0], ';', ':')); }); t.end(); }); test('qs.parse - test the weird objects, that they get parsed properly', function (t) { - qsWeirdObjects.forEach(function(testCase) { + shims.forEach(qsWeirdObjects, function(testCase) { t.deepEqual(testCase[2], qs.parse(testCase[1])); }); t.end(); }); test('qs.stringify - simple example', function (t) { - qsNoMungeTestCases.forEach(function(testCase) { + shims.forEach(qsNoMungeTestCases, function(testCase) { t.deepEqual(testCase[0], qs.stringify(testCase[1], '&', '=', false)); }); t.end(); @@ -128,15 +129,15 @@ test('qs.parse - nested in colon', function (t) { }); test('qs.stringify - now test stringifying', function (t) { - qsTestCases.forEach(function(testCase) { + shims.forEach(qsTestCases, function(testCase) { t.equal(testCase[1], qs.stringify(testCase[2])); }); - qsColonTestCases.forEach(function(testCase) { + shims.forEach(qsColonTestCases, function(testCase) { t.equal(testCase[1], qs.stringify(testCase[2], ';', ':')); }); - qsWeirdObjects.forEach(function(testCase) { + shims.forEach(qsWeirdObjects, function(testCase) { t.equal(testCase[1], qs.stringify(testCase[0])); }); @@ -182,7 +183,7 @@ test('qs.parse - on nothing', function (t) { // Test limiting test('qs.parse - test limiting', function (t) { t.equal( - Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length, + shims.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length, 1); t.end(); }); @@ -197,7 +198,7 @@ test('qs.parse - Test removing limit', function (t) { url = qs.stringify(query); t.equal( - Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length, + shims.keys(qs.parse(url, null, null, { maxKeys: 0 })).length, 2000); } testUnlimitedKeys(); From bd591b9698f30024d16726b2a70f59b57ca71c6f Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 14:45:32 +0200 Subject: [PATCH 33/47] [fix] add IE8 support for url.js --- builtin/_shims.js | 10 ++++++++-- builtin/url.js | 12 ++++++------ test/browser/url-simple.js | 36 ++++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index 452f419..48f52ed 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -35,10 +35,10 @@ exports.filter = function filter(xs, fn) { }; // Array.prototype.forEach is supported in IE9 -exports.forEach = function forEach(xs, fn) { +exports.forEach = function forEach(xs, fn, self) { if (xs.forEach) return xs.forEach(fn); for (var i = 0; i < xs.length; i++) { - fn(xs[i], i, xs); + fn.call(self, xs[i], i, xs); } }; @@ -91,6 +91,12 @@ if ('ab'.substr(-1) !== 'b') { }; } +// String.prototype.trim is supported in IE9 +exports.trim = function (str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +}; + // Object.create is supported in IE9 function create(prototype, properties) { var object; diff --git a/builtin/url.js b/builtin/url.js index a0ce69b..e17634f 100644 --- a/builtin/url.js +++ b/builtin/url.js @@ -92,7 +92,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { // trim before proceeding. // This is to support parse stuff like " http://foo.com \n" - rest = rest.trim(); + rest = shims.trim(rest); var proto = protocolPattern.exec(rest); if (proto) { @@ -360,7 +360,7 @@ Url.prototype.format = function() { var search = this.search || (query && ('?' + query)) || ''; - if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + if (protocol && shims.substr(protocol, -1) !== ':') protocol += ':'; // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. // unless they had them to begin with. @@ -404,7 +404,7 @@ Url.prototype.resolveObject = function(relative) { } var result = new Url(); - shims.keys(this).forEach(function(k) { + shims.forEach(shims.keys(this), function(k) { result[k] = this[k]; }, this); @@ -421,7 +421,7 @@ Url.prototype.resolveObject = function(relative) { // hrefs like //foo/bar always cut to the protocol. if (relative.slashes && !relative.protocol) { // take everything except the protocol from relative - shims.keys(relative).forEach(function(k) { + shims.forEach(shims.keys(relative), function(k) { if (k !== 'protocol') result[k] = relative[k]; }); @@ -446,7 +446,7 @@ Url.prototype.resolveObject = function(relative) { // because that's known to be hostless. // anything else is assumed to be absolute. if (!slashedProtocol[relative.protocol]) { - shims.keys(relative).forEach(function(k) { + shims.forEach(shims.keys(relative), function(k) { result[k] = relative[k]; }); result.href = result.format(); @@ -614,7 +614,7 @@ Url.prototype.resolveObject = function(relative) { srcPath.unshift(''); } - if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { + if (hasTrailingSlash && (shims.substr(srcPath.join('/'), -1) !== '/')) { srcPath.push(''); } diff --git a/test/browser/url-simple.js b/test/browser/url-simple.js index bb2679b..49dc3cd 100644 --- a/test/browser/url-simple.js +++ b/test/browser/url-simple.js @@ -1,8 +1,20 @@ var test = require('tape'); +var assert = require('assert'); +var shims = require('../../builtin/_shims.js'); var url = require('url'); +function objectify(instance) { + var obj = {}; + for (var key in instance) { + if (instance.hasOwnProperty(key)) { + obj[key] = instance[obj]; + } + } + return obj; +} + // URLs to parse, and expected data // { url : parsed } var parseTests = { @@ -713,14 +725,14 @@ test('url.parse and url.format - main test', function (t) { spaced = url.parse(' \t ' + u + '\n\t'); expected = parseTests[u]; - Object.keys(actual).forEach(function (i) { + shims.forEach(shims.keys(actual), function (i) { if (expected[i] === undefined && actual[i] === null) { expected[i] = null; } }); - t.deepEqual(actual, expected); - t.deepEqual(spaced, expected); + t.deepEqual(objectify(actual), objectify(expected)); + t.deepEqual(objectify(spaced), objectify(expected)); var expected = parseTests[u].href, actual = url.format(parseTests[u]); @@ -765,7 +777,7 @@ test('url.parse - with qoutes', function (t) { } } - t.deepEqual(actual, expected); + t.deepEqual(objectify(actual), objectify(expected)); } t.end(); @@ -1020,7 +1032,7 @@ var relativeTests = [ ]; test('url.relative - main tests', function (t) { - relativeTests.forEach(function(relativeTest) { + shims.forEach(relativeTests, function(relativeTest) { var a = url.resolve(relativeTest[0], relativeTest[1]), e = relativeTest[2]; t.equal(a, e, @@ -1033,7 +1045,7 @@ test('url.relative - main tests', function (t) { test('url.parse - will throw on none string', function (t) { // https://github.com/joyent/node/issues/568 - [ + shims.forEach([ undefined, null, true, @@ -1042,7 +1054,7 @@ test('url.parse - will throw on none string', function (t) { 0, [], {} - ].forEach(function(val) { + ], function(val) { t.throws(function() { url.parse(val); }, TypeError); }); @@ -1337,7 +1349,7 @@ var relativeTests2 = [ ]; test('url.resolve - main tests', function (t) { - relativeTests2.forEach(function(relativeTest) { + shims.forEach(relativeTests2, function(relativeTest) { var a = url.resolve(relativeTest[1], relativeTest[0]), e = relativeTest[2]; t.equal(a, e, @@ -1353,12 +1365,12 @@ test('url.resolve - main tests', function (t) { test('url.resolveObject - inverse operations', function (t) { //format: [from, path, expected] - relativeTests.forEach(function(relativeTest) { + shims.forEach(relativeTests, function(relativeTest) { var actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]), expected = url.parse(relativeTest[2]); - t.deepEqual(actual, expected); + t.deepEqual(objectify(actual), objectify(expected)); expected = relativeTest[2]; actual = url.format(actual); @@ -1385,11 +1397,11 @@ test('url - fundamental host problem', function (t) { relativeTests2[181][2] === 'f://g') { relativeTests2.splice(181, 1); } - relativeTests2.forEach(function(relativeTest) { + shims.forEach(relativeTests2, function(relativeTest) { var actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]), expected = url.parse(relativeTest[2]); - t.deepEqual(actual, expected); + t.deepEqual(objectify(actual), objectify(expected)); var expected = relativeTest[2], actual = url.format(actual); From 8354e804964b8cc0f32e20c82e5e90a68e4a0ee0 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 14:47:16 +0200 Subject: [PATCH 34/47] [fix] forEach shim in browser there support forEach --- builtin/_shims.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index 48f52ed..a54b3d3 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -36,7 +36,7 @@ exports.filter = function filter(xs, fn) { // Array.prototype.forEach is supported in IE9 exports.forEach = function forEach(xs, fn, self) { - if (xs.forEach) return xs.forEach(fn); + if (xs.forEach) return xs.forEach(fn, self); for (var i = 0; i < xs.length; i++) { fn.call(self, xs[i], i, xs); } From aaa99ff12188c54a0f5906a2d1d3465895420742 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 17:37:30 +0200 Subject: [PATCH 35/47] [test] path-simple.js shouldn't depend on process.cwd() --- test/browser/path-simple.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/browser/path-simple.js b/test/browser/path-simple.js index 8a5b484..ccf42ca 100644 --- a/test/browser/path-simple.js +++ b/test/browser/path-simple.js @@ -170,8 +170,6 @@ test('path.resolve', function (t) { // arguments result [[['/var/lib', '../', 'file/'], '/var/file'], [['/var/lib', '/../', 'file/'], '/file'], - [['a/b/c/', '../../..'], process.cwd()], - [['.'], process.cwd()], [['/some/dir', '.', '/absolute/'], '/absolute']]; shims.forEach(resolveTests, function(test) { From 8f4c84fcc90dcbb103bec8405e1b917a5e3a55df Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 19:08:55 +0200 Subject: [PATCH 36/47] [feature] upgrade stream to stream2 and add tests --- builtin/_stream_duplex.js | 50 + builtin/_stream_passthrough.js | 21 + builtin/_stream_readable.js | 899 ++++++++++++++++++ builtin/_stream_transform.js | 185 ++++ builtin/_stream_writable.js | 348 +++++++ builtin/stream.js | 57 +- test/browser/stream-big-push.js | 67 ++ test/browser/stream-pipe-after-end.js | 68 ++ test/browser/stream-pipe-cleanup.js | 103 ++ test/browser/stream-pipe-error-handling.js | 39 + test/browser/stream-pipe-event.js | 31 + test/browser/stream-push-order.js | 34 + test/browser/stream-push-strings.js | 49 + test/browser/stream-readable-event.js | 109 +++ .../browser/stream-readable-flow-recursion.js | 51 + test/browser/stream-unshift-empty-chunk.js | 63 ++ test/browser/stream-unshift-read-race.js | 115 +++ test/browser/stream2-basic.js | 417 ++++++++ test/browser/stream2-compatibility.js | 33 + test/browser/stream2-finish-pipe.js | 24 + test/browser/stream2-large-read-stall.js | 55 ++ test/browser/stream2-objects.js | 293 ++++++ test/browser/stream2-pipe-error-handling.js | 87 ++ test/browser/stream2-push.js | 112 +++ test/browser/stream2-read-sync-stack.js | 31 + .../stream2-readable-empty-buffer-no-eof.js | 97 ++ test/browser/stream2-readable-from-list.js | 64 ++ test/browser/stream2-readable-legacy-drain.js | 59 ++ .../browser/stream2-readable-non-empty-end.js | 58 ++ test/browser/stream2-readable-wrap-empty.js | 26 + test/browser/stream2-set-encoding.js | 300 ++++++ test/browser/stream2-transform.js | 415 ++++++++ test/browser/stream2-unpipe-drain.js | 64 ++ test/browser/stream2-unpipe-leak.js | 50 + test/browser/stream2-writable.js | 319 +++++++ 35 files changed, 4758 insertions(+), 35 deletions(-) create mode 100644 builtin/_stream_duplex.js create mode 100644 builtin/_stream_passthrough.js create mode 100644 builtin/_stream_readable.js create mode 100644 builtin/_stream_transform.js create mode 100644 builtin/_stream_writable.js create mode 100644 test/browser/stream-big-push.js create mode 100644 test/browser/stream-pipe-after-end.js create mode 100644 test/browser/stream-pipe-cleanup.js create mode 100644 test/browser/stream-pipe-error-handling.js create mode 100644 test/browser/stream-pipe-event.js create mode 100644 test/browser/stream-push-order.js create mode 100644 test/browser/stream-push-strings.js create mode 100644 test/browser/stream-readable-event.js create mode 100644 test/browser/stream-readable-flow-recursion.js create mode 100644 test/browser/stream-unshift-empty-chunk.js create mode 100644 test/browser/stream-unshift-read-race.js create mode 100644 test/browser/stream2-basic.js create mode 100644 test/browser/stream2-compatibility.js create mode 100644 test/browser/stream2-finish-pipe.js create mode 100644 test/browser/stream2-large-read-stall.js create mode 100644 test/browser/stream2-objects.js create mode 100644 test/browser/stream2-pipe-error-handling.js create mode 100644 test/browser/stream2-push.js create mode 100644 test/browser/stream2-read-sync-stack.js create mode 100644 test/browser/stream2-readable-empty-buffer-no-eof.js create mode 100644 test/browser/stream2-readable-from-list.js create mode 100644 test/browser/stream2-readable-legacy-drain.js create mode 100644 test/browser/stream2-readable-non-empty-end.js create mode 100644 test/browser/stream2-readable-wrap-empty.js create mode 100644 test/browser/stream2-set-encoding.js create mode 100644 test/browser/stream2-transform.js create mode 100644 test/browser/stream2-unpipe-drain.js create mode 100644 test/browser/stream2-unpipe-leak.js create mode 100644 test/browser/stream2-writable.js diff --git a/builtin/_stream_duplex.js b/builtin/_stream_duplex.js new file mode 100644 index 0000000..079205e --- /dev/null +++ b/builtin/_stream_duplex.js @@ -0,0 +1,50 @@ + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +module.exports = Duplex; +var util = require('util'); +var timers = require('timers'); +var Readable = require('./_stream_readable.js'); +var Writable = require('./_stream_writable.js'); + +util.inherits(Duplex, Readable); + +Object.keys(Writable.prototype).forEach(function(method) { + if (!Duplex.prototype[method]) + Duplex.prototype[method] = Writable.prototype[method]; +}); + +function Duplex(options) { + if (!(this instanceof Duplex)) + return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) + this.readable = false; + + if (options && options.writable === false) + this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) + this.allowHalfOpen = false; + + this.once('end', onend); +} + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) + return; + + // no more data can be written. + // But allow more writes to happen in this tick. + timers.setImmediate(this.end.bind(this)); +} diff --git a/builtin/_stream_passthrough.js b/builtin/_stream_passthrough.js new file mode 100644 index 0000000..67fc221 --- /dev/null +++ b/builtin/_stream_passthrough.js @@ -0,0 +1,21 @@ + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +module.exports = PassThrough; + +var Transform = require('./_stream_transform.js'); +var util = require('util'); +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) + return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function(chunk, encoding, cb) { + cb(null, chunk); +}; diff --git a/builtin/_stream_readable.js b/builtin/_stream_readable.js new file mode 100644 index 0000000..13dcb29 --- /dev/null +++ b/builtin/_stream_readable.js @@ -0,0 +1,899 @@ + +module.exports = Readable; +Readable.ReadableState = ReadableState; + +var EE = require('events').EventEmitter; +var Stream = require('stream'); +var Buffer = require('buffer').Buffer; +var timers = require('timers'); +var util = require('util'); +var StringDecoder; + +util.inherits(Readable, Stream); + +function ReadableState(options, stream) { + options = options || {}; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024; + + // cast to ints. + this.highWaterMark = ~~this.highWaterMark; + + this.buffer = []; + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = false; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // In streams that never have any data, and do push(null) right away, + // the consumer can miss the 'end' event if they do some I/O before + // consuming the stream. So, we don't emit('end') until some reading + // happens. + this.calledRead = false; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, becuase any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // when piping, we only care about 'readable' events that happen + // after read()ing all the bytes and not getting any pushback. + this.ranOut = false; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) + StringDecoder = require('string_decoder').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + if (!(this instanceof Readable)) + return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + Stream.call(this); +} + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function(chunk, encoding) { + var state = this._readableState; + + if (typeof chunk === 'string' && !state.objectMode) { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = new Buffer(chunk, encoding); + encoding = ''; + } + } + + return readableAddChunk(this, state, chunk, encoding, false); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function(chunk) { + var state = this._readableState; + return readableAddChunk(this, state, chunk, '', true); +}; + +function readableAddChunk(stream, state, chunk, encoding, addToFront) { + var er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (chunk === null || chunk === undefined) { + state.reading = false; + if (!state.ended) + onEofChunk(stream, state); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (state.ended && !addToFront) { + var e = new Error('stream.push() after EOF'); + stream.emit('error', e); + } else if (state.endEmitted && addToFront) { + var e = new Error('stream.unshift() after end event'); + stream.emit('error', e); + } else { + if (state.decoder && !addToFront && !encoding) + chunk = state.decoder.write(chunk); + + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) { + state.buffer.unshift(chunk); + } else { + state.reading = false; + state.buffer.push(chunk); + } + + if (state.needReadable) + emitReadable(stream); + + maybeReadMore(stream, state); + } + } else if (!addToFront) { + state.reading = false; + } + + return needMoreData(state); +} + + + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && + (state.needReadable || + state.length < state.highWaterMark || + state.length === 0); +} + +// backwards compatibility. +Readable.prototype.setEncoding = function(enc) { + if (!StringDecoder) + StringDecoder = require('string_decoder').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; +}; + +// Don't raise the hwm > 128MB +var MAX_HWM = 0x800000; +function roundUpToNextPowerOf2(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 + n--; + for (var p = 1; p < 32; p <<= 1) n |= n >> p; + n++; + } + return n; +} + +function howMuchToRead(n, state) { + if (state.length === 0 && state.ended) + return 0; + + if (state.objectMode) + return n === 0 ? 0 : 1; + + if (isNaN(n) || n === null) { + // only flow one buffer at a time + if (state.flowing && state.buffer.length) + return state.buffer[0].length; + else + return state.length; + } + + if (n <= 0) + return 0; + + // If we're asking for more than the target buffer level, + // then raise the water mark. Bump up to the next highest + // power of 2, to prevent increasing it excessively in tiny + // amounts. + if (n > state.highWaterMark) + state.highWaterMark = roundUpToNextPowerOf2(n); + + // don't have that much. return null, unless we've ended. + if (n > state.length) { + if (!state.ended) { + state.needReadable = true; + return 0; + } else + return state.length; + } + + return n; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function(n) { + var state = this._readableState; + state.calledRead = true; + var nOrig = n; + + if (typeof n !== 'number' || n > 0) + state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && + state.needReadable && + (state.length >= state.highWaterMark || state.ended)) { + emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) + endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + + // if we currently have less than the highWaterMark, then also read some + if (state.length - n <= state.highWaterMark) + doRead = true; + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) + doRead = false; + + if (doRead) { + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) + state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + } + + // If _read called its callback synchronously, then `reading` + // will be false, and we need to re-evaluate how much data we + // can return to the user. + if (doRead && !state.reading) + n = howMuchToRead(nOrig, state); + + var ret; + if (n > 0) + ret = fromList(n, state); + else + ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } + + state.length -= n; + + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (state.length === 0 && !state.ended) + state.needReadable = true; + + // If we happened to read() exactly the remaining amount in the + // buffer, and the EOF has been seen at this point, then make sure + // that we emit 'end' on the very next tick. + if (state.ended && !state.endEmitted && state.length === 0) + endReadable(this); + + return ret; +}; + +function chunkInvalid(state, chunk) { + var er = null; + if (!Buffer.isBuffer(chunk) && + 'string' !== typeof chunk && + chunk !== null && + chunk !== undefined && + !state.objectMode && + !er) { + console.log('chunk: ', chunk); + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + + +function onEofChunk(stream, state) { + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // if we've ended and we have some data left, then emit + // 'readable' now to make sure it gets picked up. + if (state.length > 0) + emitReadable(stream); + else + endReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (state.emittedReadable) + return; + + state.emittedReadable = true; + if (state.sync) + timers.setImmediate(function() { + emitReadable_(stream); + }); + else + emitReadable_(stream); +} + +function emitReadable_(stream) { + stream.emit('readable'); +} + + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + timers.setImmediate(function() { + maybeReadMore_(stream, state); + }); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && + state.length < state.highWaterMark) { + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break; + else + len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function(n) { + this.emit('error', new Error('not implemented')); +}; + +Readable.prototype.pipe = function(dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && + dest !== process.stdout && + dest !== process.stderr; + + var endFn = doEnd ? onend : cleanup; + if (state.endEmitted) + timers.setImmediate(endFn); + else + src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable) { + if (readable !== src) return; + cleanup(); + } + + function onend() { + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + function cleanup() { + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', cleanup); + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (!dest._writableState || dest._writableState.needDrain) + ondrain(); + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + // check for listeners before emit removes one-time listeners. + var errListeners = EE.listenerCount(dest, 'error'); + function onerror(er) { + unpipe(); + if (errListeners === 0 && EE.listenerCount(dest, 'error') === 0) + dest.emit('error', er); + } + dest.once('error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + // the handler that waits for readable events after all + // the data gets sucked out in flow. + // This would be easier to follow with a .once() handler + // in flow(), but that is too slow. + this.on('readable', pipeOnReadable); + + state.flowing = true; + timers.setImmediate(function() { + flow(src); + }); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function() { + var dest = this; + var state = src._readableState; + state.awaitDrain--; + if (state.awaitDrain === 0) + flow(src); + }; +} + +function flow(src) { + var state = src._readableState; + var chunk; + state.awaitDrain = 0; + + function write(dest, i, list) { + var written = dest.write(chunk); + if (false === written) { + state.awaitDrain++; + } + } + + while (state.pipesCount && null !== (chunk = src.read())) { + + if (state.pipesCount === 1) + write(state.pipes, 0, null); + else + state.pipes.forEach(write); + + src.emit('data', chunk); + + // if anyone needs a drain, then we have to wait for that. + if (state.awaitDrain > 0) + return; + } + + // if every destination was unpiped, either before entering this + // function, or in the while loop, then stop flowing. + // + // NB: This is a pretty rare edge case. + if (state.pipesCount === 0) { + state.flowing = false; + + // if there were data event listeners added, then switch to old mode. + if (EE.listenerCount(src, 'data') > 0) + emitDataEvents(src); + return; + } + + // at this point, no one needed a drain, so we just ran out of data + // on the next readable event, start it over again. + state.ranOut = true; +} + +function pipeOnReadable() { + if (this._readableState.ranOut) { + this._readableState.ranOut = false; + flow(this); + } +} + + +Readable.prototype.unpipe = function(dest) { + var state = this._readableState; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) + return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) + return this; + + if (!dest) + dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + this.removeListener('readable', pipeOnReadable); + state.flowing = false; + if (dest) + dest.emit('unpipe', this); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + this.removeListener('readable', pipeOnReadable); + state.flowing = false; + + for (var i = 0; i < len; i++) + dests[i].emit('unpipe', this); + return this; + } + + // try to find the right one. + var i = state.pipes.indexOf(dest); + if (i === -1) + return this; + + state.pipes.splice(i, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) + state.pipes = state.pipes[0]; + + dest.emit('unpipe', this); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function(ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data' && !this._readableState.flowing) + emitDataEvents(this); + + if (ev === 'readable' && this.readable) { + var state = this._readableState; + if (!state.readableListening) { + state.readableListening = true; + state.emittedReadable = false; + state.needReadable = true; + if (!state.reading) { + this.read(0); + } else if (state.length) { + emitReadable(this, state); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function() { + emitDataEvents(this); + this.read(0); + this.emit('resume'); +}; + +Readable.prototype.pause = function() { + emitDataEvents(this, true); + this.emit('pause'); +}; + +function emitDataEvents(stream, startPaused) { + var state = stream._readableState; + + if (state.flowing) { + // https://github.com/isaacs/readable-stream/issues/16 + throw new Error('Cannot switch to old mode now.'); + } + + var paused = startPaused || false; + var readable = false; + + // convert to an old-style stream. + stream.readable = true; + stream.pipe = Stream.prototype.pipe; + stream.on = stream.addListener = Stream.prototype.on; + + stream.on('readable', function() { + readable = true; + + var c; + while (!paused && (null !== (c = stream.read()))) + stream.emit('data', c); + + if (c === null) { + readable = false; + stream._readableState.needReadable = true; + } + }); + + stream.pause = function() { + paused = true; + this.emit('pause'); + }; + + stream.resume = function() { + paused = false; + if (readable) + timers.setImmediate(function() { + stream.emit('readable'); + }); + else + this.read(0); + this.emit('resume'); + }; + + // now make it start, just in case it hadn't already. + stream.emit('readable'); +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function(stream) { + var state = this._readableState; + var paused = false; + + var self = this; + stream.on('end', function() { + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) + self.push(chunk); + } + + self.push(null); + }); + + stream.on('data', function(chunk) { + if (state.decoder) + chunk = state.decoder.write(chunk); + if (!chunk || !state.objectMode && !chunk.length) + return; + + var ret = self.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (typeof stream[i] === 'function' && + typeof this[i] === 'undefined') { + this[i] = function(method) { return function() { + return stream[method].apply(stream, arguments); + }}(i); + } + } + + // proxy certain important events. + var events = ['error', 'close', 'destroy', 'pause', 'resume']; + events.forEach(function(ev) { + stream.on(ev, self.emit.bind(self, ev)); + }); + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + self._read = function(n) { + if (paused) { + paused = false; + stream.resume(); + } + }; + + return self; +}; + + + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +function fromList(n, state) { + var list = state.buffer; + var length = state.length; + var stringMode = !!state.decoder; + var objectMode = !!state.objectMode; + var ret; + + // nothing in the list, definitely empty. + if (list.length === 0) + return null; + + if (length === 0) + ret = null; + else if (objectMode) + ret = list.shift(); + else if (!n || n >= length) { + // read it all, truncate the array. + if (stringMode) + ret = list.join(''); + else + ret = Buffer.concat(list, length); + list.length = 0; + } else { + // read just some of it. + if (n < list[0].length) { + // just take a part of the first list item. + // slice is the same for buffers and strings. + var buf = list[0]; + ret = buf.slice(0, n); + list[0] = buf.slice(n); + } else if (n === list[0].length) { + // first list is a perfect match + ret = list.shift(); + } else { + // complex case. + // we have enough to cover it, but it spans past the first buffer. + if (stringMode) + ret = ''; + else + ret = new Buffer(n); + + var c = 0; + for (var i = 0, l = list.length; i < l && c < n; i++) { + var buf = list[0]; + var cpy = Math.min(n - c, buf.length); + + if (stringMode) + ret += buf.slice(0, cpy); + else + buf.copy(ret, c, 0, cpy); + + if (cpy < buf.length) + list[0] = buf.slice(cpy); + else + list.shift(); + + c += cpy; + } + } + } + + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) + throw new Error('endReadable called on non-empty stream'); + + if (!state.endEmitted && state.calledRead) { + state.ended = true; + timers.setImmediate(function() { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } + }); + } +} diff --git a/builtin/_stream_transform.js b/builtin/_stream_transform.js new file mode 100644 index 0000000..5d4313d --- /dev/null +++ b/builtin/_stream_transform.js @@ -0,0 +1,185 @@ + + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +module.exports = Transform; + +var Duplex = require('./_stream_duplex.js'); +var util = require('util'); +util.inherits(Transform, Duplex); + + +function TransformState(options, stream) { + this.afterTransform = function(er, data) { + return afterTransform(stream, er, data); + }; + + this.needTransform = false; + this.transforming = false; + this.writecb = null; + this.writechunk = null; +} + +function afterTransform(stream, er, data) { + var ts = stream._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) + return stream.emit('error', new Error('no writecb in Transform class')); + + ts.writechunk = null; + ts.writecb = null; + + if (data !== null && data !== undefined) + stream.push(data); + + if (cb) + cb(er); + + var rs = stream._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + stream._read(rs.highWaterMark); + } +} + + +function Transform(options) { + if (!(this instanceof Transform)) + return new Transform(options); + + Duplex.call(this, options); + + var ts = this._transformState = new TransformState(options, this); + + // when the writable side finishes, then flush out anything remaining. + var stream = this; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + this.once('finish', function() { + if ('function' === typeof this._flush) + this._flush(function(er) { + done(stream, er); + }); + else + done(stream); + }); +} + +Transform.prototype.push = function(chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function(chunk, encoding, cb) { + throw new Error('not implemented'); +}; + +Transform.prototype._write = function(chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || + rs.needReadable || + rs.length < rs.highWaterMark) + this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function(n) { + var ts = this._transformState; + + if (ts.writechunk && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + + +function done(stream, er) { + if (er) + return stream.emit('error', er); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var rs = stream._readableState; + var ts = stream._transformState; + + if (ws.length) + throw new Error('calling transform done when ws.length != 0'); + + if (ts.transforming) + throw new Error('calling transform done when still transforming'); + + return stream.push(null); +} diff --git a/builtin/_stream_writable.js b/builtin/_stream_writable.js new file mode 100644 index 0000000..dd42d6c --- /dev/null +++ b/builtin/_stream_writable.js @@ -0,0 +1,348 @@ + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, cb), and it'll handle all +// the drain event emission and buffering. + +module.exports = Writable; +Writable.WritableState = WritableState; + +var util = require('util'); +var Stream = require('stream'); +var timers = require('timers'); +var Buffer = require('buffer').Buffer; + +util.inherits(Writable, Stream); + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; +} + +function WritableState(options, stream) { + options = options || {}; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + // cast to ints. + this.highWaterMark = ~~this.highWaterMark; + + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, becuase any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function(er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.buffer = []; +} + +function Writable(options) { + // Writable ctor is applied to Duplexes, though they're not + // instanceof Writable, they're instanceof Readable. + if (!(this instanceof Writable) && !(this instanceof Stream.Duplex)) + return new Writable(options); + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function() { + this.emit('error', new Error('Cannot pipe. Not readable.')); +}; + + +function writeAfterEnd(stream, state, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + timers.setImmediate(function() { + cb(er); + }); +} + +// If we get something that is not a buffer, string, null, or undefined, +// and we're not in objectMode, then that's an error. +// Otherwise stream chunks are all considered to be of length=1, and the +// watermarks determine how many objects to keep in the buffer, rather than +// how many bytes or characters. +function validChunk(stream, state, chunk, cb) { + var valid = true; + if (!Buffer.isBuffer(chunk) && + 'string' !== typeof chunk && + chunk !== null && + chunk !== undefined && + !state.objectMode) { + var er = new TypeError('Invalid non-string/buffer chunk'); + stream.emit('error', er); + timers.setImmediate(function() { + cb(er); + }); + valid = false; + } + return valid; +} + +Writable.prototype.write = function(chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (Buffer.isBuffer(chunk)) + encoding = 'buffer'; + else if (!encoding) + encoding = state.defaultEncoding; + + if (typeof cb !== 'function') + cb = function() {}; + + if (state.ended) + writeAfterEnd(this, state, cb); + else if (validChunk(this, state, chunk, cb)) + ret = writeOrBuffer(this, state, chunk, encoding, cb); + + return ret; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && + state.decodeStrings !== false && + typeof chunk === 'string') { + chunk = new Buffer(chunk, encoding); + } + return chunk; +} + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, chunk, encoding, cb) { + chunk = decodeChunk(state, chunk, encoding); + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + state.needDrain = !ret; + + if (state.writing) + state.buffer.push(new WriteReq(chunk, encoding, cb)); + else + doWrite(stream, state, len, chunk, encoding, cb); + + return ret; +} + +function doWrite(stream, state, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + if (sync) + timers.setImmediate(function() { + cb(er); + }); + else + cb(er); + + stream.emit('error', er); +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) + onwriteError(stream, state, sync, er, cb); + else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(stream, state); + + if (!finished && !state.bufferProcessing && state.buffer.length) + clearBuffer(stream, state); + + if (sync) { + timers.setImmediate(function() { + afterWrite(stream, state, finished, cb); + }); + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) + onwriteDrain(stream, state); + cb(); + if (finished) + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + + for (var c = 0; c < state.buffer.length; c++) { + var entry = state.buffer[c]; + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, len, chunk, encoding, cb); + + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + c++; + break; + } + } + + state.bufferProcessing = false; + if (c < state.buffer.length) + state.buffer = state.buffer.slice(c); + else + state.buffer.length = 0; +} + +Writable.prototype._write = function(chunk, encoding, cb) { + cb(new Error('not implemented')); +}; + +Writable.prototype.end = function(chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (typeof chunk !== 'undefined' && chunk !== null) + this.write(chunk, encoding); + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) + endWritable(this, state, cb); +}; + + +function needFinish(stream, state) { + return (state.ending && + state.length === 0 && + !state.finished && + !state.writing); +} + +function finishMaybe(stream, state) { + var need = needFinish(stream, state); + if (need) { + state.finished = true; + stream.emit('finish'); + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) + timers.setImmediate(cb); + else + stream.once('finish', cb); + } + state.ended = true; +} diff --git a/builtin/stream.js b/builtin/stream.js index 4feb410..9c4d79c 100644 --- a/builtin/stream.js +++ b/builtin/stream.js @@ -1,14 +1,27 @@ -var events = require('events'); +module.exports = Stream; + +var EE = require('events').EventEmitter; var util = require('util'); -function Stream() { - events.EventEmitter.call(this); -} -util.inherits(Stream, events.EventEmitter); -module.exports = Stream; +util.inherits(Stream, EE); +Stream.Readable = require('./_stream_readable.js'); +Stream.Writable = require('./_stream_writable.js'); +Stream.Duplex = require('./_stream_duplex.js'); +Stream.Transform = require('./_stream_transform.js'); +Stream.PassThrough = require('./_stream_passthrough.js'); + // Backwards-compat with node 0.4.x Stream.Stream = Stream; + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + Stream.prototype.pipe = function(dest, options) { var source = this; @@ -31,12 +44,8 @@ Stream.prototype.pipe = function(dest, options) { dest.on('drain', ondrain); // If the 'end' option is not supplied, dest.end() will be called when - // source gets the 'end' or 'close' events. Only dest.end() once, and - // only when all sources have ended. + // source gets the 'end' or 'close' events. Only dest.end() once. if (!dest._isStdio && (!options || options.end !== false)) { - dest._pipeCount = dest._pipeCount || 0; - dest._pipeCount++; - source.on('end', onend); source.on('close', onclose); } @@ -46,16 +55,6 @@ Stream.prototype.pipe = function(dest, options) { if (didOnEnd) return; didOnEnd = true; - dest._pipeCount--; - - // remove the listeners - cleanup(); - - if (dest._pipeCount > 0) { - // waiting for other incoming streams to end. - return; - } - dest.end(); } @@ -64,23 +63,13 @@ Stream.prototype.pipe = function(dest, options) { if (didOnEnd) return; didOnEnd = true; - dest._pipeCount--; - - // remove the listeners - cleanup(); - - if (dest._pipeCount > 0) { - // waiting for other incoming streams to end. - return; - } - - dest.destroy(); + if (typeof dest.destroy === 'function') dest.destroy(); } // don't leave dangling pipes when there are errors. function onerror(er) { cleanup(); - if (this.listeners('error').length === 0) { + if (EE.listenerCount(this, 'error') === 0) { throw er; // Unhandled stream error in pipe. } } @@ -102,14 +91,12 @@ Stream.prototype.pipe = function(dest, options) { source.removeListener('end', cleanup); source.removeListener('close', cleanup); - dest.removeListener('end', cleanup); dest.removeListener('close', cleanup); } source.on('end', cleanup); source.on('close', cleanup); - dest.on('end', cleanup); dest.on('close', cleanup); dest.emit('pipe', source); diff --git a/test/browser/stream-big-push.js b/test/browser/stream-big-push.js new file mode 100644 index 0000000..206b704 --- /dev/null +++ b/test/browser/stream-big-push.js @@ -0,0 +1,67 @@ + +var test = require('tape'); + +var stream = require('stream'); +var str = 'asdfasdfasdfasdfasdf'; + +test('stream.Readable - big push', function (t) { + var r = new stream.Readable({ + highWaterMark: 5, + encoding: 'utf8' + }); + + var reads = 0; + var eofed = false; + var ended = false; + + r._read = function(n) { + if (reads === 0) { + setTimeout(function() { + r.push(str); + }); + reads++; + } else if (reads === 1) { + var ret = r.push(str); + t.equal(ret, false); + reads++; + } else { + t.ok(!eofed); + eofed = true; + r.push(null); + } + }; + + r.on('end', function() { + ended = true; + done(); + }); + + // push some data in to start. + // we've never gotten any read event at this point. + var ret = r.push(str); + // should be false. > hwm + t.ok(!ret); + var chunk = r.read(); + t.equal(chunk, str); + chunk = r.read(); + t.equal(chunk, null); + + r.once('readable', function() { + // this time, we'll get *all* the remaining data, because + // it's been added synchronously, as the read WOULD take + // us below the hwm, and so it triggered a _read() again, + // which synchronously added more, which we then return. + chunk = r.read(); + t.equal(chunk, str + str); + + chunk = r.read(); + t.equal(chunk, null); + }); + + function done() { + t.ok(eofed); + t.ok(ended); + t.equal(reads, 2); + t.end(); + } +}); diff --git a/test/browser/stream-pipe-after-end.js b/test/browser/stream-pipe-after-end.js new file mode 100644 index 0000000..3819cc9 --- /dev/null +++ b/test/browser/stream-pipe-after-end.js @@ -0,0 +1,68 @@ + +var test = require('tape'); + +var Readable = require('stream').Readable; +var Writable = require('stream').Writable; +var util = require('util'); + +test('stream - pipe after end', function (t) { + util.inherits(TestReadable, Readable); + function TestReadable(opt) { + if (!(this instanceof TestReadable)) + return new TestReadable(opt); + Readable.call(this, opt); + this._ended = false; + } + + TestReadable.prototype._read = function(n) { + if (this._ended) + this.emit('error', new Error('_read called twice')); + this._ended = true; + this.push(null); + }; + + util.inherits(TestWritable, Writable); + function TestWritable(opt) { + if (!(this instanceof TestWritable)) + return new TestWritable(opt); + Writable.call(this, opt); + this._written = []; + } + + TestWritable.prototype._write = function(chunk, encoding, cb) { + this._written.push(chunk); + cb(); + }; + + // this one should not emit 'end' until we read() from it later. + var ender = new TestReadable(); + var enderEnded = false; + + // what happens when you pipe() a Readable that's already ended? + var piper = new TestReadable(); + // pushes EOF null, and length=0, so this will trigger 'end' + piper.read(); + + setTimeout(function() { + ender.on('end', function() { + enderEnded = true; + }); + t.ok(!enderEnded); + var c = ender.read(); + t.equal(c, null); + + var w = new TestWritable(); + var writableFinished = false; + w.on('finish', function() { + writableFinished = true; + done(); + }); + piper.pipe(w); + + function done() { + t.ok(enderEnded); + t.ok(writableFinished); + t.end(); + } + }); +}); diff --git a/test/browser/stream-pipe-cleanup.js b/test/browser/stream-pipe-cleanup.js new file mode 100644 index 0000000..a061e3a --- /dev/null +++ b/test/browser/stream-pipe-cleanup.js @@ -0,0 +1,103 @@ + +var stream = require('stream'); + +var test = require('tape'); +var util = require('util'); + +test('stream - pipe cleanup', function (t) { + function Writable() { + this.writable = true; + this.endCalls = 0; + stream.Stream.call(this); + } + util.inherits(Writable, stream.Stream); + Writable.prototype.end = function() { + this.endCalls++; + }; + + Writable.prototype.destroy = function() { + this.endCalls++; + }; + + function Readable() { + this.readable = true; + stream.Stream.call(this); + } + util.inherits(Readable, stream.Stream); + + function Duplex() { + this.readable = true; + Writable.call(this); + } + util.inherits(Duplex, Writable); + + var i = 0; + var limit = 100; + + var w = new Writable(); + + var r; + + for (i = 0; i < limit; i++) { + r = new Readable(); + r.pipe(w); + r.emit('end'); + } + t.equal(0, r.listeners('end').length); + t.equal(limit, w.endCalls); + + w.endCalls = 0; + + for (i = 0; i < limit; i++) { + r = new Readable(); + r.pipe(w); + r.emit('close'); + } + t.equal(0, r.listeners('close').length); + t.equal(limit, w.endCalls); + + w.endCalls = 0; + + r = new Readable(); + + for (i = 0; i < limit; i++) { + w = new Writable(); + r.pipe(w); + w.emit('close'); + } + t.equal(0, w.listeners('close').length); + + r = new Readable(); + w = new Writable(); + var d = new Duplex(); + r.pipe(d); // pipeline A + d.pipe(w); // pipeline B + t.equal(r.listeners('end').length, 2); // A.onend, A.cleanup + t.equal(r.listeners('close').length, 2); // A.onclose, A.cleanup + t.equal(d.listeners('end').length, 2); // B.onend, B.cleanup + t.equal(d.listeners('close').length, 3); // A.cleanup, B.onclose, B.cleanup + t.equal(w.listeners('end').length, 0); + t.equal(w.listeners('close').length, 1); // B.cleanup + + r.emit('end'); + t.equal(d.endCalls, 1); + t.equal(w.endCalls, 0); + t.equal(r.listeners('end').length, 0); + t.equal(r.listeners('close').length, 0); + t.equal(d.listeners('end').length, 2); // B.onend, B.cleanup + t.equal(d.listeners('close').length, 2); // B.onclose, B.cleanup + t.equal(w.listeners('end').length, 0); + t.equal(w.listeners('close').length, 1); // B.cleanup + + d.emit('end'); + t.equal(d.endCalls, 1); + t.equal(w.endCalls, 1); + t.equal(r.listeners('end').length, 0); + t.equal(r.listeners('close').length, 0); + t.equal(d.listeners('end').length, 0); + t.equal(d.listeners('close').length, 0); + t.equal(w.listeners('end').length, 0); + t.equal(w.listeners('close').length, 0); + + t.end(); +}); diff --git a/test/browser/stream-pipe-error-handling.js b/test/browser/stream-pipe-error-handling.js new file mode 100644 index 0000000..ca1f4c3 --- /dev/null +++ b/test/browser/stream-pipe-error-handling.js @@ -0,0 +1,39 @@ + +var test = require('tape'); +var Stream = require('stream').Stream; + +test('stream - pipe error handling - error listener catches', function (t) { + var source = new Stream(); + var dest = new Stream(); + + source.pipe(dest); + + var gotErr = null; + source.on('error', function(err) { + gotErr = err; + }); + + var err = new Error('This stream turned into bacon.'); + source.emit('error', err); + t.strictEqual(gotErr, err); + t.end(); +}); + +test('stream - pipe erro handling - error without listener throws', function (t) { + var source = new Stream(); + var dest = new Stream(); + + source.pipe(dest); + + var err = new Error('This stream turned into bacon.'); + + var gotErr = null; + try { + source.emit('error', err); + } catch (e) { + gotErr = e; + } + + t.strictEqual(gotErr, err); + t.end(); +}); diff --git a/test/browser/stream-pipe-event.js b/test/browser/stream-pipe-event.js new file mode 100644 index 0000000..11e77df --- /dev/null +++ b/test/browser/stream-pipe-event.js @@ -0,0 +1,31 @@ + +var stream = require('stream'); +var test = require('tape'); +var util = require('util'); + +test('stream - pipe event', function (t) { + function Writable() { + this.writable = true; + stream.Stream.call(this); + } + util.inherits(Writable, stream.Stream); + + function Readable() { + this.readable = true; + stream.Stream.call(this); + } + util.inherits(Readable, stream.Stream); + + var passed = false; + + var w = new Writable(); + w.on('pipe', function(src) { + passed = true; + }); + + var r = new Readable(); + r.pipe(w); + + t.ok(passed); + t.end(); +}); diff --git a/test/browser/stream-push-order.js b/test/browser/stream-push-order.js new file mode 100644 index 0000000..847a5d2 --- /dev/null +++ b/test/browser/stream-push-order.js @@ -0,0 +1,34 @@ + +var Readable = require('stream').Readable; +var test = require('tape'); + +test('stream - push order', function (t) { + var s = new Readable({ + highWaterMark: 20, + encoding: 'ascii' + }); + + var list = ['1', '2', '3', '4', '5', '6']; + + s._read = function (n) { + var one = list.shift(); + if (!one) { + s.push(null); + done(); + } else { + var two = list.shift(); + s.push(one); + s.push(two); + } + }; + + var v = s.read(0); + + // ACTUALLY [1, 3, 5, 6, 4, 2] + + function done() { + t.deepEqual(s._readableState.buffer, + ['1', '2', '3', '4', '5', '6']); + t.end(); + } +}); \ No newline at end of file diff --git a/test/browser/stream-push-strings.js b/test/browser/stream-push-strings.js new file mode 100644 index 0000000..c1ec807 --- /dev/null +++ b/test/browser/stream-push-strings.js @@ -0,0 +1,49 @@ + +var test = require('tape'); + +var Readable = require('stream').Readable; +var timers = require('timers'); +var util = require('util'); + +test('stream - push strings', function (t) { + util.inherits(MyStream, Readable); + function MyStream(options) { + Readable.call(this, options); + this._chunks = 3; + } + + MyStream.prototype._read = function(n) { + switch (this._chunks--) { + case 0: + return this.push(null); + case 1: + return setTimeout(function() { + this.push('last chunk'); + }.bind(this), 100); + case 2: + return this.push('second to last chunk'); + case 3: + return timers.setImmediate(function() { + this.push('first chunk'); + }.bind(this)); + default: + throw new Error('?'); + } + }; + + var ms = new MyStream(); + var results = []; + ms.on('readable', function() { + var chunk; + while (null !== (chunk = ms.read())) + results.push(chunk + ''); + }); + + var expect = [ 'first chunksecond to last chunk', 'last chunk' ]; + function done() { + t.equal(ms._chunks, -1); + t.deepEqual(results, expect); + t.end(); + } + ms.once('end', done); +}); diff --git a/test/browser/stream-readable-event.js b/test/browser/stream-readable-event.js new file mode 100644 index 0000000..7cc4f02 --- /dev/null +++ b/test/browser/stream-readable-event.js @@ -0,0 +1,109 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +var Readable = require('stream').Readable; + +test('stream.Readable - readable event - first', function (t) { + // First test, not reading when the readable is added. + // make sure that on('readable', ...) triggers a readable event. + var r = new Readable({ + highWaterMark: 3 + }); + + var _readCalled = false; + r._read = function(n) { + _readCalled = true; + }; + + // This triggers a 'readable' event, which is lost. + r.push(new Buffer('blerg')); + + var caughtReadable = false; + setTimeout(function() { + // we're testing what we think we are + t.ok(!r._readableState.reading); + r.on('readable', function() { + caughtReadable = true; + done(); + }); + }); + + function done() { + // we're testing what we think we are + t.ok(!_readCalled); + + t.ok(caughtReadable); + t.end(); + } +}); + +test('stream.Readable - readable event - second', function (t) { + // second test, make sure that readable is re-emitted if there's + // already a length, while it IS reading. + + var r = new Readable({ + highWaterMark: 3 + }); + + var _readCalled = false; + r._read = function(n) { + _readCalled = true; + }; + + // This triggers a 'readable' event, which is lost. + r.push(new Buffer('bl')); + + var caughtReadable = false; + setTimeout(function() { + // assert we're testing what we think we are + t.ok(r._readableState.reading); + r.on('readable', function() { + caughtReadable = true; + done(); + }); + }); + + function done() { + // we're testing what we think we are + t.ok(_readCalled); + + t.ok(caughtReadable); + t.end(); + } +}); + +test('stream.Readable - readable event - third', function (t) { + // Third test, not reading when the stream has not passed + // the highWaterMark but *has* reached EOF. + var r = new Readable({ + highWaterMark: 30 + }); + + var _readCalled = false; + r._read = function(n) { + _readCalled = true; + }; + + // This triggers a 'readable' event, which is lost. + r.push(new Buffer('blerg')); + r.push(null); + + var caughtReadable = false; + setTimeout(function() { + // assert we're testing what we think we are + t.ok(!r._readableState.reading); + r.on('readable', function() { + caughtReadable = true; + done(); + }); + }); + + function done() { + // we're testing what we think we are + t.ok(!_readCalled); + + t.ok(caughtReadable); + t.end(); + } +}); diff --git a/test/browser/stream-readable-flow-recursion.js b/test/browser/stream-readable-flow-recursion.js new file mode 100644 index 0000000..3d6cb4f --- /dev/null +++ b/test/browser/stream-readable-flow-recursion.js @@ -0,0 +1,51 @@ + +var test = require('tape'); +var Readable = require('stream').Readable; +var Buffer = require('buffer').Buffer; + +// this test verifies that passing a huge number to read(size) +// will push up the highWaterMark, and cause the stream to read +// more data continuously, but without triggering a nextTick +// warning or RangeError. + +test('stream - readable flow recursion', function (t) { + + var stream = new Readable({ highWaterMark: 2 }); + var reads = 0; + var total = 5000; + stream._read = function(size) { + reads++; + size = Math.min(size, total); + total -= size; + if (size === 0) + stream.push(null); + else + stream.push(new Buffer(size)); + }; + + var depth = 0; + + function flow(stream, size, callback) { + depth += 1; + var chunk = stream.read(size); + + if (!chunk) + stream.once('readable', flow.bind(null, stream, size, callback)); + else + callback(chunk); + + depth -= 1; + } + + flow(stream, 5000, function() { }); + + stream.on('end', function() { + t.equal(reads, 2); + // we pushed up the high water mark + t.equal(stream._readableState.highWaterMark, 8192); + // length is 0 right now, because we pulled it all out. + t.equal(stream._readableState.length, 0); + t.equal(depth, 0); + t.end(); + }); +}); diff --git a/test/browser/stream-unshift-empty-chunk.js b/test/browser/stream-unshift-empty-chunk.js new file mode 100644 index 0000000..31f8f26 --- /dev/null +++ b/test/browser/stream-unshift-empty-chunk.js @@ -0,0 +1,63 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +// This test verifies that stream.unshift(Buffer(0)) or +// stream.unshift('') does not set state.reading=false. +var Readable = require('stream').Readable; + +test('stream - unshift empty chunk', function (t) { + var r = new Readable(); + var nChunks = 10; + var chunk = new Buffer(10); + chunk.fill('x'); + + r._read = function(n) { + setTimeout(function() { + r.push(--nChunks === 0 ? null : chunk); + }); + }; + + var readAll = false; + var seen = []; + r.on('readable', function() { + var chunk; + while (chunk = r.read()) { + seen.push(chunk.toString()); + // simulate only reading a certain amount of the data, + // and then putting the rest of the chunk back into the + // stream, like a parser might do. We just fill it with + // 'y' so that it's easy to see which bits were touched, + // and which were not. + var putBack = new Buffer(readAll ? 0 : 5); + putBack.fill('y'); + readAll = !readAll; + r.unshift(putBack); + } + }); + + var expect = + [ 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy' ]; + + r.on('end', function() { + t.deepEqual(seen, expect); + t.end(); + }); +}); diff --git a/test/browser/stream-unshift-read-race.js b/test/browser/stream-unshift-read-race.js new file mode 100644 index 0000000..8246828 --- /dev/null +++ b/test/browser/stream-unshift-read-race.js @@ -0,0 +1,115 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +// This test verifies that: +// 1. unshift() does not cause colliding _read() calls. +// 2. unshift() after the 'end' event is an error, but after the EOF +// signalling null, it is ok, and just creates a new readable chunk. +// 3. push() after the EOF signaling null is an error. +// 4. _read() is not called after pushing the EOF null chunk. + +var stream = require('stream'); + +test('stream - unshift read race', function (tt) { + var hwm = 10; + var r = stream.Readable({ highWaterMark: hwm }); + var chunks = 10; + var t = (chunks * 5); + + var data = new Buffer(chunks * hwm + Math.ceil(hwm / 2)); + for (var i = 0; i < data.length; i++) { + var c = 'asdf'.charCodeAt(i % 4); + data[i] = c; + } + + var pos = 0; + var pushedNull = false; + r._read = function(n) { + tt.ok(!pushedNull, '_read after null push'); + + // every third chunk is fast + push(!(chunks % 3)); + + function push(fast) { + tt.ok(!pushedNull, 'push() after null push'); + var c = pos >= data.length ? null : data.slice(pos, pos + n); + pushedNull = c === null; + if (fast) { + pos += n; + r.push(c); + if (c === null) pushError(); + } else { + setTimeout(function() { + pos += n; + r.push(c); + if (c === null) pushError(); + }); + } + } + }; + + function pushError() { + tt.throws(function() { + r.push(new Buffer(1)); + }); + } + + + var w = stream.Writable(); + var written = []; + w._write = function(chunk, encoding, cb) { + written.push(chunk.toString()); + cb(); + }; + + var ended = false; + r.on('end', function() { + tt.ok(!ended, 'end emitted more than once'); + tt.throws(function() { + r.unshift(new Buffer(1)); + }); + ended = true; + w.end(); + }); + + r.on('readable', function() { + var chunk; + while (null !== (chunk = r.read(10))) { + w.write(chunk); + if (chunk.length > 4) + r.unshift(new Buffer('1234')); + } + }); + + var finished = false; + w.on('finish', function() { + finished = true; + // each chunk should start with 1234, and then be asfdasdfasdf... + // The first got pulled out before the first unshift('1234'), so it's + // lacking that piece. + tt.equal(written[0], 'asdfasdfas'); + var asdf = 'd'; + for (var i = 1; i < written.length; i++) { + tt.equal(written[i].slice(0, 4), '1234'); + for (var j = 4; j < written[i].length; j++) { + var c = written[i].charAt(j); + tt.equal(c, asdf); + switch (asdf) { + case 'a': asdf = 's'; break; + case 's': asdf = 'd'; break; + case 'd': asdf = 'f'; break; + case 'f': asdf = 'a'; break; + } + } + } + }); + + function done() { + tt.equal(written.length, 18); + tt.ok(ended, 'stream ended'); + tt.ok(finished, 'stream finished'); + tt.end(); + } + w.on('finish', done); +}); diff --git a/test/browser/stream2-basic.js b/test/browser/stream2-basic.js new file mode 100644 index 0000000..753eeb1 --- /dev/null +++ b/test/browser/stream2-basic.js @@ -0,0 +1,417 @@ +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +var R = require('stream').Readable; +var timers = require('timers'); + +var util = require('util'); +var EE = require('events').EventEmitter; + +function TestReader(n) { + R.apply(this); + this._buffer = new Buffer(n || 100); + this._buffer.fill('x'); + this._pos = 0; + this._bufs = 10; +} + +util.inherits(TestReader, R); + +TestReader.prototype.read = function(n) { + if (n === 0) return null; + var max = this._buffer.length - this._pos; + n = n || max; + n = Math.max(n, 0); + var toRead = Math.min(n, max); + if (toRead === 0) { + // simulate the read buffer filling up with some more bytes some time + // in the future. + setTimeout(function() { + this._pos = 0; + this._bufs -= 1; + if (this._bufs <= 0) { + // read them all! + if (!this.ended) { + this.emit('end'); + this.ended = true; + } + } else { + this.emit('readable'); + } + }.bind(this), 10); + return null; + } + + var ret = this._buffer.slice(this._pos, this._pos + toRead); + this._pos += toRead; + return ret; +}; + +///// + +function TestWriter() { + EE.apply(this); + this.received = []; + this.flush = false; +} + +util.inherits(TestWriter, EE); + +TestWriter.prototype.write = function(c) { + this.received.push(c.toString()); + this.emit('write', c); + return true; +}; + +TestWriter.prototype.end = function(c) { + if (c) this.write(c); + this.emit('end', this.received); +}; + +test('a most basic test', function(t) { + var r = new TestReader(20); + + var reads = []; + var expect = [ 'x', + 'xx', + 'xxx', + 'xxxx', + 'xxxxx', + 'xxxxx', + 'xxxxxxxx', + 'xxxxxxxxx', + 'xxx', + 'xxxxxxxxxxxx', + 'xxxxxxxx', + 'xxxxxxxxxxxxxxx', + 'xxxxx', + 'xxxxxxxxxxxxxxxxxx', + 'xx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx' ]; + + r.on('end', function() { + t.same(reads, expect); + t.end(); + }); + + var readSize = 1; + function flow() { + var res; + while (null !== (res = r.read(readSize++))) { + reads.push(res.toString()); + } + r.once('readable', flow); + } + + flow(); +}); + +test('pipe', function(t) { + var r = new TestReader(5); + + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ] + + var w = new TestWriter; + var flush = true; + + w.on('end', function(received) { + t.same(received, expect); + t.end(); + }); + + r.pipe(w); +}); + + + +[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { + test('unpipe', function(t) { + var r = new TestReader(5); + + // unpipe after 3 writes, then write to another stream instead. + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + var w = [ new TestWriter(), new TestWriter() ]; + + var writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + t.equal(r._readableState.pipes, null); + w[0].end(); + r.pipe(w[1]); + t.equal(r._readableState.pipes, w[1]); + } + }); + + var ended = 0; + + var ended0 = false; + var ended1 = false; + w[0].on('end', function(results) { + t.equal(ended0, false); + ended0 = true; + ended++; + t.same(results, expect[0]); + }); + + w[1].on('end', function(results) { + t.equal(ended1, false); + ended1 = true; + ended++; + t.equal(ended, 2); + t.same(results, expect[1]); + t.end(); + }); + + r.pipe(w[0]); + }); +}); + + +// both writers should get the same exact data. +test('multipipe', function(t) { + var r = new TestReader(5); + var w = [ new TestWriter, new TestWriter ]; + + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + + var c = 2; + w[0].on('end', function(received) { + t.same(received, expect, 'first'); + if (--c === 0) t.end(); + }); + w[1].on('end', function(received) { + t.same(received, expect, 'second'); + if (--c === 0) t.end(); + }); + + r.pipe(w[0]); + r.pipe(w[1]); +}); + + +[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { + test('multi-unpipe', function(t) { + var r = new TestReader(5); + + // unpipe after 3 writes, then write to another stream instead. + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + var w = [ new TestWriter(), new TestWriter(), new TestWriter() ]; + + var writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + w[0].end(); + r.pipe(w[1]); + } + }); + + var ended = 0; + + w[0].on('end', function(results) { + ended++; + t.same(results, expect[0]); + }); + + w[1].on('end', function(results) { + ended++; + t.equal(ended, 2); + t.same(results, expect[1]); + t.end(); + }); + + r.pipe(w[0]); + r.pipe(w[2]); + }); +}); + +test('back pressure respected', function (t) { + function noop() {} + + var r = new R({ objectMode: true }); + r._read = noop; + var counter = 0; + r.push(["one"]); + r.push(["two"]); + r.push(["three"]); + r.push(["four"]); + r.push(null); + + var w1 = new R(); + w1.write = function (chunk) { + t.equal(chunk[0], "one"); + w1.emit("close"); + timers.setImmediate(function () { + r.pipe(w2); + r.pipe(w3); + }) + }; + w1.end = noop; + + r.pipe(w1); + + var expected = ["two", "two", "three", "three", "four", "four"]; + + var w2 = new R(); + w2.write = function (chunk) { + t.equal(chunk[0], expected.shift()); + t.equal(counter, 0); + + counter++; + + if (chunk[0] === "four") { + return true; + } + + setTimeout(function () { + counter--; + w2.emit("drain"); + }, 10); + + return false; + } + w2.end = noop; + + var w3 = new R(); + w3.write = function (chunk) { + t.equal(chunk[0], expected.shift()); + t.equal(counter, 1); + + counter++; + + if (chunk[0] === "four") { + return true; + } + + setTimeout(function () { + counter--; + w3.emit("drain"); + }, 50); + + return false; + }; + w3.end = function () { + t.equal(counter, 2); + t.equal(expected.length, 0); + t.end(); + }; +}); + +test('read(0) for ended streams', function (t) { + var r = new R(); + var written = false; + var ended = false; + r._read = function (n) {}; + + r.push(new Buffer("foo")); + r.push(null); + + var v = r.read(0); + + t.equal(v, null); + + var w = new R(); + + w.write = function (buffer) { + written = true; + t.equal(ended, false); + t.equal(buffer.toString(), "foo") + }; + + w.end = function () { + ended = true; + t.equal(written, true); + t.end(); + }; + + r.pipe(w); +}) + +test('sync _read ending', function (t) { + var r = new R(); + var called = false; + r._read = function (n) { + r.push(null); + }; + + r.once('end', function () { + called = true; + }) + + r.read(); + + timers.setImmediate(function () { + t.equal(called, true); + t.end(); + }) +}); + +test('adding readable triggers data flow', function(t) { + var r = new R({ highWaterMark: 5 }); + var onReadable = false; + var readCalled = 0; + + r._read = function(n) { + if (readCalled++ === 2) + r.push(null); + else + r.push(new Buffer('asdf')); + }; + + var called = false; + r.on('readable', function() { + onReadable = true; + r.read(); + }); + + r.on('end', function() { + t.equal(readCalled, 3); + t.ok(onReadable); + t.end(); + }); +}); diff --git a/test/browser/stream2-compatibility.js b/test/browser/stream2-compatibility.js new file mode 100644 index 0000000..360baa0 --- /dev/null +++ b/test/browser/stream2-compatibility.js @@ -0,0 +1,33 @@ + +var R = require('stream').Readable; +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +var util = require('util'); +var EE = require('events').EventEmitter; + +test('stream2 - compatibility', function (t) { + + var ondataCalled = 0; + + function TestReader() { + R.apply(this); + this._buffer = new Buffer(100); + this._buffer.fill('x'); + + this.on('data', function() { + ondataCalled++; + }); + } + + util.inherits(TestReader, R); + + TestReader.prototype._read = function(n) { + this.push(this._buffer); + this._buffer = new Buffer(0); + }; + + var reader = new TestReader(); + t.equal(ondataCalled, 1); + t.end(); +}); \ No newline at end of file diff --git a/test/browser/stream2-finish-pipe.js b/test/browser/stream2-finish-pipe.js new file mode 100644 index 0000000..78942b0 --- /dev/null +++ b/test/browser/stream2-finish-pipe.js @@ -0,0 +1,24 @@ + +var test = require('tape'); +var stream = require('stream'); +var Buffer = require('buffer').Buffer; + +test('stream2 - finish pipe', function (t) { + var r = new stream.Readable(); + r._read = function(size) { + r.push(new Buffer(size)); + }; + + var w = new stream.Writable(); + w._write = function(data, encoding, cb) { + cb(null); + }; + + r.pipe(w); + + // This might sound unrealistic, but it happens in net.js. When + // `socket.allowHalfOpen === false`, EOF will cause `.destroySoon()` call which + // ends the writable side of net.Socket. + w.end(); + t.end(); +}); diff --git a/test/browser/stream2-large-read-stall.js b/test/browser/stream2-large-read-stall.js new file mode 100644 index 0000000..bbba193 --- /dev/null +++ b/test/browser/stream2-large-read-stall.js @@ -0,0 +1,55 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +test('stream2 - large read stall', function (t) { + // If everything aligns so that you do a read(n) of exactly the + // remaining buffer, then make sure that 'end' still emits. + + var READSIZE = 100; + var PUSHSIZE = 20; + var PUSHCOUNT = 1000; + var HWM = 50; + + var Readable = require('stream').Readable; + var r = new Readable({ + highWaterMark: HWM + }); + var rs = r._readableState; + + r._read = push; + + r.on('readable', function() { + do { + var ret = r.read(READSIZE); + } while (ret && ret.length === READSIZE); + }); + + var endEmitted = false; + r.on('end', function() { + endEmitted = true; + }); + + var pushes = 0; + function push() { + if (pushes > PUSHCOUNT) + return; + + if (pushes++ === PUSHCOUNT) { + return r.push(null); + } + + if (r.push(new Buffer(PUSHSIZE))) + setTimeout(push); + } + + // start the flow + var ret = r.read(0); + + function done() { + t.equal(pushes, PUSHCOUNT + 1); + t.ok(endEmitted); + t.end(); + } + r.on('end', done); +}); diff --git a/test/browser/stream2-objects.js b/test/browser/stream2-objects.js new file mode 100644 index 0000000..91730c3 --- /dev/null +++ b/test/browser/stream2-objects.js @@ -0,0 +1,293 @@ + +var Readable = require('stream').Readable; +var Writable = require('stream').Writable; +var test = require('tape'); +var timers = require('timers'); + +function toArray(callback) { + var stream = new Writable({ objectMode: true }); + var list = []; + stream.write = function(chunk) { + list.push(chunk); + }; + + stream.end = function() { + callback(list); + }; + + return stream; +} + +function fromArray(list) { + var r = new Readable({ objectMode: true }); + r._read = noop; + list.forEach(function(chunk) { + r.push(chunk); + }); + r.push(null); + + return r; +} + +function noop() {} + +test('can read objects from stream', function(t) { + var r = fromArray([{ one: '1'}, { two: '2' }]); + + var v1 = r.read(); + var v2 = r.read(); + var v3 = r.read(); + + t.deepEqual(v1, { one: '1' }); + t.deepEqual(v2, { two: '2' }); + t.deepEqual(v3, null); + + t.end(); +}); + +test('can pipe objects into stream', function(t) { + var r = fromArray([{ one: '1'}, { two: '2' }]); + + r.pipe(toArray(function(list) { + t.deepEqual(list, [ + { one: '1' }, + { two: '2' } + ]); + + t.end(); + })); +}); + +test('read(n) is ignored', function(t) { + var r = fromArray([{ one: '1'}, { two: '2' }]); + + var value = r.read(2); + + t.deepEqual(value, { one: '1' }); + + t.end(); +}); + +test('can read objects from _read (sync)', function(t) { + var r = new Readable({ objectMode: true }); + var list = [{ one: '1'}, { two: '2' }]; + r._read = function(n) { + var item = list.shift(); + r.push(item || null); + }; + + r.pipe(toArray(function(list) { + t.deepEqual(list, [ + { one: '1' }, + { two: '2' } + ]); + + t.end(); + })); +}); + +test('can read objects from _read (async)', function(t) { + var r = new Readable({ objectMode: true }); + var list = [{ one: '1'}, { two: '2' }]; + r._read = function(n) { + var item = list.shift(); + timers.setImmediate(function() { + r.push(item || null); + }); + }; + + r.pipe(toArray(function(list) { + t.deepEqual(list, [ + { one: '1' }, + { two: '2' } + ]); + + t.end(); + })); +}); + +test('can read strings as objects', function(t) { + var r = new Readable({ + objectMode: true + }); + r._read = noop; + var list = ['one', 'two', 'three']; + list.forEach(function(str) { + r.push(str); + }); + r.push(null); + + r.pipe(toArray(function(array) { + t.deepEqual(array, list); + + t.end(); + })); +}); + +test('read(0) for object streams', function(t) { + var r = new Readable({ + objectMode: true + }); + r._read = noop; + + r.push('foobar'); + r.push(null); + + var v = r.read(0); + + r.pipe(toArray(function(array) { + t.deepEqual(array, ['foobar']); + + t.end(); + })); +}); + +test('falsey values', function(t) { + var r = new Readable({ + objectMode: true + }); + r._read = noop; + + r.push(false); + r.push(0); + r.push(''); + r.push(null); + + r.pipe(toArray(function(array) { + t.deepEqual(array, [false, 0, '']); + + t.end(); + })); +}); + +test('high watermark _read', function(t) { + var r = new Readable({ + highWaterMark: 6, + objectMode: true + }); + var calls = 0; + var list = ['1', '2', '3', '4', '5', '6', '7', '8']; + + r._read = function(n) { + calls++; + }; + + list.forEach(function(c) { + r.push(c); + }); + + var v = r.read(); + + t.equal(calls, 0); + t.equal(v, '1'); + + var v2 = r.read(); + + t.equal(calls, 1); + t.equal(v2, '2'); + + t.end(); +}); + +test('high watermark push', function(t) { + var r = new Readable({ + highWaterMark: 6, + objectMode: true + }); + r._read = function(n) {}; + for (var i = 0; i < 6; i++) { + var bool = r.push(i); + t.equal(bool, i === 5 ? false : true); + } + + t.end(); +}); + +test('can write objects to stream', function(t) { + var w = new Writable({ objectMode: true }); + + w._write = function(chunk, encoding, cb) { + t.deepEqual(chunk, { foo: 'bar' }); + cb(); + }; + + w.on('finish', function() { + t.end(); + }); + + w.write({ foo: 'bar' }); + w.end(); +}); + +test('can write multiple objects to stream', function(t) { + var w = new Writable({ objectMode: true }); + var list = []; + + w._write = function(chunk, encoding, cb) { + list.push(chunk); + cb(); + }; + + w.on('finish', function() { + t.deepEqual(list, [0, 1, 2, 3, 4]); + + t.end(); + }); + + w.write(0); + w.write(1); + w.write(2); + w.write(3); + w.write(4); + w.end(); +}); + +test('can write strings as objects', function(t) { + var w = new Writable({ + objectMode: true + }); + var list = []; + + w._write = function(chunk, encoding, cb) { + list.push(chunk); + timers.setImmediate(cb); + }; + + w.on('finish', function() { + t.deepEqual(list, ['0', '1', '2', '3', '4']); + + t.end(); + }); + + w.write('0'); + w.write('1'); + w.write('2'); + w.write('3'); + w.write('4'); + w.end(); +}); + +test('buffers finish until cb is called', function(t) { + var w = new Writable({ + objectMode: true + }); + var called = false; + + w._write = function(chunk, encoding, cb) { + t.equal(chunk, 'foo'); + + timers.setImmediate(function() { + called = true; + cb(); + }); + }; + + w.on('finish', function() { + t.equal(called, true); + + t.end(); + }); + + w.write('foo'); + w.end(); +}); diff --git a/test/browser/stream2-pipe-error-handling.js b/test/browser/stream2-pipe-error-handling.js new file mode 100644 index 0000000..7da84da --- /dev/null +++ b/test/browser/stream2-pipe-error-handling.js @@ -0,0 +1,87 @@ + +var test = require('tape'); +var stream = require('stream'); +var Buffer = require('buffer').Buffer; + +test('stream2 - pipe error handling - error listener catches', function (t) { + var count = 1000; + + var source = new stream.Readable(); + source._read = function(n) { + n = Math.min(count, n); + count -= n; + source.push(new Buffer(n)); + }; + + var unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + var dest = new stream.Writable(); + dest._write = function(chunk, encoding, cb) { + cb(); + }; + + source.pipe(dest); + + var gotErr = null; + dest.on('error', function(err) { + gotErr = err; + }); + + var unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + var err = new Error('This stream turned into bacon.'); + dest.emit('error', err); + t.strictEqual(gotErr, err); + t.strictEqual(unpipedSource, source); + t.strictEqual(unpipedDest, dest); + t.end(); +}); + +test('stream2 - pipe error handling - error listener catches', function (t) { + var count = 1000; + + var source = new stream.Readable(); + source._read = function(n) { + n = Math.min(count, n); + count -= n; + source.push(new Buffer(n)); + }; + + var unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + var dest = new stream.Writable(); + dest._write = function(chunk, encoding, cb) { + cb(); + }; + + source.pipe(dest); + + var unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + var err = new Error('This stream turned into bacon.'); + + var gotErr = null; + try { + dest.emit('error', err); + } catch (e) { + gotErr = e; + } + t.strictEqual(gotErr, err); + t.strictEqual(unpipedSource, source); + t.strictEqual(unpipedDest, dest); + t.end(); +}); diff --git a/test/browser/stream2-push.js b/test/browser/stream2-push.js new file mode 100644 index 0000000..139c777 --- /dev/null +++ b/test/browser/stream2-push.js @@ -0,0 +1,112 @@ + +var stream = require('stream'); +var Readable = stream.Readable; +var Writable = stream.Writable; +var test = require('tape'); +var timers = require('timers'); + +var EE = require('events').EventEmitter; + +test('stream2 - push', function (t) { + // a mock thing a bit like the net.Socket/tcp_wrap.handle interaction + + var stream = new Readable({ + highWaterMark: 16, + encoding: 'utf8' + }); + + var source = new EE; + + stream._read = function() { + readStart(); + }; + + var ended = false; + stream.on('end', function() { + ended = true; + }); + + source.on('data', function(chunk) { + var ret = stream.push(chunk); + if (!ret) + readStop(); + }); + + source.on('end', function() { + stream.push(null); + }); + + var reading = false; + + function readStart() { + reading = true; + } + + function readStop() { + reading = false; + timers.setImmediate(function() { + var r = stream.read(); + if (r !== null) + writer.write(r); + }); + } + + var writer = new Writable({ + decodeStrings: false + }); + + var written = []; + + var expectWritten = + [ 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg' ]; + + writer._write = function(chunk, encoding, cb) { + written.push(chunk); + timers.setImmediate(cb); + }; + + writer.on('finish', finish); + + + // now emit some chunks. + + var chunk = "asdfg"; + + var set = 0; + readStart(); + data(); + function data() { + t.ok(reading); + source.emit('data', chunk); + t.ok(reading); + source.emit('data', chunk); + t.ok(reading); + source.emit('data', chunk); + t.ok(reading); + source.emit('data', chunk); + t.ok(!reading); + if (set++ < 5) + setTimeout(data, 10); + else + end(); + } + + function finish() { + t.deepEqual(written, expectWritten); + } + + function end() { + source.emit('end'); + t.ok(!reading); + writer.end(stream.read()); + setTimeout(function() { + t.ok(ended); + t.end(); + }); + } +}); \ No newline at end of file diff --git a/test/browser/stream2-read-sync-stack.js b/test/browser/stream2-read-sync-stack.js new file mode 100644 index 0000000..a1d41b0 --- /dev/null +++ b/test/browser/stream2-read-sync-stack.js @@ -0,0 +1,31 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; +var Readable = require('stream').Readable; +var r = new Readable(); +var N = 256 * 1024; + +test('stream2 - read sync stack', function (t) { + var reads = 0; + r._read = function(n) { + var chunk = reads++ === N ? null : new Buffer(1); + r.push(chunk); + }; + + r.on('readable', function onReadable() { + r.read(N * 2); + }); + + var ended = false; + r.on('end', function onEnd() { + ended = true; + }); + + r.read(0); + + function done() { + t.ok(ended); + t.end(); + } + r.on('end', done); +}); diff --git a/test/browser/stream2-readable-empty-buffer-no-eof.js b/test/browser/stream2-readable-empty-buffer-no-eof.js new file mode 100644 index 0000000..41239bf --- /dev/null +++ b/test/browser/stream2-readable-empty-buffer-no-eof.js @@ -0,0 +1,97 @@ + +var test = require('tape'); +var timers = require('timers'); +var Buffer = require('buffer').Buffer; +var Readable = require('stream').Readable; + +test('steam2 - readable empty buffer to eof - 1', function (t) { + var r = new Readable(); + + // should not end when we get a Buffer(0) or '' as the _read result + // that just means that there is *temporarily* no data, but to go + // ahead and try again later. + // + // note that this is very unusual. it only works for crypto streams + // because the other side of the stream will call read(0) to cycle + // data through openssl. that's why we set the timeouts to call + // r.read(0) again later, otherwise there is no more work being done + // and the process just exits. + + var buf = new Buffer(5); + buf.fill('x'); + var reads = 5; + r._read = function(n) { + switch (reads--) { + case 0: + return r.push(null); // EOF + case 1: + return r.push(buf); + case 2: + setTimeout(r.read.bind(r, 0), 10); + return r.push(new Buffer(0)); // Not-EOF! + case 3: + setTimeout(r.read.bind(r, 0), 10); + return timers.setImmediate(function() { + return r.push(new Buffer(0)); + }); + case 4: + setTimeout(r.read.bind(r, 0), 10); + return setTimeout(function() { + return r.push(new Buffer(0)); + }); + case 5: + return setTimeout(function() { + return r.push(buf); + }); + default: + throw new Error('unreachable'); + } + }; + + var results = []; + function flow() { + var chunk; + while (null !== (chunk = r.read())) + results.push(chunk + ''); + } + r.on('readable', flow); + r.on('end', function() { + results.push('EOF'); + }); + flow(); + + function done() { + t.deepEqual(results, [ 'xxxxx', 'xxxxx', 'EOF' ]); + t.end(); + } + r.on('end', done); +}); + +test('steam2 - readable empty buffer to eof - 2', function (t) { + var r = new Readable({ encoding: 'base64' }); + var reads = 5; + r._read = function(n) { + if (!reads--) + return r.push(null); // EOF + else + return r.push(new Buffer('x')); + }; + + var results = []; + function flow() { + var chunk; + while (null !== (chunk = r.read())) + results.push(chunk + ''); + } + r.on('readable', flow); + r.on('end', function() { + results.push('EOF'); + }); + flow(); + + function done() { + t.deepEqual(results, [ 'eHh4', 'eHg=', 'EOF' ]); + t.end(); + } + r.on('end', done); +}); diff --git a/test/browser/stream2-readable-from-list.js b/test/browser/stream2-readable-from-list.js new file mode 100644 index 0000000..80f2aaa --- /dev/null +++ b/test/browser/stream2-readable-from-list.js @@ -0,0 +1,64 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; +var fromList = require('stream').Readable._fromList; + +test('buffers', function(t) { + // have a length + var len = 16; + var list = [ new Buffer('foog'), + new Buffer('bark'), + new Buffer('bazy'), + new Buffer('kuel') ]; + + // read more than the first element. + var ret = fromList(6, { buffer: list, length: 16 }); + t.equal(ret.toString(), 'foogba'); + + // read exactly the first element. + ret = fromList(2, { buffer: list, length: 10 }); + t.equal(ret.toString(), 'rk'); + + // read less than the first element. + ret = fromList(2, { buffer: list, length: 8 }); + t.equal(ret.toString(), 'ba'); + + // read more than we have. + ret = fromList(100, { buffer: list, length: 6 }); + t.equal(ret.toString(), 'zykuel'); + + // all consumed. + t.same(list, []); + + t.end(); +}); + +test('strings', function(t) { + // have a length + var len = 16; + var list = [ 'foog', + 'bark', + 'bazy', + 'kuel' ]; + + // read more than the first element. + var ret = fromList(6, { buffer: list, length: 16, decoder: true }); + t.equal(ret, 'foogba'); + + // read exactly the first element. + ret = fromList(2, { buffer: list, length: 10, decoder: true }); + t.equal(ret, 'rk'); + + // read less than the first element. + ret = fromList(2, { buffer: list, length: 8, decoder: true }); + t.equal(ret, 'ba'); + + // read more than we have. + ret = fromList(100, { buffer: list, length: 6, decoder: true }); + t.equal(ret, 'zykuel'); + + // all consumed. + t.same(list, []); + + t.end(); +}); diff --git a/test/browser/stream2-readable-legacy-drain.js b/test/browser/stream2-readable-legacy-drain.js new file mode 100644 index 0000000..07bd551 --- /dev/null +++ b/test/browser/stream2-readable-legacy-drain.js @@ -0,0 +1,59 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; + +var Stream = require('stream'); +var Readable = Stream.Readable; +var timers = require('timers'); + +test('stream2 - readable legacy drain', function (t) { + var r = new Readable(); + var N = 256; + var reads = 0; + r._read = function(n) { + return r.push(++reads === N ? null : new Buffer(1)); + }; + + var rended = false; + r.on('end', function() { + rended = true; + }); + + var w = new Stream(); + w.writable = true; + var writes = 0; + var buffered = 0; + w.write = function(c) { + writes += c.length; + buffered += c.length; + timers.setImmediate(drain); + return false; + }; + + function drain() { + t.ok(buffered <= 2); + buffered = 0; + w.emit('drain'); + } + + + var wended = false; + w.end = function() { + wended = true; + }; + + // Just for kicks, let's mess with the drain count. + // This verifies that even if it gets negative in the + // pipe() cleanup function, we'll still function properly. + r.on('readable', function() { + w.emit('drain'); + }); + + r.pipe(w); + function done() { + t.ok(rended); + t.ok(wended); + t.end(); + } + r.on('end', done); +}); diff --git a/test/browser/stream2-readable-non-empty-end.js b/test/browser/stream2-readable-non-empty-end.js new file mode 100644 index 0000000..ff0fe98 --- /dev/null +++ b/test/browser/stream2-readable-non-empty-end.js @@ -0,0 +1,58 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; +var Readable = require('_stream_readable'); + +test('stream2 - readable non empty end', function (t) { + var len = 0; + var chunks = new Array(10); + for (var i = 1; i <= 10; i++) { + chunks[i-1] = new Buffer(i); + len += i; + } + + var test = new Readable(); + var n = 0; + test._read = function(size) { + var chunk = chunks[n++]; + setTimeout(function() { + test.push(chunk); + }); + }; + + test.on('end', thrower); + function thrower() { + throw new Error('this should not happen!'); + } + + var bytesread = 0; + test.on('readable', function() { + var b = len - bytesread - 1; + var res = test.read(b); + if (res) { + bytesread += res.length; + console.error('br=%d len=%d', bytesread, len); + setTimeout(next); + } + test.read(0); + }); + test.read(0); + + function next() { + // now let's make 'end' happen + test.removeListener('end', thrower); + + var endEmitted = false; + test.on('end', function() { + endEmitted = true; + }); + + // one to get the last byte + var r = test.read(); + t.ok(r); + t.equal(r.length, 1); + r = test.read(); + t.equal(r, null); + t.end(); + } +}); diff --git a/test/browser/stream2-readable-wrap-empty.js b/test/browser/stream2-readable-wrap-empty.js new file mode 100644 index 0000000..8a065b6 --- /dev/null +++ b/test/browser/stream2-readable-wrap-empty.js @@ -0,0 +1,26 @@ + +var test = require('tape'); + +var Readable = require('_stream_readable'); +var EE = require('events').EventEmitter; + +test('stream2 - readable wrap empty', function (t) { + var oldStream = new EE(); + oldStream.pause = function(){}; + oldStream.resume = function(){}; + + var newStream = new Readable().wrap(oldStream); + + var ended = false; + newStream + .on('readable', function(){}) + .on('end', function(){ ended = true; }); + + oldStream.emit('end'); + + function done(){ + t.ok(ended); + t.end(); + } + newStream.on('end', done); +}); \ No newline at end of file diff --git a/test/browser/stream2-set-encoding.js b/test/browser/stream2-set-encoding.js new file mode 100644 index 0000000..1620f05 --- /dev/null +++ b/test/browser/stream2-set-encoding.js @@ -0,0 +1,300 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; +var R = require('stream').Readable; +var util = require('util'); + +util.inherits(TestReader, R); + +function TestReader(n, opts) { + R.call(this, opts); + + this.pos = 0; + this.len = n || 100; +} + +TestReader.prototype._read = function(n) { + setTimeout(function() { + + if (this.pos >= this.len) { + // double push(null) to test eos handling + this.push(null); + return this.push(null); + } + + n = Math.min(n, this.len - this.pos); + if (n <= 0) { + // double push(null) to test eos handling + this.push(null); + return this.push(null); + } + + this.pos += n; + var ret = new Buffer(n); + ret.fill('a'); + + return this.push(ret); + }.bind(this), 1); +}; + +test('setEncoding utf8', function(t) { + var tr = new TestReader(100); + tr.setEncoding('utf8'); + var out = []; + var expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + + +test('setEncoding hex', function(t) { + var tr = new TestReader(100); + tr.setEncoding('hex'); + var out = []; + var expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + +test('setEncoding hex with read(13)', function(t) { + var tr = new TestReader(100); + tr.setEncoding('hex'); + var out = []; + var expect = + [ "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "16161" ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + +test('setEncoding base64', function(t) { + var tr = new TestReader(100); + tr.setEncoding('base64'); + var out = []; + var expect = + [ 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYQ==' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + +test('encoding: utf8', function(t) { + var tr = new TestReader(100, { encoding: 'utf8' }); + var out = []; + var expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + + +test('encoding: hex', function(t) { + var tr = new TestReader(100, { encoding: 'hex' }); + var out = []; + var expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + +test('encoding: hex with read(13)', function(t) { + var tr = new TestReader(100, { encoding: 'hex' }); + var out = []; + var expect = + [ "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "16161" ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); + +test('encoding: base64', function(t) { + var tr = new TestReader(100, { encoding: 'base64' }); + var out = []; + var expect = + [ 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYQ==' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); +}); diff --git a/test/browser/stream2-transform.js b/test/browser/stream2-transform.js new file mode 100644 index 0000000..01dcc9b --- /dev/null +++ b/test/browser/stream2-transform.js @@ -0,0 +1,415 @@ + +var test = require('tape'); +var Buffer = require('buffer').Buffer; +var PassThrough = require('stream').PassThrough; +var Transform = require('stream').Transform; +var timers = require('timers'); + +test('writable side consumption', function(t) { + var tx = new Transform({ + highWaterMark: 10 + }); + + var transformed = 0; + tx._transform = function(chunk, encoding, cb) { + transformed += chunk.length; + tx.push(chunk); + cb(); + }; + + for (var i = 1; i <= 10; i++) { + tx.write(new Buffer(i)); + } + tx.end(); + + t.equal(tx._readableState.length, 10); + t.equal(transformed, 10); + t.equal(tx._transformState.writechunk.length, 5); + t.same(tx._writableState.buffer.map(function(c) { + return c.chunk.length; + }), [6, 7, 8, 9, 10]); + + t.end(); +}); + +test('passthrough', function(t) { + var pt = new PassThrough(); + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5).toString(), 'l'); + t.end(); +}); + +test('simple transform', function(t) { + var pt = new Transform; + pt._transform = function(c, e, cb) { + var ret = new Buffer(c.length); + ret.fill('x'); + pt.push(ret); + cb(); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'x'); + t.end(); +}); + +test('async passthrough', function(t) { + var pt = new Transform; + pt._transform = function(chunk, encoding, cb) { + setTimeout(function() { + pt.push(chunk); + cb(); + }, 10); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + pt.on('finish', function() { + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5).toString(), 'l'); + t.end(); + }); +}); + +test('assymetric transform (expand)', function(t) { + var pt = new Transform; + + // emit each chunk 2 times. + pt._transform = function(chunk, encoding, cb) { + setTimeout(function() { + pt.push(chunk); + setTimeout(function() { + pt.push(chunk); + cb(); + }, 10) + }, 10); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + pt.on('finish', function() { + t.equal(pt.read(5).toString(), 'foogf'); + t.equal(pt.read(5).toString(), 'oogba'); + t.equal(pt.read(5).toString(), 'rkbar'); + t.equal(pt.read(5).toString(), 'kbazy'); + t.equal(pt.read(5).toString(), 'bazyk'); + t.equal(pt.read(5).toString(), 'uelku'); + t.equal(pt.read(5).toString(), 'el'); + t.end(); + }); +}); + +test('assymetric transform (compress)', function(t) { + var pt = new Transform; + + // each output is the first char of 3 consecutive chunks, + // or whatever's left. + pt.state = ''; + + pt._transform = function(chunk, encoding, cb) { + if (!chunk) + chunk = ''; + var s = chunk.toString(); + setTimeout(function() { + this.state += s.charAt(0); + if (this.state.length === 3) { + pt.push(new Buffer(this.state)); + this.state = ''; + } + cb(); + }.bind(this), 10); + }; + + pt._flush = function(cb) { + // just output whatever we have. + pt.push(new Buffer(this.state)); + this.state = ''; + cb(); + }; + + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.write(new Buffer('eeee')); + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.write(new Buffer('eeee')); + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.end(); + + // 'abcdeabcdeabcd' + pt.on('finish', function() { + t.equal(pt.read(5).toString(), 'abcde'); + t.equal(pt.read(5).toString(), 'abcde'); + t.equal(pt.read(5).toString(), 'abcd'); + t.end(); + }); +}); + +// this tests for a stall when data is written to a full stream +// that has empty transforms. +test('complex transform', function(t) { + var count = 0; + var saved = null; + var pt = new Transform({highWaterMark:3}); + pt._transform = function(c, e, cb) { + if (count++ === 1) + saved = c; + else { + if (saved) { + pt.push(saved); + saved = null; + } + pt.push(c); + } + + cb(); + }; + + pt.once('readable', function() { + timers.setImmediate(function() { + pt.write(new Buffer('d')); + pt.write(new Buffer('ef'), function() { + pt.end(); + t.end(); + }); + t.equal(pt.read().toString(), 'abc'); + t.equal(pt.read().toString(), 'def'); + t.equal(pt.read(), null); + }); + }); + + pt.write(new Buffer('abc')); +}); + + +test('passthrough event emission', function(t) { + var pt = new PassThrough(); + var emits = 0; + pt.on('readable', function() { + var state = pt._readableState; + console.error('>>> emit readable %d', emits); + emits++; + }); + + var i = 0; + + pt.write(new Buffer('foog')); + + console.error('need emit 0'); + pt.write(new Buffer('bark')); + + console.error('should have emitted readable now 1 === %d', emits); + t.equal(emits, 1); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5) + '', 'null'); + + console.error('need emit 1'); + + pt.write(new Buffer('bazy')); + console.error('should have emitted, but not again'); + pt.write(new Buffer('kuel')); + + console.error('should have emitted readable now 2 === %d', emits); + t.equal(emits, 2); + + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5), null); + + console.error('need emit 2'); + + pt.end(); + + t.equal(emits, 3); + + t.equal(pt.read(5).toString(), 'l'); + t.equal(pt.read(5), null); + + console.error('should not have emitted again'); + t.equal(emits, 3); + t.end(); +}); + +test('passthrough event emission reordered', function(t) { + var pt = new PassThrough; + var emits = 0; + pt.on('readable', function() { + console.error('emit readable', emits) + emits++; + }); + + pt.write(new Buffer('foog')); + console.error('need emit 0'); + pt.write(new Buffer('bark')); + console.error('should have emitted readable now 1 === %d', emits); + t.equal(emits, 1); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5), null); + + console.error('need emit 1'); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'arkba'); + + t.equal(pt.read(5), null); + + console.error('need emit 2'); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5), null); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'l'); + t.equal(pt.read(5), null); + t.equal(emits, 4); + t.end(); + }); + pt.end(); + }); + pt.write(new Buffer('kuel')); + }); + + pt.write(new Buffer('bazy')); +}); + +test('passthrough facaded', function(t) { + console.error('passthrough facaded'); + var pt = new PassThrough; + var datas = []; + pt.on('data', function(chunk) { + datas.push(chunk.toString()); + }); + + pt.on('end', function() { + t.same(datas, ['foog', 'bark', 'bazy', 'kuel']); + t.end(); + }); + + pt.write(new Buffer('foog')); + setTimeout(function() { + pt.write(new Buffer('bark')); + setTimeout(function() { + pt.write(new Buffer('bazy')); + setTimeout(function() { + pt.write(new Buffer('kuel')); + setTimeout(function() { + pt.end(); + }, 10); + }, 10); + }, 10); + }, 10); +}); + +test('object transform (json parse)', function(t) { + console.error('json parse stream'); + var jp = new Transform({ objectMode: true }); + jp._transform = function(data, encoding, cb) { + try { + jp.push(JSON.parse(data)); + cb(); + } catch (er) { + cb(er); + } + }; + + // anything except null/undefined is fine. + // those are "magic" in the stream API, because they signal EOF. + var objects = [ + { foo: 'bar' }, + 100, + "string", + { nested: { things: [ { foo: 'bar' }, 100, "string" ] } } + ]; + + var ended = false; + jp.on('end', function() { + ended = true; + }); + + objects.forEach(function(obj) { + jp.write(JSON.stringify(obj)); + var res = jp.read(); + t.same(res, obj); + }); + + jp.end(); + + timers.setImmediate(function() { + t.ok(ended); + t.end(); + }) +}); + +test('object transform (json stringify)', function(t) { + console.error('json parse stream'); + var js = new Transform({ objectMode: true }); + js._transform = function(data, encoding, cb) { + try { + js.push(JSON.stringify(data)); + cb(); + } catch (er) { + cb(er); + } + }; + + // anything except null/undefined is fine. + // those are "magic" in the stream API, because they signal EOF. + var objects = [ + { foo: 'bar' }, + 100, + "string", + { nested: { things: [ { foo: 'bar' }, 100, "string" ] } } + ]; + + var ended = false; + js.on('end', function() { + ended = true; + }); + + objects.forEach(function(obj) { + js.write(obj); + var res = js.read(); + t.equal(res, JSON.stringify(obj)); + }); + + js.end(); + + timers.setImmediate(function() { + t.ok(ended); + t.end(); + }) +}); diff --git a/test/browser/stream2-unpipe-drain.js b/test/browser/stream2-unpipe-drain.js new file mode 100644 index 0000000..b909b8d --- /dev/null +++ b/test/browser/stream2-unpipe-drain.js @@ -0,0 +1,64 @@ + +var test = require('tape'); +var stream = require('stream'); +var timers = require('timers'); +var Buffer = require('buffer').Buffer; + +function randomBytes(size) { + var arr = []; + for (var i = 0; i < size; i++) arr[i] = Math.floor(Math.random() * 256); + return new Buffer(arr); +} + +var util = require('util'); + +test('stream2 - unpipe drain', function (t) { + function TestWriter() { + stream.Writable.call(this); + } + util.inherits(TestWriter, stream.Writable); + + TestWriter.prototype._write = function (buffer, encoding, callback) { + // super slow write stream (callback never called) + }; + + var dest = new TestWriter(); + + function TestReader(id) { + stream.Readable.call(this); + this.reads = 0; + } + util.inherits(TestReader, stream.Readable); + + TestReader.prototype._read = function (size) { + this.reads += 1; + this.push(randomBytes(size)); + }; + + var src1 = new TestReader(); + var src2 = new TestReader(); + + src1.pipe(dest); + + src1.once('readable', function () { + timers.setImmediate(function () { + + src2.pipe(dest); + + src2.once('readable', function () { + timers.setImmediate(function () { + + src1.unpipe(dest); + + done(); + }); + }); + }); + }); + + function done() { + t.equal(src1.reads, 2); + t.equal(src2.reads, 2); + t.end(); + } +}); diff --git a/test/browser/stream2-unpipe-leak.js b/test/browser/stream2-unpipe-leak.js new file mode 100644 index 0000000..0c036d1 --- /dev/null +++ b/test/browser/stream2-unpipe-leak.js @@ -0,0 +1,50 @@ + +var test = require('tape'); +var stream = require('stream'); +var Buffer = require('buffer').Buffer; + +var chunk = new Buffer('hallo'); + +var util = require('util'); + +test('stream2 - unpipe leak', function (t) { + function TestWriter() { + stream.Writable.call(this); + } + util.inherits(TestWriter, stream.Writable); + + TestWriter.prototype._write = function(buffer, encoding, callback) { + callback(null); + }; + + var dest = new TestWriter(); + + // Set this high so that we'd trigger a nextTick warning + // and/or RangeError if we do maybeReadMore wrong. + function TestReader() { + stream.Readable.call(this, { highWaterMark: 0x10000 }); + } + util.inherits(TestReader, stream.Readable); + + TestReader.prototype._read = function(size) { + this.push(chunk); + }; + + var src = new TestReader(); + + for (var i = 0; i < 10; i++) { + src.pipe(dest); + src.unpipe(dest); + } + + t.equal(src.listeners('end').length, 0); + t.equal(src.listeners('readable').length, 0); + + t.equal(dest.listeners('unpipe').length, 0); + t.equal(dest.listeners('drain').length, 0); + t.equal(dest.listeners('error').length, 0); + t.equal(dest.listeners('close').length, 0); + t.equal(dest.listeners('finish').length, 0); + + t.end(); +}); diff --git a/test/browser/stream2-writable.js b/test/browser/stream2-writable.js new file mode 100644 index 0000000..a2e4542 --- /dev/null +++ b/test/browser/stream2-writable.js @@ -0,0 +1,319 @@ + +var test = require('tape'); +var R = require('stream').Readable; +var W = require('stream').Writable; +var D = require('stream').Duplex; +var timers = require('timers'); +var Buffer = require('buffer').Buffer; + +var stdout = new R(); + +var util = require('util'); +util.inherits(TestWriter, W); + +function TestWriter() { + W.apply(this, arguments); + this.buffer = []; + this.written = 0; +} + +TestWriter.prototype._write = function(chunk, encoding, cb) { + // simulate a small unpredictable latency + setTimeout(function() { + this.buffer.push(chunk.toString()); + this.written += chunk.length; + cb(); + }.bind(this), Math.floor(Math.random() * 10)); +}; + +var chunks = new Array(50); +for (var i = 0; i < chunks.length; i++) { + chunks[i] = new Array(i + 1).join('x'); +} + +test('write fast', function(t) { + var tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.end(); + }); + + chunks.forEach(function(chunk) { + // screw backpressure. Just buffer it all up. + tw.write(chunk); + }); + tw.end(); +}); + +test('write slow', function(t) { + var tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.end(); + }); + + var i = 0; + (function W() { + tw.write(chunks[i++]); + if (i < chunks.length) + setTimeout(W, 10); + else + tw.end(); + })(); +}); + +test('write backpressure', function(t) { + var tw = new TestWriter({ + highWaterMark: 50 + }); + + var drains = 0; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.equal(drains, 17); + t.end(); + }); + + tw.on('drain', function() { + drains++; + }); + + var i = 0; + (function W() { + do { + var ret = tw.write(chunks[i++]); + } while (ret !== false && i < chunks.length); + + if (i < chunks.length) { + t.ok(tw._writableState.length >= 50); + tw.once('drain', W); + } else { + tw.end(); + } + })(); +}); + +test('write bufferize', function(t) { + var tw = new TestWriter({ + highWaterMark: 100 + }); + + var encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'binary', + 'base64', + undefined ]; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got the expected chunks'); + }); + + chunks.forEach(function(chunk, i) { + var enc = encodings[ i % encodings.length ]; + chunk = new Buffer(chunk); + tw.write(chunk.toString(enc), enc); + }); + t.end(); +}); + +test('write no bufferize', function(t) { + var tw = new TestWriter({ + highWaterMark: 100, + decodeStrings: false + }); + + tw._write = function(chunk, encoding, cb) { + t.ok(typeof chunk === 'string'); + chunk = new Buffer(chunk, encoding); + return TestWriter.prototype._write.call(this, chunk, encoding, cb); + }; + + var encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'binary', + 'base64', + undefined ]; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got the expected chunks'); + }); + + chunks.forEach(function(chunk, i) { + var enc = encodings[ i % encodings.length ]; + chunk = new Buffer(chunk); + tw.write(chunk.toString(enc), enc); + }); + t.end(); +}); + +test('write callbacks', function (t) { + var callbacks = chunks.map(function(chunk, i) { + return [i, function(er) { + callbacks._called[i] = chunk; + }]; + }).reduce(function(set, x) { + set['callback-' + x[0]] = x[1]; + return set; + }, {}); + callbacks._called = []; + + var tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', function() { + timers.setImmediate(function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.same(callbacks._called, chunks, 'called all callbacks'); + t.end(); + }); + }); + + chunks.forEach(function(chunk, i) { + tw.write(chunk, callbacks['callback-' + i]); + }); + tw.end(); +}); + +test('end callback', function (t) { + var tw = new TestWriter(); + tw.end(function () { + t.end(); + }); +}); + +test('end callback with chunk', function (t) { + var tw = new TestWriter(); + tw.end(new Buffer('hello world'), function () { + t.end(); + }); +}); + +test('end callback with chunk and encoding', function (t) { + var tw = new TestWriter(); + tw.end('hello world', 'ascii', function () { + t.end(); + }); +}); + +test('end callback after .write() call', function (t) { + var tw = new TestWriter(); + tw.write(new Buffer('hello world')); + tw.end(function () { + t.end(); + }); +}); + +test('end callback called after write callback', function (t) { + var tw = new TestWriter(); + var writeCalledback = false; + tw.write(new Buffer('hello world'), function() { + writeCalledback = true; + }); + tw.end(function () { + t.equal(writeCalledback, true); + t.end(); + }); +}); + +test('encoding should be ignored for buffers', function(t) { + var tw = new W(); + var hex = '018b5e9a8f6236ffe30e31baf80d2cf6eb'; + tw._write = function(chunk, encoding, cb) { + t.equal(chunk.toString('hex'), hex); + t.end(); + }; + var buf = new Buffer(hex, 'hex'); + tw.write(buf, 'binary'); +}); + +test('writables are not pipable', function(t) { + var w = new W(); + w._write = function() {}; + var gotError = false; + w.on('error', function(er) { + gotError = true; + }); + w.pipe(stdout); + t.ok(gotError); + t.end(); +}); + +test('duplexes are pipable', function(t) { + var d = new D(); + d._read = function() {}; + d._write = function() {}; + var gotError = false; + d.on('error', function(er) { + gotError = true; + }); + d.pipe(stdout); + t.ok(!gotError); + t.end(); +}); + +test('end(chunk) two times is an error', function(t) { + var w = new W(); + w._write = function() {}; + var gotError = false; + w.on('error', function(er) { + gotError = true; + t.equal(er.message, 'write after end'); + }); + w.end('this is the end'); + w.end('and so is this'); + timers.setImmediate(function() { + t.ok(gotError); + t.end(); + }); +}); + +test('dont end while writing', function(t) { + var w = new W(); + var wrote = false; + w._write = function(chunk, e, cb) { + t.ok(!this.writing); + wrote = true; + this.writing = true; + setTimeout(function() { + this.writing = false; + cb(); + }); + }; + w.on('finish', function() { + t.ok(wrote); + t.end(); + }); + w.write(Buffer(0)); + w.end(); +}); + +test('finish does not come before write cb', function(t) { + var w = new W(); + var writeCb = false; + w._write = function(chunk, e, cb) { + setTimeout(function() { + writeCb = true; + cb(); + }, 10); + }; + w.on('finish', function() { + t.ok(writeCb); + t.end(); + }); + w.write(Buffer(0)); + w.end(); +}); From 7f2e3a5ca50d9f5e890ab2362ab941721dbd5fe8 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 19:33:17 +0200 Subject: [PATCH 37/47] [test] fix race condition in IE10 --- test/browser/stream-pipe-after-end.js | 9 ++------- test/browser/stream-readable-event.js | 11 ++--------- test/browser/stream2-push.js | 16 ++++++++-------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/test/browser/stream-pipe-after-end.js b/test/browser/stream-pipe-after-end.js index 3819cc9..f42969b 100644 --- a/test/browser/stream-pipe-after-end.js +++ b/test/browser/stream-pipe-after-end.js @@ -46,6 +46,7 @@ test('stream - pipe after end', function (t) { setTimeout(function() { ender.on('end', function() { enderEnded = true; + if (enderEnded && writableFinished) t.end(); }); t.ok(!enderEnded); var c = ender.read(); @@ -55,14 +56,8 @@ test('stream - pipe after end', function (t) { var writableFinished = false; w.on('finish', function() { writableFinished = true; - done(); + if (enderEnded && writableFinished) t.end(); }); piper.pipe(w); - - function done() { - t.ok(enderEnded); - t.ok(writableFinished); - t.end(); - } }); }); diff --git a/test/browser/stream-readable-event.js b/test/browser/stream-readable-event.js index 7cc4f02..09538fe 100644 --- a/test/browser/stream-readable-event.js +++ b/test/browser/stream-readable-event.js @@ -49,6 +49,7 @@ test('stream.Readable - readable event - second', function (t) { var _readCalled = false; r._read = function(n) { _readCalled = true; + if (_readCalled && caughtReadable) t.end(); }; // This triggers a 'readable' event, which is lost. @@ -60,17 +61,9 @@ test('stream.Readable - readable event - second', function (t) { t.ok(r._readableState.reading); r.on('readable', function() { caughtReadable = true; - done(); + if (_readCalled && caughtReadable) t.end(); }); }); - - function done() { - // we're testing what we think we are - t.ok(_readCalled); - - t.ok(caughtReadable); - t.end(); - } }); test('stream.Readable - readable event - third', function (t) { diff --git a/test/browser/stream2-push.js b/test/browser/stream2-push.js index 139c777..b462a6c 100644 --- a/test/browser/stream2-push.js +++ b/test/browser/stream2-push.js @@ -81,17 +81,17 @@ test('stream2 - push', function (t) { readStart(); data(); function data() { - t.ok(reading); + t.ok(reading, 'reading 1'); source.emit('data', chunk); - t.ok(reading); + t.ok(reading, 'reading 2'); source.emit('data', chunk); - t.ok(reading); + t.ok(reading, 'reading 3'); source.emit('data', chunk); - t.ok(reading); + t.ok(reading, 'reading 4'); source.emit('data', chunk); - t.ok(!reading); + t.ok(!reading, 'not reading 5'); if (set++ < 5) - setTimeout(data, 10); + setTimeout(data, 100); else end(); } @@ -102,10 +102,10 @@ test('stream2 - push', function (t) { function end() { source.emit('end'); - t.ok(!reading); + t.ok(!reading, 'not reading end'); writer.end(stream.read()); setTimeout(function() { - t.ok(ended); + t.ok(ended, 'end emitted'); t.end(); }); } From 2dbcbb63f0a7bc30ece42e205904c80021bd59fe Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 20:05:37 +0200 Subject: [PATCH 38/47] [fix] add IE8 support for stream.js --- builtin/_shims.js | 12 ++++++++++++ builtin/_stream_duplex.js | 5 +++-- builtin/_stream_readable.js | 9 +++++---- test/browser/stream-push-strings.js | 9 +++++---- test/browser/stream2-basic.js | 9 +++++---- test/browser/stream2-large-read-stall.js | 2 +- test/browser/stream2-objects.js | 7 ++++--- .../stream2-readable-empty-buffer-no-eof.js | 7 ++++--- test/browser/stream2-set-encoding.js | 5 +++-- test/browser/stream2-transform.js | 11 ++++++----- test/browser/stream2-writable.js | 17 +++++++++-------- 11 files changed, 57 insertions(+), 36 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index a54b3d3..bf4d853 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -97,6 +97,18 @@ exports.trim = function (str) { return str.replace(/^\s+|\s+$/g, ''); }; +// Function.prototype.bind is supported in IE9 +exports.bind = function (fn) { + if (fn.bind) return fn.bind.apply(fn, arguments); + + var args = Array.prototype.slice.call(arguments); + var fn = args.shift(); + var self = args.shift(); + return function () { + fn.apply(self, args.concat([Array.prototype.slice.call(arguments)])); + }; +}; + // Object.create is supported in IE9 function create(prototype, properties) { var object; diff --git a/builtin/_stream_duplex.js b/builtin/_stream_duplex.js index 079205e..31a818e 100644 --- a/builtin/_stream_duplex.js +++ b/builtin/_stream_duplex.js @@ -6,13 +6,14 @@ module.exports = Duplex; var util = require('util'); +var shims = require('./_shims'); var timers = require('timers'); var Readable = require('./_stream_readable.js'); var Writable = require('./_stream_writable.js'); util.inherits(Duplex, Readable); -Object.keys(Writable.prototype).forEach(function(method) { +shims.forEach(shims.keys(Writable.prototype), function(method) { if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; }); @@ -46,5 +47,5 @@ function onend() { // no more data can be written. // But allow more writes to happen in this tick. - timers.setImmediate(this.end.bind(this)); + timers.setImmediate(shims.bind(this.end, this)); } diff --git a/builtin/_stream_readable.js b/builtin/_stream_readable.js index 13dcb29..af9efea 100644 --- a/builtin/_stream_readable.js +++ b/builtin/_stream_readable.js @@ -4,6 +4,7 @@ Readable.ReadableState = ReadableState; var EE = require('events').EventEmitter; var Stream = require('stream'); +var shims = require('./_shims.js'); var Buffer = require('buffer').Buffer; var timers = require('timers'); var util = require('util'); @@ -566,7 +567,7 @@ function flow(src) { if (state.pipesCount === 1) write(state.pipes, 0, null); else - state.pipes.forEach(write); + shims.forEach(state.pipes, write); src.emit('data', chunk); @@ -644,7 +645,7 @@ Readable.prototype.unpipe = function(dest) { } // try to find the right one. - var i = state.pipes.indexOf(dest); + var i = shims.indexOf(state.pipes, dest); if (i === -1) return this; @@ -790,8 +791,8 @@ Readable.prototype.wrap = function(stream) { // proxy certain important events. var events = ['error', 'close', 'destroy', 'pause', 'resume']; - events.forEach(function(ev) { - stream.on(ev, self.emit.bind(self, ev)); + shims.forEach(events, function(ev) { + stream.on(ev, shims.bind(self.emit, self, ev)); }); // when we try to consume some more bytes, simply unpause the diff --git a/test/browser/stream-push-strings.js b/test/browser/stream-push-strings.js index c1ec807..42ad41f 100644 --- a/test/browser/stream-push-strings.js +++ b/test/browser/stream-push-strings.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var Readable = require('stream').Readable; var timers = require('timers'); @@ -17,15 +18,15 @@ test('stream - push strings', function (t) { case 0: return this.push(null); case 1: - return setTimeout(function() { + return setTimeout(shims.bind(function() { this.push('last chunk'); - }.bind(this), 100); + }, this), 100); case 2: return this.push('second to last chunk'); case 3: - return timers.setImmediate(function() { + return timers.setImmediate(shims.bind(function() { this.push('first chunk'); - }.bind(this)); + }, this)); default: throw new Error('?'); } diff --git a/test/browser/stream2-basic.js b/test/browser/stream2-basic.js index 753eeb1..06b174a 100644 --- a/test/browser/stream2-basic.js +++ b/test/browser/stream2-basic.js @@ -1,4 +1,5 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var Buffer = require('buffer').Buffer; var R = require('stream').Readable; @@ -26,7 +27,7 @@ TestReader.prototype.read = function(n) { if (toRead === 0) { // simulate the read buffer filling up with some more bytes some time // in the future. - setTimeout(function() { + setTimeout(shims.bind(function() { this._pos = 0; this._bufs -= 1; if (this._bufs <= 0) { @@ -38,7 +39,7 @@ TestReader.prototype.read = function(n) { } else { this.emit('readable'); } - }.bind(this), 10); + }, this), 10); return null; } @@ -137,7 +138,7 @@ test('pipe', function(t) { -[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { +shims.forEach([1,2,3,4,5,6,7,8,9], function(SPLIT) { test('unpipe', function(t) { var r = new TestReader(5); @@ -223,7 +224,7 @@ test('multipipe', function(t) { }); -[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { +shims.forEach([1,2,3,4,5,6,7,8,9], function(SPLIT) { test('multi-unpipe', function(t) { var r = new TestReader(5); diff --git a/test/browser/stream2-large-read-stall.js b/test/browser/stream2-large-read-stall.js index bbba193..2b43403 100644 --- a/test/browser/stream2-large-read-stall.js +++ b/test/browser/stream2-large-read-stall.js @@ -1,6 +1,7 @@ var test = require('tape'); var Buffer = require('buffer').Buffer; +var Readable = require('stream').Readable; test('stream2 - large read stall', function (t) { // If everything aligns so that you do a read(n) of exactly the @@ -11,7 +12,6 @@ test('stream2 - large read stall', function (t) { var PUSHCOUNT = 1000; var HWM = 50; - var Readable = require('stream').Readable; var r = new Readable({ highWaterMark: HWM }); diff --git a/test/browser/stream2-objects.js b/test/browser/stream2-objects.js index 91730c3..038ef99 100644 --- a/test/browser/stream2-objects.js +++ b/test/browser/stream2-objects.js @@ -1,6 +1,7 @@ var Readable = require('stream').Readable; var Writable = require('stream').Writable; +var shims = require('../../builtin/_shims.js'); var test = require('tape'); var timers = require('timers'); @@ -21,7 +22,7 @@ function toArray(callback) { function fromArray(list) { var r = new Readable({ objectMode: true }); r._read = noop; - list.forEach(function(chunk) { + shims.forEach(list, function(chunk) { r.push(chunk); }); r.push(null); @@ -112,7 +113,7 @@ test('can read strings as objects', function(t) { }); r._read = noop; var list = ['one', 'two', 'three']; - list.forEach(function(str) { + shims.forEach(list, function(str) { r.push(str); }); r.push(null); @@ -172,7 +173,7 @@ test('high watermark _read', function(t) { calls++; }; - list.forEach(function(c) { + shims.forEach(list, function(c) { r.push(c); }); diff --git a/test/browser/stream2-readable-empty-buffer-no-eof.js b/test/browser/stream2-readable-empty-buffer-no-eof.js index 41239bf..b89b3a4 100644 --- a/test/browser/stream2-readable-empty-buffer-no-eof.js +++ b/test/browser/stream2-readable-empty-buffer-no-eof.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var timers = require('timers'); var Buffer = require('buffer').Buffer; var Readable = require('stream').Readable; @@ -27,15 +28,15 @@ test('steam2 - readable empty buffer to eof - 1', function (t) { case 1: return r.push(buf); case 2: - setTimeout(r.read.bind(r, 0), 10); + setTimeout(shims.bind(r.read, r, 0), 10); return r.push(new Buffer(0)); // Not-EOF! case 3: - setTimeout(r.read.bind(r, 0), 10); + setTimeout(shims.bind(r.read, r, 0), 10); return timers.setImmediate(function() { return r.push(new Buffer(0)); }); case 4: - setTimeout(r.read.bind(r, 0), 10); + setTimeout(shims.bind(r.read, r, 0), 10); return setTimeout(function() { return r.push(new Buffer(0)); }); diff --git a/test/browser/stream2-set-encoding.js b/test/browser/stream2-set-encoding.js index 1620f05..ceb0542 100644 --- a/test/browser/stream2-set-encoding.js +++ b/test/browser/stream2-set-encoding.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var Buffer = require('buffer').Buffer; var R = require('stream').Readable; var util = require('util'); @@ -14,7 +15,7 @@ function TestReader(n, opts) { } TestReader.prototype._read = function(n) { - setTimeout(function() { + setTimeout(shims.bind(function() { if (this.pos >= this.len) { // double push(null) to test eos handling @@ -34,7 +35,7 @@ TestReader.prototype._read = function(n) { ret.fill('a'); return this.push(ret); - }.bind(this), 1); + }, this), 1); }; test('setEncoding utf8', function(t) { diff --git a/test/browser/stream2-transform.js b/test/browser/stream2-transform.js index 01dcc9b..6e43a7e 100644 --- a/test/browser/stream2-transform.js +++ b/test/browser/stream2-transform.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var Buffer = require('buffer').Buffer; var PassThrough = require('stream').PassThrough; var Transform = require('stream').Transform; @@ -25,7 +26,7 @@ test('writable side consumption', function(t) { t.equal(tx._readableState.length, 10); t.equal(transformed, 10); t.equal(tx._transformState.writechunk.length, 5); - t.same(tx._writableState.buffer.map(function(c) { + t.same(shims.map(tx._writableState.buffer, function(c) { return c.chunk.length; }), [6, 7, 8, 9, 10]); @@ -137,14 +138,14 @@ test('assymetric transform (compress)', function(t) { if (!chunk) chunk = ''; var s = chunk.toString(); - setTimeout(function() { + setTimeout(shims.bind(function() { this.state += s.charAt(0); if (this.state.length === 3) { pt.push(new Buffer(this.state)); this.state = ''; } cb(); - }.bind(this), 10); + }, this), 10); }; pt._flush = function(cb) { @@ -360,7 +361,7 @@ test('object transform (json parse)', function(t) { ended = true; }); - objects.forEach(function(obj) { + shims.forEach(objects, function(obj) { jp.write(JSON.stringify(obj)); var res = jp.read(); t.same(res, obj); @@ -400,7 +401,7 @@ test('object transform (json stringify)', function(t) { ended = true; }); - objects.forEach(function(obj) { + shims.forEach(objects, function(obj) { js.write(obj); var res = js.read(); t.equal(res, JSON.stringify(obj)); diff --git a/test/browser/stream2-writable.js b/test/browser/stream2-writable.js index a2e4542..5a0ad30 100644 --- a/test/browser/stream2-writable.js +++ b/test/browser/stream2-writable.js @@ -1,5 +1,6 @@ var test = require('tape'); +var shims = require('../../builtin/_shims.js'); var R = require('stream').Readable; var W = require('stream').Writable; var D = require('stream').Duplex; @@ -19,11 +20,11 @@ function TestWriter() { TestWriter.prototype._write = function(chunk, encoding, cb) { // simulate a small unpredictable latency - setTimeout(function() { + setTimeout(shims.bind(function() { this.buffer.push(chunk.toString()); this.written += chunk.length; cb(); - }.bind(this), Math.floor(Math.random() * 10)); + }, this), Math.floor(Math.random() * 10)); }; var chunks = new Array(50); @@ -41,7 +42,7 @@ test('write fast', function(t) { t.end(); }); - chunks.forEach(function(chunk) { + shims.forEach(chunks, function(chunk) { // screw backpressure. Just buffer it all up. tw.write(chunk); }); @@ -118,7 +119,7 @@ test('write bufferize', function(t) { t.same(tw.buffer, chunks, 'got the expected chunks'); }); - chunks.forEach(function(chunk, i) { + shims.forEach(chunks, function(chunk, i) { var enc = encodings[ i % encodings.length ]; chunk = new Buffer(chunk); tw.write(chunk.toString(enc), enc); @@ -151,7 +152,7 @@ test('write no bufferize', function(t) { t.same(tw.buffer, chunks, 'got the expected chunks'); }); - chunks.forEach(function(chunk, i) { + shims.forEach(chunks, function(chunk, i) { var enc = encodings[ i % encodings.length ]; chunk = new Buffer(chunk); tw.write(chunk.toString(enc), enc); @@ -160,11 +161,11 @@ test('write no bufferize', function(t) { }); test('write callbacks', function (t) { - var callbacks = chunks.map(function(chunk, i) { + var callbacks = shims.reduce(shims.map(chunks, function(chunk, i) { return [i, function(er) { callbacks._called[i] = chunk; }]; - }).reduce(function(set, x) { + }), function(set, x) { set['callback-' + x[0]] = x[1]; return set; }, {}); @@ -182,7 +183,7 @@ test('write callbacks', function (t) { }); }); - chunks.forEach(function(chunk, i) { + shims.forEach(chunks, function(chunk, i) { tw.write(chunk, callbacks['callback-' + i]); }); tw.end(); From c8cf09f7e7edd9f89054cfc20dcd855b6437ef33 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 20:43:39 +0200 Subject: [PATCH 39/47] [license] the files there is from nodecore --- builtin/_stream_duplex.js | 20 ++++++++++++++++++ builtin/_stream_passthrough.js | 20 ++++++++++++++++++ builtin/_stream_readable.js | 20 ++++++++++++++++++ builtin/_stream_transform.js | 21 ++++++++++++++++++- builtin/_stream_writable.js | 20 ++++++++++++++++++ builtin/assert.js | 21 +++++++++++++++++++ builtin/events.js | 20 ++++++++++++++++++ builtin/path.js | 20 ++++++++++++++++++ builtin/querystring.js | 21 +++++++++++++++++++ builtin/stream.js | 21 +++++++++++++++++++ builtin/string_decoder.js | 20 ++++++++++++++++++ builtin/url.js | 20 ++++++++++++++++++ builtin/util.js | 20 ++++++++++++++++++ test/browser/assert-simple.js | 21 ++++++++++++++++++- test/browser/events-listeners.js | 20 ++++++++++++++++++ test/browser/events-remove-listeners.js | 20 ++++++++++++++++++ test/browser/path-simple.js | 20 ++++++++++++++++++ test/browser/querystring-simple.js | 20 ++++++++++++++++++ test/browser/stream-big-push.js | 20 ++++++++++++++++++ test/browser/stream-pipe-after-end.js | 20 ++++++++++++++++++ test/browser/stream-pipe-cleanup.js | 20 ++++++++++++++++++ test/browser/stream-pipe-error-handling.js | 20 ++++++++++++++++++ test/browser/stream-pipe-event.js | 20 ++++++++++++++++++ test/browser/stream-push-order.js | 20 ++++++++++++++++++ test/browser/stream-push-strings.js | 20 ++++++++++++++++++ test/browser/stream-readable-event.js | 20 ++++++++++++++++++ .../browser/stream-readable-flow-recursion.js | 20 ++++++++++++++++++ test/browser/stream-unshift-empty-chunk.js | 20 ++++++++++++++++++ test/browser/stream-unshift-read-race.js | 20 ++++++++++++++++++ test/browser/stream2-basic.js | 21 +++++++++++++++++++ test/browser/stream2-compatibility.js | 20 ++++++++++++++++++ test/browser/stream2-finish-pipe.js | 20 ++++++++++++++++++ test/browser/stream2-large-read-stall.js | 20 ++++++++++++++++++ test/browser/stream2-objects.js | 20 ++++++++++++++++++ test/browser/stream2-pipe-error-handling.js | 20 ++++++++++++++++++ test/browser/stream2-push.js | 20 ++++++++++++++++++ test/browser/stream2-read-sync-stack.js | 20 ++++++++++++++++++ .../stream2-readable-empty-buffer-no-eof.js | 20 ++++++++++++++++++ test/browser/stream2-readable-from-list.js | 20 ++++++++++++++++++ test/browser/stream2-readable-legacy-drain.js | 20 ++++++++++++++++++ .../browser/stream2-readable-non-empty-end.js | 20 ++++++++++++++++++ test/browser/stream2-readable-wrap-empty.js | 20 ++++++++++++++++++ test/browser/stream2-set-encoding.js | 20 ++++++++++++++++++ test/browser/stream2-transform.js | 20 ++++++++++++++++++ test/browser/stream2-unpipe-drain.js | 20 ++++++++++++++++++ test/browser/stream2-unpipe-leak.js | 20 ++++++++++++++++++ test/browser/stream2-writable.js | 20 ++++++++++++++++++ test/browser/string-decoder-simple.js | 20 ++++++++++++++++++ test/browser/url-simple.js | 20 ++++++++++++++++++ test/browser/util-inspect.js | 20 ++++++++++++++++++ test/browser/util-is.js | 20 ++++++++++++++++++ 51 files changed, 1024 insertions(+), 2 deletions(-) diff --git a/builtin/_stream_duplex.js b/builtin/_stream_duplex.js index 31a818e..2273b0a 100644 --- a/builtin/_stream_duplex.js +++ b/builtin/_stream_duplex.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. // a duplex stream is just a stream that is both readable and writable. // Since JS doesn't have multiple prototypal inheritance, this class diff --git a/builtin/_stream_passthrough.js b/builtin/_stream_passthrough.js index 67fc221..80c9d0d 100644 --- a/builtin/_stream_passthrough.js +++ b/builtin/_stream_passthrough.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. // a passthrough stream. // basically just the most minimal sort of Transform stream. diff --git a/builtin/_stream_readable.js b/builtin/_stream_readable.js index af9efea..f240261 100644 --- a/builtin/_stream_readable.js +++ b/builtin/_stream_readable.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. module.exports = Readable; Readable.ReadableState = ReadableState; diff --git a/builtin/_stream_transform.js b/builtin/_stream_transform.js index 5d4313d..299cd65 100644 --- a/builtin/_stream_transform.js +++ b/builtin/_stream_transform.js @@ -1,4 +1,23 @@ - +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. // a transform stream is a readable/writable stream where you do // something with the data. Sometimes it's called a "filter", diff --git a/builtin/_stream_writable.js b/builtin/_stream_writable.js index dd42d6c..039e558 100644 --- a/builtin/_stream_writable.js +++ b/builtin/_stream_writable.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. // A bit simpler than readable streams. // Implement an async ._write(chunk, cb), and it'll handle all diff --git a/builtin/assert.js b/builtin/assert.js index f546b0e..1df1e0a 100644 --- a/builtin/assert.js +++ b/builtin/assert.js @@ -1,3 +1,24 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + // UTILITY var util = require('util'); var shims = require('./_shims.js'); diff --git a/builtin/events.js b/builtin/events.js index d341913..6dbdde2 100644 --- a/builtin/events.js +++ b/builtin/events.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var util = require('util'); diff --git a/builtin/path.js b/builtin/path.js index a4b6538..66f2014 100644 --- a/builtin/path.js +++ b/builtin/path.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var util = require('util'); var shims = require('./_shims.js'); diff --git a/builtin/querystring.js b/builtin/querystring.js index 6297c9c..2db64a8 100644 --- a/builtin/querystring.js +++ b/builtin/querystring.js @@ -1,3 +1,24 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + // Query String Utilities var QueryString = exports; diff --git a/builtin/stream.js b/builtin/stream.js index 9c4d79c..be66a3a 100644 --- a/builtin/stream.js +++ b/builtin/stream.js @@ -1,3 +1,24 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + module.exports = Stream; var EE = require('events').EventEmitter; diff --git a/builtin/string_decoder.js b/builtin/string_decoder.js index 0c27320..80edb05 100644 --- a/builtin/string_decoder.js +++ b/builtin/string_decoder.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var Buffer = require('buffer').Buffer; diff --git a/builtin/url.js b/builtin/url.js index e17634f..b02b748 100644 --- a/builtin/url.js +++ b/builtin/url.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var punycode = { encode : function (s) { return s } }; var util = require('util'); diff --git a/builtin/util.js b/builtin/util.js index 5dbd6a0..431fe73 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var shims = require('./_shims.js'); diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js index da1d6b9..c5725a2 100644 --- a/test/browser/assert-simple.js +++ b/test/browser/assert-simple.js @@ -1,4 +1,23 @@ - +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/events-listeners.js b/test/browser/events-listeners.js index 82f14dd..dc9fe19 100644 --- a/test/browser/events-listeners.js +++ b/test/browser/events-listeners.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/events-remove-listeners.js b/test/browser/events-remove-listeners.js index 7543529..9a60e40 100644 --- a/test/browser/events-remove-listeners.js +++ b/test/browser/events-remove-listeners.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/path-simple.js b/test/browser/path-simple.js index ccf42ca..76b10dc 100644 --- a/test/browser/path-simple.js +++ b/test/browser/path-simple.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/querystring-simple.js b/test/browser/querystring-simple.js index 2559240..bd8aae0 100644 --- a/test/browser/querystring-simple.js +++ b/test/browser/querystring-simple.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/stream-big-push.js b/test/browser/stream-big-push.js index 206b704..f04f551 100644 --- a/test/browser/stream-big-push.js +++ b/test/browser/stream-big-push.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/stream-pipe-after-end.js b/test/browser/stream-pipe-after-end.js index f42969b..a45b752 100644 --- a/test/browser/stream-pipe-after-end.js +++ b/test/browser/stream-pipe-after-end.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/stream-pipe-cleanup.js b/test/browser/stream-pipe-cleanup.js index a061e3a..349f31c 100644 --- a/test/browser/stream-pipe-cleanup.js +++ b/test/browser/stream-pipe-cleanup.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var stream = require('stream'); diff --git a/test/browser/stream-pipe-error-handling.js b/test/browser/stream-pipe-error-handling.js index ca1f4c3..c8723f6 100644 --- a/test/browser/stream-pipe-error-handling.js +++ b/test/browser/stream-pipe-error-handling.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Stream = require('stream').Stream; diff --git a/test/browser/stream-pipe-event.js b/test/browser/stream-pipe-event.js index 11e77df..0e9e438 100644 --- a/test/browser/stream-pipe-event.js +++ b/test/browser/stream-pipe-event.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var stream = require('stream'); var test = require('tape'); diff --git a/test/browser/stream-push-order.js b/test/browser/stream-push-order.js index 847a5d2..fc2ca64 100644 --- a/test/browser/stream-push-order.js +++ b/test/browser/stream-push-order.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var Readable = require('stream').Readable; var test = require('tape'); diff --git a/test/browser/stream-push-strings.js b/test/browser/stream-push-strings.js index 42ad41f..c811d2e 100644 --- a/test/browser/stream-push-strings.js +++ b/test/browser/stream-push-strings.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/stream-readable-event.js b/test/browser/stream-readable-event.js index 09538fe..bd68bdb 100644 --- a/test/browser/stream-readable-event.js +++ b/test/browser/stream-readable-event.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream-readable-flow-recursion.js b/test/browser/stream-readable-flow-recursion.js index 3d6cb4f..566768b 100644 --- a/test/browser/stream-readable-flow-recursion.js +++ b/test/browser/stream-readable-flow-recursion.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Readable = require('stream').Readable; diff --git a/test/browser/stream-unshift-empty-chunk.js b/test/browser/stream-unshift-empty-chunk.js index 31f8f26..99e92ed 100644 --- a/test/browser/stream-unshift-empty-chunk.js +++ b/test/browser/stream-unshift-empty-chunk.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream-unshift-read-race.js b/test/browser/stream-unshift-read-race.js index 8246828..761a5fd 100644 --- a/test/browser/stream-unshift-read-race.js +++ b/test/browser/stream-unshift-read-race.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-basic.js b/test/browser/stream2-basic.js index 06b174a..e4d0127 100644 --- a/test/browser/stream2-basic.js +++ b/test/browser/stream2-basic.js @@ -1,3 +1,24 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + var test = require('tape'); var shims = require('../../builtin/_shims.js'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-compatibility.js b/test/browser/stream2-compatibility.js index 360baa0..1757f63 100644 --- a/test/browser/stream2-compatibility.js +++ b/test/browser/stream2-compatibility.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var R = require('stream').Readable; var test = require('tape'); diff --git a/test/browser/stream2-finish-pipe.js b/test/browser/stream2-finish-pipe.js index 78942b0..6564207 100644 --- a/test/browser/stream2-finish-pipe.js +++ b/test/browser/stream2-finish-pipe.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var stream = require('stream'); diff --git a/test/browser/stream2-large-read-stall.js b/test/browser/stream2-large-read-stall.js index 2b43403..df22032 100644 --- a/test/browser/stream2-large-read-stall.js +++ b/test/browser/stream2-large-read-stall.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-objects.js b/test/browser/stream2-objects.js index 038ef99..9efe15d 100644 --- a/test/browser/stream2-objects.js +++ b/test/browser/stream2-objects.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var Readable = require('stream').Readable; var Writable = require('stream').Writable; diff --git a/test/browser/stream2-pipe-error-handling.js b/test/browser/stream2-pipe-error-handling.js index 7da84da..fc0f1e8 100644 --- a/test/browser/stream2-pipe-error-handling.js +++ b/test/browser/stream2-pipe-error-handling.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var stream = require('stream'); diff --git a/test/browser/stream2-push.js b/test/browser/stream2-push.js index b462a6c..2e85eb7 100644 --- a/test/browser/stream2-push.js +++ b/test/browser/stream2-push.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var stream = require('stream'); var Readable = stream.Readable; diff --git a/test/browser/stream2-read-sync-stack.js b/test/browser/stream2-read-sync-stack.js index a1d41b0..2a53c1c 100644 --- a/test/browser/stream2-read-sync-stack.js +++ b/test/browser/stream2-read-sync-stack.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-readable-empty-buffer-no-eof.js b/test/browser/stream2-readable-empty-buffer-no-eof.js index b89b3a4..4b89bb7 100644 --- a/test/browser/stream2-readable-empty-buffer-no-eof.js +++ b/test/browser/stream2-readable-empty-buffer-no-eof.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/stream2-readable-from-list.js b/test/browser/stream2-readable-from-list.js index 80f2aaa..7a00536 100644 --- a/test/browser/stream2-readable-from-list.js +++ b/test/browser/stream2-readable-from-list.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-readable-legacy-drain.js b/test/browser/stream2-readable-legacy-drain.js index 07bd551..e82a72c 100644 --- a/test/browser/stream2-readable-legacy-drain.js +++ b/test/browser/stream2-readable-legacy-drain.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-readable-non-empty-end.js b/test/browser/stream2-readable-non-empty-end.js index ff0fe98..8f2040d 100644 --- a/test/browser/stream2-readable-non-empty-end.js +++ b/test/browser/stream2-readable-non-empty-end.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var Buffer = require('buffer').Buffer; diff --git a/test/browser/stream2-readable-wrap-empty.js b/test/browser/stream2-readable-wrap-empty.js index 8a065b6..4226438 100644 --- a/test/browser/stream2-readable-wrap-empty.js +++ b/test/browser/stream2-readable-wrap-empty.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/stream2-set-encoding.js b/test/browser/stream2-set-encoding.js index ceb0542..0f12898 100644 --- a/test/browser/stream2-set-encoding.js +++ b/test/browser/stream2-set-encoding.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/stream2-transform.js b/test/browser/stream2-transform.js index 6e43a7e..e1012f3 100644 --- a/test/browser/stream2-transform.js +++ b/test/browser/stream2-transform.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/stream2-unpipe-drain.js b/test/browser/stream2-unpipe-drain.js index b909b8d..5d5c24f 100644 --- a/test/browser/stream2-unpipe-drain.js +++ b/test/browser/stream2-unpipe-drain.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var stream = require('stream'); diff --git a/test/browser/stream2-unpipe-leak.js b/test/browser/stream2-unpipe-leak.js index 0c036d1..3d1cb39 100644 --- a/test/browser/stream2-unpipe-leak.js +++ b/test/browser/stream2-unpipe-leak.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var stream = require('stream'); diff --git a/test/browser/stream2-writable.js b/test/browser/stream2-writable.js index 5a0ad30..fe9ae00 100644 --- a/test/browser/stream2-writable.js +++ b/test/browser/stream2-writable.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); diff --git a/test/browser/string-decoder-simple.js b/test/browser/string-decoder-simple.js index 0b3756b..d7c535b 100644 --- a/test/browser/string-decoder-simple.js +++ b/test/browser/string-decoder-simple.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/url-simple.js b/test/browser/url-simple.js index 49dc3cd..439b271 100644 --- a/test/browser/url-simple.js +++ b/test/browser/url-simple.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var assert = require('assert'); diff --git a/test/browser/util-inspect.js b/test/browser/util-inspect.js index c063637..bd52760 100644 --- a/test/browser/util-inspect.js +++ b/test/browser/util-inspect.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); diff --git a/test/browser/util-is.js b/test/browser/util-is.js index 5ba4a9d..ac7b8da 100644 --- a/test/browser/util-is.js +++ b/test/browser/util-is.js @@ -1,3 +1,23 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); var shims = require('../../builtin/_shims.js'); From 37a6069e2ac7fa581711e42a3465f726bb06e143 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 20:48:33 +0200 Subject: [PATCH 40/47] [fix] bind shim in browser there supports bind --- builtin/_shims.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin/_shims.js b/builtin/_shims.js index bf4d853..fcbabc9 100644 --- a/builtin/_shims.js +++ b/builtin/_shims.js @@ -98,11 +98,10 @@ exports.trim = function (str) { }; // Function.prototype.bind is supported in IE9 -exports.bind = function (fn) { - if (fn.bind) return fn.bind.apply(fn, arguments); - +exports.bind = function () { var args = Array.prototype.slice.call(arguments); var fn = args.shift(); + if (fn.bind) return fn.bind.apply(fn, args); var self = args.shift(); return function () { fn.apply(self, args.concat([Array.prototype.slice.call(arguments)])); From 274eaa2fd855864f87f6b1d01642e1d11eb97af1 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 20:58:37 +0200 Subject: [PATCH 41/47] [test] fix race conditons and console issues in IE10 --- test/browser/events-remove-listeners.js | 3 --- test/browser/stream-readable-event.js | 7 ++++--- test/browser/stream2-push.js | 2 +- test/browser/stream2-readable-non-empty-end.js | 1 - test/browser/stream2-transform.js | 18 ------------------ 5 files changed, 5 insertions(+), 26 deletions(-) diff --git a/test/browser/events-remove-listeners.js b/test/browser/events-remove-listeners.js index 9a60e40..aebc39b 100644 --- a/test/browser/events-remove-listeners.js +++ b/test/browser/events-remove-listeners.js @@ -26,17 +26,14 @@ var events = require('events'); var count = 0; function listener1() { - console.log('listener1'); count++; } function listener2() { - console.log('listener2'); count++; } function listener3() { - console.log('listener3'); count++; } diff --git a/test/browser/stream-readable-event.js b/test/browser/stream-readable-event.js index bd68bdb..97ea7e5 100644 --- a/test/browser/stream-readable-event.js +++ b/test/browser/stream-readable-event.js @@ -20,6 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. var test = require('tape'); +var timers = require('timers'); var Buffer = require('buffer').Buffer; var Readable = require('stream').Readable; @@ -40,7 +41,7 @@ test('stream.Readable - readable event - first', function (t) { r.push(new Buffer('blerg')); var caughtReadable = false; - setTimeout(function() { + timers.setImmediate(function() { // we're testing what we think we are t.ok(!r._readableState.reading); r.on('readable', function() { @@ -76,7 +77,7 @@ test('stream.Readable - readable event - second', function (t) { r.push(new Buffer('bl')); var caughtReadable = false; - setTimeout(function() { + timers.setImmediate(function() { // assert we're testing what we think we are t.ok(r._readableState.reading); r.on('readable', function() { @@ -103,7 +104,7 @@ test('stream.Readable - readable event - third', function (t) { r.push(null); var caughtReadable = false; - setTimeout(function() { + timers.setImmediate(function() { // assert we're testing what we think we are t.ok(!r._readableState.reading); r.on('readable', function() { diff --git a/test/browser/stream2-push.js b/test/browser/stream2-push.js index 2e85eb7..788fcd3 100644 --- a/test/browser/stream2-push.js +++ b/test/browser/stream2-push.js @@ -127,6 +127,6 @@ test('stream2 - push', function (t) { setTimeout(function() { t.ok(ended, 'end emitted'); t.end(); - }); + }, 100); } }); \ No newline at end of file diff --git a/test/browser/stream2-readable-non-empty-end.js b/test/browser/stream2-readable-non-empty-end.js index 8f2040d..f6264d7 100644 --- a/test/browser/stream2-readable-non-empty-end.js +++ b/test/browser/stream2-readable-non-empty-end.js @@ -51,7 +51,6 @@ test('stream2 - readable non empty end', function (t) { var res = test.read(b); if (res) { bytesread += res.length; - console.error('br=%d len=%d', bytesread, len); setTimeout(next); } test.read(0); diff --git a/test/browser/stream2-transform.js b/test/browser/stream2-transform.js index e1012f3..2d63913 100644 --- a/test/browser/stream2-transform.js +++ b/test/browser/stream2-transform.js @@ -242,7 +242,6 @@ test('passthrough event emission', function(t) { var emits = 0; pt.on('readable', function() { var state = pt._readableState; - console.error('>>> emit readable %d', emits); emits++; }); @@ -250,30 +249,22 @@ test('passthrough event emission', function(t) { pt.write(new Buffer('foog')); - console.error('need emit 0'); pt.write(new Buffer('bark')); - console.error('should have emitted readable now 1 === %d', emits); t.equal(emits, 1); t.equal(pt.read(5).toString(), 'foogb'); t.equal(pt.read(5) + '', 'null'); - console.error('need emit 1'); - pt.write(new Buffer('bazy')); - console.error('should have emitted, but not again'); pt.write(new Buffer('kuel')); - console.error('should have emitted readable now 2 === %d', emits); t.equal(emits, 2); t.equal(pt.read(5).toString(), 'arkba'); t.equal(pt.read(5).toString(), 'zykue'); t.equal(pt.read(5), null); - console.error('need emit 2'); - pt.end(); t.equal(emits, 3); @@ -281,7 +272,6 @@ test('passthrough event emission', function(t) { t.equal(pt.read(5).toString(), 'l'); t.equal(pt.read(5), null); - console.error('should not have emitted again'); t.equal(emits, 3); t.end(); }); @@ -290,26 +280,21 @@ test('passthrough event emission reordered', function(t) { var pt = new PassThrough; var emits = 0; pt.on('readable', function() { - console.error('emit readable', emits) emits++; }); pt.write(new Buffer('foog')); - console.error('need emit 0'); pt.write(new Buffer('bark')); - console.error('should have emitted readable now 1 === %d', emits); t.equal(emits, 1); t.equal(pt.read(5).toString(), 'foogb'); t.equal(pt.read(5), null); - console.error('need emit 1'); pt.once('readable', function() { t.equal(pt.read(5).toString(), 'arkba'); t.equal(pt.read(5), null); - console.error('need emit 2'); pt.once('readable', function() { t.equal(pt.read(5).toString(), 'zykue'); t.equal(pt.read(5), null); @@ -328,7 +313,6 @@ test('passthrough event emission reordered', function(t) { }); test('passthrough facaded', function(t) { - console.error('passthrough facaded'); var pt = new PassThrough; var datas = []; pt.on('data', function(chunk) { @@ -356,7 +340,6 @@ test('passthrough facaded', function(t) { }); test('object transform (json parse)', function(t) { - console.error('json parse stream'); var jp = new Transform({ objectMode: true }); jp._transform = function(data, encoding, cb) { try { @@ -396,7 +379,6 @@ test('object transform (json parse)', function(t) { }); test('object transform (json stringify)', function(t) { - console.error('json parse stream'); var js = new Transform({ objectMode: true }); js._transform = function(data, encoding, cb) { try { From c1ba82ab8091a6f782d815ec0fc61dc23eecaaa4 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 21:07:34 +0200 Subject: [PATCH 42/47] [fix] don't include internal files in module object --- index.js | 3 ++- test/node/node-test.js | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 871cc53..499c013 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,8 @@ var core = {}; // load core modules from builtin dir fs.readdirSync(path.resolve(__dirname, 'builtin')).forEach(function(file) { - core[path.basename(file, '.js')] = path.resolve(__dirname, 'builtin', file); + if (file[0] === '_') return; + core[path.basename(file, '.js')] = path.resolve(__dirname, 'builtin', file); }); // manually resolve modules that would otherwise resolve as core diff --git a/test/node/node-test.js b/test/node/node-test.js index cee9c12..d2320fe 100644 --- a/test/node/node-test.js +++ b/test/node/node-test.js @@ -7,19 +7,25 @@ test('test that all the modules are set', function (t) { 'assert', 'buffer', 'child_process', + 'cluster', 'console', 'constants', 'crypto', 'dgram', + 'dns', + 'domain', 'events', 'fs', 'http', 'https', 'net', + 'os', 'path', 'process', 'punycode', 'querystring', + 'readline', + 'repl', 'stream', 'string_decoder', 'sys', From 56e941650e382b2053fb26e77e419ced97516b47 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 21:51:58 +0200 Subject: [PATCH 43/47] [test] fix internal require in tests --- test/browser/stream2-readable-non-empty-end.js | 2 +- test/browser/stream2-readable-wrap-empty.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/browser/stream2-readable-non-empty-end.js b/test/browser/stream2-readable-non-empty-end.js index f6264d7..7113ca8 100644 --- a/test/browser/stream2-readable-non-empty-end.js +++ b/test/browser/stream2-readable-non-empty-end.js @@ -21,7 +21,7 @@ var test = require('tape'); var Buffer = require('buffer').Buffer; -var Readable = require('_stream_readable'); +var Readable = require('stream').Readable; test('stream2 - readable non empty end', function (t) { var len = 0; diff --git a/test/browser/stream2-readable-wrap-empty.js b/test/browser/stream2-readable-wrap-empty.js index 4226438..c01cb1b 100644 --- a/test/browser/stream2-readable-wrap-empty.js +++ b/test/browser/stream2-readable-wrap-empty.js @@ -21,7 +21,7 @@ var test = require('tape'); -var Readable = require('_stream_readable'); +var Readable = require('stream').Readable; var EE = require('events').EventEmitter; test('stream2 - readable wrap empty', function (t) { From 13d4ea5e68e519c08a7503ccf07ce5113fe13f62 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 21:53:09 +0200 Subject: [PATCH 44/47] [doc] add browser support, documentation, contribute and history sections --- README.md | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5b6dfac..db83ee6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,114 @@ # Browser altenatives to built-in node.js modules This is used by `browserify` module. Initially these files were in `node-browser-resolve` -but [disappeared in v1.0.1](https://github.com/shtylman/node-browser-resolve/commit/2799bcc316052a53fdafecd39576e14673a47ab0) which broke `browserify` dependency. This module is that missing -dependency. +but [disappeared in v1.0.1](https://github.com/shtylman/node-browser-resolve/commit/2799bcc316052a53fdafecd39576e14673a47ab0) +which broke `browserify` dependency. This module is that missing dependency. -**Would love to get test contributions** +## Browser support -"Forked" from `node-browser-resolve`, originally written by Roman Shtylman (@shtylman). +* Safari: latest +* Chrome: latest +* Firefox: latest +* Opera: latest +* Internet Explore: 8, 9, 10 + +## Documentation + +Requireing this module gives you a simple map between the modulename and a +filepath to the module containing the shim. You can then point to these files +in your pseudo-node implementation. Note that beyond the nodecore modules +there is also a process module, there mimics `global.process` in node. + +```javascript +require('browser-builtins'); +``` + +```javascript +{ + assert: '/user/node_modules/browser-builtins/builtin/assert.js', + child_process: '/user/node_modules/browser-builtins/builtin/child_process.js', + cluster: '/user/node_modules/browser-builtins/builtin/cluster.js', + dgram: '/user/node_modules/browser-builtins/builtin/dgram.js', + dns: '/user/node_modules/browser-builtins/builtin/dns.js', + domain: '/user/node_modules/browser-builtins/builtin/domain.js', + events: '/user/node_modules/browser-builtins/builtin/events.js', + fs: '/user/node_modules/browser-builtins/builtin/fs.js', + https: '/user/node_modules/browser-builtins/builtin/https.js', + net: '/user/node_modules/browser-builtins/builtin/net.js', + path: '/user/node_modules/browser-builtins/builtin/path.js', + process: '/user/node_modules/browser-builtins/builtin/process.js', + querystring: '/user/node_modules/browser-builtins/builtin/querystring.js', + readline: '/user/node_modules/browser-builtins/builtin/readline.js', + repl: '/user/node_modules/browser-builtins/builtin/repl.js', + stream: '/user/node_modules/browser-builtins/builtin/stream.js', + string_decoder: '/user/node_modules/browser-builtins/builtin/string_decoder.js', + sys: '/user/node_modules/browser-builtins/builtin/sys.js', + timers: '/user/node_modules/browser-builtins/builtin/timers.js', + tls: '/user/node_modules/browser-builtins/builtin/tls.js', + tty: '/user/node_modules/browser-builtins/builtin/tty.js', + url: '/user/node_modules/browser-builtins/builtin/url.js', + util: '/user/node_modules/browser-builtins/builtin/util.js', + punycode: '/user/node_modules/browser-builtins/node_modules/punycode/punycode.js', + http: '/user/node_modules/browser-builtins/node_modules/http-browserify/index.js', + vm: '/user/node_modules/browser-builtins/node_modules/vm-browserify/index.js', + crypto: '/user/node_modules/browser-builtins/node_modules/crypto-browserify/index.js', + console: '/user/node_modules/browser-builtins/node_modules/console-browserify/index.js', + zlib: '/user/node_modules/browser-builtins/node_modules/zlib-browserify/index.js', + buffer: '/user/node_modules/browser-builtins/node_modules/buffer-browserify/index.js', + constants: '/user/node_modules/browser-builtins/node_modules/constants-browserify/constants.json', + os: '/user/node_modules/browser-builtins/node_modules/os-browserify/browser.js' +} +``` + +## Contribute + +When you find a bug in this module and want to fix it its a good idea to follow these steps: + +> On a general note, please try to fix all issues either by coping from nodecore +> or by fixing it with a shim in the `_shims` file. + +1. Add a test (preferably in a new file) with the name `{modulename}-{issue}.js` in the `test/browser/` directory. +2. Check if the bug exists in node.js, if true `goto` _bug in nodecore_ +3. Check if the module is outdated, if true `goto` _outdated module_ +4. Check if the issue can be shimed, if true `goto` _fix with shims_ +5. Sorry, as of this date all issues could be fixed with shims, you are on your own + +#### bug in nodecore + +1. Fix it in [nodecore](https://github.com/joyent/node) +2. When merged in nodecore `goto` _outdated module_ + +#### outdated module + +1. Before you copy the module search for `shims` in the outdated module file. +2. Copypast the **entire** module from [nodecore](https://github.com/joyent/node) (checkout a stable branch). +3. Reimplement the `shims` from the outdated module file, `git diff` can help. +4. `goto` _test in browsers_ + +#### fix with shims + +In the `builtin` directory there is a `_shims.js` file this contain all the +currently necessary shims. Most of these are incomple and writen specific +for this module collection. If you find that the current implementation is +insufficient try to improve it. If it lacks a feature just added that shim, +but try to keep the implementation at a minimum. Usually `TypeError` and +similar can be omitted. + +#### test in browsers + +This module is currently not integrated with testling-ci, but you can run testling +locally in order to run the tests. + +1. First install: `npm install -g testling` and `npm install -g browserify` +2. `cd` intro this module directory +3. Run `browserify --transform ./test/browserify-transform.js test/browser/* | testling -u` +4. This gives you a url, go to that url in a modern browser (just not IE, even IE 10). +5. This will run the tests, if there was an issue you should fix it now +6. Run the tests again in IE 10, then IE 9 and at last IE 8 and fix any issues (see: _fix with shims_) +7. Test in the remaining supported browser, there shouldn't be any issues +8. You are done, make the Pull Request :+1: + +## History + +1. "Forked" from `node-browser-resolve`, originally written by Roman Shtylman (@shtylman). +2. Major update to node v0.10 and tests (@AndreasMadsen) From 0a978f0e816cb5b0bc648abc5590305d6d77b58b Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 22:00:36 +0200 Subject: [PATCH 45/47] [bump] version 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db295a0..0227691 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "browser-builtins", - "version": "1.0.7", + "version": "2.0.0", "description": "Builtins that were extracted from node-browser-resolve on which browserify depends", "main": "index.js", "scripts": { From 3dd98ffb8e47d706c0e320d5faab581a2d79362a Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Wed, 21 Aug 2013 22:34:44 +0200 Subject: [PATCH 46/47] [doc] added notes about Buffer and process usage --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index db83ee6..907bd99 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ When you find a bug in this module and want to fix it its a good idea to follow > On a general note, please try to fix all issues either by coping from nodecore > or by fixing it with a shim in the `_shims` file. +> +> Also since this module isn't just for browserify, you should always require +> the `Buffer` module and avoid using the `process` object. 1. Add a test (preferably in a new file) with the name `{modulename}-{issue}.js` in the `test/browser/` directory. 2. Check if the bug exists in node.js, if true `goto` _bug in nodecore_ From bb0969b70a46b7a1d28e5bd72703cdfaee8c2cc7 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Thu, 22 Aug 2013 13:31:47 +0200 Subject: [PATCH 47/47] [refactor] don't use relative paths for internals Relative paths increases the complexity of any require implementation and this is also how node.js works --- builtin/_stream_duplex.js | 6 +++--- builtin/_stream_passthrough.js | 2 +- builtin/_stream_readable.js | 2 +- builtin/_stream_transform.js | 2 +- builtin/assert.js | 2 +- builtin/path.js | 2 +- builtin/querystring.js | 2 +- builtin/stream.js | 10 +++++----- builtin/url.js | 2 +- builtin/util.js | 4 ++-- index.js | 1 - test/node/node-test.js | 6 ++++++ 12 files changed, 23 insertions(+), 18 deletions(-) diff --git a/builtin/_stream_duplex.js b/builtin/_stream_duplex.js index 2273b0a..891320e 100644 --- a/builtin/_stream_duplex.js +++ b/builtin/_stream_duplex.js @@ -26,10 +26,10 @@ module.exports = Duplex; var util = require('util'); -var shims = require('./_shims'); +var shims = require('_shims'); var timers = require('timers'); -var Readable = require('./_stream_readable.js'); -var Writable = require('./_stream_writable.js'); +var Readable = require('_stream_readable'); +var Writable = require('_stream_writable'); util.inherits(Duplex, Readable); diff --git a/builtin/_stream_passthrough.js b/builtin/_stream_passthrough.js index 80c9d0d..a5e9864 100644 --- a/builtin/_stream_passthrough.js +++ b/builtin/_stream_passthrough.js @@ -25,7 +25,7 @@ module.exports = PassThrough; -var Transform = require('./_stream_transform.js'); +var Transform = require('_stream_transform'); var util = require('util'); util.inherits(PassThrough, Transform); diff --git a/builtin/_stream_readable.js b/builtin/_stream_readable.js index f240261..0466ab2 100644 --- a/builtin/_stream_readable.js +++ b/builtin/_stream_readable.js @@ -24,7 +24,7 @@ Readable.ReadableState = ReadableState; var EE = require('events').EventEmitter; var Stream = require('stream'); -var shims = require('./_shims.js'); +var shims = require('_shims'); var Buffer = require('buffer').Buffer; var timers = require('timers'); var util = require('util'); diff --git a/builtin/_stream_transform.js b/builtin/_stream_transform.js index 299cd65..c5b81dc 100644 --- a/builtin/_stream_transform.js +++ b/builtin/_stream_transform.js @@ -63,7 +63,7 @@ module.exports = Transform; -var Duplex = require('./_stream_duplex.js'); +var Duplex = require('_stream_duplex'); var util = require('util'); util.inherits(Transform, Duplex); diff --git a/builtin/assert.js b/builtin/assert.js index 1df1e0a..e2c4056 100644 --- a/builtin/assert.js +++ b/builtin/assert.js @@ -21,7 +21,7 @@ // UTILITY var util = require('util'); -var shims = require('./_shims.js'); +var shims = require('_shims'); var pSlice = Array.prototype.slice; // 1. The assert module provides functions that throw diff --git a/builtin/path.js b/builtin/path.js index 66f2014..d21a977 100644 --- a/builtin/path.js +++ b/builtin/path.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. var util = require('util'); -var shims = require('./_shims.js'); +var shims = require('_shims'); // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array diff --git a/builtin/querystring.js b/builtin/querystring.js index 2db64a8..d42f0a6 100644 --- a/builtin/querystring.js +++ b/builtin/querystring.js @@ -23,7 +23,7 @@ var QueryString = exports; var util = require('util'); -var shims = require('./_shims.js'); +var shims = require('_shims'); var Buffer = require('buffer').Buffer; // If obj.hasOwnProperty has been overridden, then calling diff --git a/builtin/stream.js b/builtin/stream.js index be66a3a..098ff61 100644 --- a/builtin/stream.js +++ b/builtin/stream.js @@ -25,11 +25,11 @@ var EE = require('events').EventEmitter; var util = require('util'); util.inherits(Stream, EE); -Stream.Readable = require('./_stream_readable.js'); -Stream.Writable = require('./_stream_writable.js'); -Stream.Duplex = require('./_stream_duplex.js'); -Stream.Transform = require('./_stream_transform.js'); -Stream.PassThrough = require('./_stream_passthrough.js'); +Stream.Readable = require('_stream_readable'); +Stream.Writable = require('_stream_writable'); +Stream.Duplex = require('_stream_duplex'); +Stream.Transform = require('_stream_transform'); +Stream.PassThrough = require('_stream_passthrough'); // Backwards-compat with node 0.4.x Stream.Stream = Stream; diff --git a/builtin/url.js b/builtin/url.js index b02b748..6e730b2 100644 --- a/builtin/url.js +++ b/builtin/url.js @@ -21,7 +21,7 @@ var punycode = { encode : function (s) { return s } }; var util = require('util'); -var shims = require('./_shims.js'); +var shims = require('_shims'); exports.parse = urlParse; exports.resolve = urlResolve; diff --git a/builtin/util.js b/builtin/util.js index 431fe73..0724af7 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -19,7 +19,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var shims = require('./_shims.js'); +var shims = require('_shims'); var formatRegExp = /%[sdj%]/g; exports.format = function(f) { @@ -294,7 +294,7 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { output.push(''); } } - + shims.forEach(keys, function(key) { if (!key.match(/^\d+$/)) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, diff --git a/index.js b/index.js index 499c013..93f53c9 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,6 @@ var core = {}; // load core modules from builtin dir fs.readdirSync(path.resolve(__dirname, 'builtin')).forEach(function(file) { - if (file[0] === '_') return; core[path.basename(file, '.js')] = path.resolve(__dirname, 'builtin', file); }); diff --git a/test/node/node-test.js b/test/node/node-test.js index d2320fe..2347a26 100644 --- a/test/node/node-test.js +++ b/test/node/node-test.js @@ -4,6 +4,12 @@ var builtins = require('../../index.js'); test('test that all the modules are set', function (t) { t.deepEqual(Object.keys(builtins).sort(), [ + '_shims', + '_stream_duplex', + '_stream_passthrough', + '_stream_readable', + '_stream_transform', + '_stream_writable', 'assert', 'buffer', 'child_process',