diff --git a/README.md b/README.md index 5b6dfac..907bd99 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,117 @@ # 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. +> +> 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_ +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) diff --git a/builtin/_shims.js b/builtin/_shims.js new file mode 100644 index 0000000..fcbabc9 --- /dev/null +++ b/builtin/_shims.js @@ -0,0 +1,216 @@ + + +// +// 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, self) { + if (xs.forEach) return xs.forEach(fn, self); + for (var i = 0; i < xs.length; i++) { + fn.call(self, xs[i], i, xs); + } +}; + +// 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); + 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; +}; + +// 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); + }; +} + +// String.prototype.trim is supported in IE9 +exports.trim = function (str) { + if (str.trim) return str.trim(); + return str.replace(/^\s+|\s+$/g, ''); +}; + +// Function.prototype.bind is supported in IE9 +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)])); + }; +}; + +// 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 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)) { + result.push(name); + } + } + 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) { + 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'); + } + return result; +} + +var keys = typeof Object.keys === 'function' ? Object.keys : keysShim; +var getOwnPropertyNames = typeof Object.getOwnPropertyNames === 'function' ? + Object.getOwnPropertyNames : propertyShim; + +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; +} + +// 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/_stream_duplex.js b/builtin/_stream_duplex.js new file mode 100644 index 0000000..891320e --- /dev/null +++ b/builtin/_stream_duplex.js @@ -0,0 +1,71 @@ +// 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 +// prototypally inherits from Readable, and then parasitically from +// Writable. + +module.exports = Duplex; +var util = require('util'); +var shims = require('_shims'); +var timers = require('timers'); +var Readable = require('_stream_readable'); +var Writable = require('_stream_writable'); + +util.inherits(Duplex, Readable); + +shims.forEach(shims.keys(Writable.prototype), 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(shims.bind(this.end, this)); +} diff --git a/builtin/_stream_passthrough.js b/builtin/_stream_passthrough.js new file mode 100644 index 0000000..a5e9864 --- /dev/null +++ b/builtin/_stream_passthrough.js @@ -0,0 +1,41 @@ +// 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. +// Every written chunk gets output as-is. + +module.exports = PassThrough; + +var Transform = require('_stream_transform'); +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..0466ab2 --- /dev/null +++ b/builtin/_stream_readable.js @@ -0,0 +1,920 @@ +// 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; + +var EE = require('events').EventEmitter; +var Stream = require('stream'); +var shims = require('_shims'); +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 + shims.forEach(state.pipes, 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 = shims.indexOf(state.pipes, 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']; + 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 + // 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..c5b81dc --- /dev/null +++ b/builtin/_stream_transform.js @@ -0,0 +1,204 @@ +// 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", +// 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'); +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..039e558 --- /dev/null +++ b/builtin/_stream_writable.js @@ -0,0 +1,368 @@ +// 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 +// 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/assert.js b/builtin/assert.js index 3007829..e2c4056 100644 --- a/builtin/assert.js +++ b/builtin/assert.js @@ -1,19 +1,29 @@ +// 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 Buffer = require("buffer").Buffer; +var shims = require('_shims'); 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 +37,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 +100,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 +140,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 +151,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 +180,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 +200,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 +259,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 +273,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 +288,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 +309,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/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/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/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/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/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/builtin/events.js b/builtin/events.js index d468b73..6dbdde2 100644 --- a/builtin/events.js +++ b/builtin/events.js @@ -1,53 +1,77 @@ -if (!process.EventEmitter) process.EventEmitter = function () {}; +// 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 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 +85,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 (isArray(handler)) { - var args = Array.prototype.slice.call(arguments, 1); + } else if (util.isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; - 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 (!util.isFunction(listener)) + throw TypeError('listener must be a function'); - if (!this._events) this._events = {}; + 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 +157,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/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/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/builtin/path.js b/builtin/path.js index 14b960a..d21a977 100644 --- a/builtin/path.js +++ b/builtin/path.js @@ -1,10 +1,26 @@ -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; -} +// 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'); // 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 +29,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 +52,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 +94,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 = shims.substr(path, -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 +108,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 +173,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/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/querystring.js b/builtin/querystring.js index aff8065..d42f0a6 100644 --- a/builtin/querystring.js +++ b/builtin/querystring.js @@ -1,317 +1,210 @@ +// 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; +var util = require('util'); +var shims = require('_shims'); +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.map(shims.keys(obj), function(k) { + var ks = QueryString.escape(stringifyPrimitive(k)) + eq; + if (util.isArray(obj[k])) { + return shims.map(obj[k], 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); - } - } -} + }).join(sep); -/** - * Merge parent key/val pair. - */ - -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); + if (!name) return ''; + return QueryString.escape(stringifyPrimitive(name)) + eq + + QueryString.escape(stringifyPrimitive(obj)); +}; - // ?foo - if ('' == key) key = pair, val = ''; - if ('' == key) return ret; +// Parse a key=val string. +QueryString.parse = QueryString.decode = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; - return merge(ret, decode(key), decode(val)); - }, { base: {} }).base; -} + if (!util.isString(qs) || qs.length === 0) { + return obj; + } -/** - * 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); -}; + var regexp = /\+/g; + qs = qs.split(sep); -/** - * Turn the given `obj` into a query string - * - * @param {Object} obj - * @return {String} - * @api public - */ - -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)); + var maxKeys = 1000; + if (options && util.isNumber(options.maxKeys)) { + maxKeys = options.maxKeys; } -}; -/** - * 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 - */ - -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 len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; } - return ret.join('&'); -} -/** - * Stringify the given `obj`. - * - * @param {Object} obj - * @param {String} prefix - * @return {String} - * @api private - */ - -function stringifyObject(obj, prefix) { - var ret = [] - , keys = objectKeys(obj) - , key; - - for (var i = 0, len = keys.length; i < len; ++i) { - key = keys[i]; - if (null == obj[key]) { - ret.push(encodeURIComponent(key) + '='); + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; + + 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]; - } -} + try { + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + } catch (e) { + k = QueryString.unescape(kstr, true); + v = QueryString.unescape(vstr, true); + } -/** - * Locate last brace in `str` within the key. - * - * @param {String} str - * @return {Number} - * @api private - */ - -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/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/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/builtin/stream.js b/builtin/stream.js index 4feb410..098ff61 100644 --- a/builtin/stream.js +++ b/builtin/stream.js @@ -1,14 +1,48 @@ -var events = require('events'); -var util = require('util'); +// 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. -function Stream() { - events.EventEmitter.call(this); -} -util.inherits(Stream, events.EventEmitter); module.exports = Stream; + +var EE = require('events').EventEmitter; +var util = require('util'); + +util.inherits(Stream, EE); +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; + + +// 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 +65,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 +76,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 +84,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 +112,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/builtin/string_decoder.js b/builtin/string_decoder.js index f9a0982..80edb05 100644 --- a/builtin/string_decoder.js +++ b/builtin/string_decoder.js @@ -1,5 +1,35 @@ +// 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; + +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/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/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/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/builtin/url.js b/builtin/url.js index d623925..6e730b2 100644 --- a/builtin/url.js +++ b/builtin/url.js @@ -1,22 +1,48 @@ +// 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'); +var shims = require('_shims'); 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 +50,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 +80,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 +96,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 = shims.trim(rest); 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 +130,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; } - 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; + // 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); } - if (firstNonHost !== -1) { - out.host = rest.substr(0, firstNonHost); - rest = rest.substr(firstNonHost); - } else { - out.host = rest; - rest = ''; + // 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); } - // 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]; + // 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. + 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 +236,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(); } - out.hostname = newOut.join('.'); - out.host = (out.hostname || '') + - ((out.port) ? ':' + out.port : ''); - out.href += out.host; + 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('.'); + } + + 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 +295,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 +342,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 || ''; - - if (protocol && protocol.substr(-1) !== ':') protocol += ':'; + 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 && shims.substr(protocol, -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 +395,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.forEach(shims.keys(this), 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.forEach(shims.keys(relative), 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 +466,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.forEach(shims.keys(relative), 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 +481,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 +555,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 @@ -551,7 +634,7 @@ function urlResolveObject(source, relative) { srcPath.unshift(''); } - if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { + if (hasTrailingSlash && (shims.substr(srcPath.join('/'), -1) !== '/')) { srcPath.push(''); } @@ -560,45 +643,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/builtin/util.js b/builtin/util.js index f460aa3..0724af7 100644 --- a/builtin/util.js +++ b/builtin/util.js @@ -1,250 +1,483 @@ -var events = require('events'); +// 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'); -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 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 = {}; + + shims.forEach(array, 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); + } - var base, type, braces; - // Determine the object type - if (isArray(value)) { - type = 'Array'; - braces = ['[', ']']; + // 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]; + } + + 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); + }); + } + + 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) + ']'; +} + - // Make functions say that they are functions - if (typeof value === 'function') { - var n = value.name ? ': ' + value.name : ''; - base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; +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(); + shims.forEach(keys, 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 = shims.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', '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'); - } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (shims.indexOf(ctx.seen, 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 = shims.reduce(output, 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 +490,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 +522,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/index.js b/index.js index 7a5f653..93f53c9 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'); @@ -24,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 cb57ef2..0227691 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,11 @@ { "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": { + "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" @@ -14,14 +17,17 @@ "author": "Alex Gorbatchev ", "license": "MIT", "dependencies": { - "resolve": "0.3.x", "punycode": "1.2.x", "console-browserify": "0.1.x", "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" + "constants-browserify": "0.0.x", + "os-browserify": "0.1.0" + }, + "devDependencies": { + "tape": "1.0.x" } } diff --git a/test/browser/assert-simple.js b/test/browser/assert-simple.js new file mode 100644 index 0000000..c5725a2 --- /dev/null +++ b/test/browser/assert-simple.js @@ -0,0 +1,354 @@ +// 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 assert = require('assert'); + +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, shims.keys(a1), shims.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(); +}); + +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.doesNotThrow(makeBlock(thrower, TypeError), TypeError); + } catch (e) { + threw = true; + t.ok(e instanceof assert.AssertionError); + } + t.equal(true, threw, + 'a.doesNotThrow is not catching type matching errors'); + + t.end(); +}); + +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.throws( + function() { + throw ({}); + }, + Array + ); + } catch (e) { + threw = true; + } + t.ok(threw, 'wrong constructor validation'); + + t.end(); +}); + +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; + + var c = {}; + c.b = c; + + var gotError = false; + try { + assert.deepEqual(b, c); + } catch (e) { + gotError = true; + } + + 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(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(); +}); 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(); +}); 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(); +}); 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(); +}); 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(); +}); 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(); +}); diff --git a/test/browser/events-listeners.js b/test/browser/events-listeners.js new file mode 100644 index 0000000..dc9fe19 --- /dev/null +++ b/test/browser/events-listeners.js @@ -0,0 +1,68 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +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..aebc39b --- /dev/null +++ b/test/browser/events-remove-listeners.js @@ -0,0 +1,110 @@ +// 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 events = require('events'); + +var count = 0; + +function listener1() { + count++; +} + +function listener2() { + count++; +} + +function 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(); +}); 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(); +}); 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(); +}); diff --git a/test/browser/path-simple.js b/test/browser/path-simple.js new file mode 100644 index 0000000..76b10dc --- /dev/null +++ b/test/browser/path-simple.js @@ -0,0 +1,242 @@ +// 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 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. + 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]; + shims.forEach(joinThrowTests, 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'], + [['/some/dir', '.', '/absolute/'], '/absolute']]; + + shims.forEach(resolveTests, 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']]; + + shims.forEach(relativeTests, 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(); +}); diff --git a/test/browser/querystring-simple.js b/test/browser/querystring-simple.js new file mode 100644 index 0000000..bd8aae0 --- /dev/null +++ b/test/browser/querystring-simple.js @@ -0,0 +1,255 @@ +// 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 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) { + 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) { + 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) { + shims.forEach(qsWeirdObjects, function(testCase) { + t.deepEqual(testCase[2], qs.parse(testCase[1])); + }); + t.end(); +}); + +test('qs.stringify - simple example', function (t) { + shims.forEach(qsNoMungeTestCases, 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) { + shims.forEach(qsTestCases, function(testCase) { + t.equal(testCase[1], qs.stringify(testCase[2])); + }); + + shims.forEach(qsColonTestCases, function(testCase) { + t.equal(testCase[1], qs.stringify(testCase[2], ';', ':')); + }); + + shims.forEach(qsWeirdObjects, 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( + shims.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( + shims.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(); +}); 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(); +}); 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(); +}); diff --git a/test/browser/stream-big-push.js b/test/browser/stream-big-push.js new file mode 100644 index 0000000..f04f551 --- /dev/null +++ b/test/browser/stream-big-push.js @@ -0,0 +1,87 @@ +// 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'); +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..a45b752 --- /dev/null +++ b/test/browser/stream-pipe-after-end.js @@ -0,0 +1,83 @@ +// 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; +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; + if (enderEnded && writableFinished) t.end(); + }); + t.ok(!enderEnded); + var c = ender.read(); + t.equal(c, null); + + var w = new TestWritable(); + var writableFinished = false; + w.on('finish', function() { + writableFinished = true; + if (enderEnded && writableFinished) t.end(); + }); + piper.pipe(w); + }); +}); diff --git a/test/browser/stream-pipe-cleanup.js b/test/browser/stream-pipe-cleanup.js new file mode 100644 index 0000000..349f31c --- /dev/null +++ b/test/browser/stream-pipe-cleanup.js @@ -0,0 +1,123 @@ +// 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'); +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..c8723f6 --- /dev/null +++ b/test/browser/stream-pipe-error-handling.js @@ -0,0 +1,59 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +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..0e9e438 --- /dev/null +++ b/test/browser/stream-pipe-event.js @@ -0,0 +1,51 @@ +// 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'); +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..fc2ca64 --- /dev/null +++ b/test/browser/stream-push-order.js @@ -0,0 +1,54 @@ +// 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'); + +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..c811d2e --- /dev/null +++ b/test/browser/stream-push-strings.js @@ -0,0 +1,70 @@ +// 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 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(shims.bind(function() { + this.push('last chunk'); + }, this), 100); + case 2: + return this.push('second to last chunk'); + case 3: + return timers.setImmediate(shims.bind(function() { + this.push('first chunk'); + }, 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..97ea7e5 --- /dev/null +++ b/test/browser/stream-readable-event.js @@ -0,0 +1,123 @@ +// 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 timers = require('timers'); +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; + timers.setImmediate(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; + if (_readCalled && caughtReadable) t.end(); + }; + + // This triggers a 'readable' event, which is lost. + r.push(new Buffer('bl')); + + var caughtReadable = false; + timers.setImmediate(function() { + // assert we're testing what we think we are + t.ok(r._readableState.reading); + r.on('readable', function() { + caughtReadable = true; + if (_readCalled && 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; + timers.setImmediate(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..566768b --- /dev/null +++ b/test/browser/stream-readable-flow-recursion.js @@ -0,0 +1,71 @@ +// 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; +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..99e92ed --- /dev/null +++ b/test/browser/stream-unshift-empty-chunk.js @@ -0,0 +1,83 @@ +// 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; + +// 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..761a5fd --- /dev/null +++ b/test/browser/stream-unshift-read-race.js @@ -0,0 +1,135 @@ +// 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; + +// 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..e4d0127 --- /dev/null +++ b/test/browser/stream2-basic.js @@ -0,0 +1,439 @@ +// 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; + +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(shims.bind(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'); + } + }, 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); +}); + + + +shims.forEach([1,2,3,4,5,6,7,8,9], 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]); +}); + + +shims.forEach([1,2,3,4,5,6,7,8,9], 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..1757f63 --- /dev/null +++ b/test/browser/stream2-compatibility.js @@ -0,0 +1,53 @@ +// 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'); +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..6564207 --- /dev/null +++ b/test/browser/stream2-finish-pipe.js @@ -0,0 +1,44 @@ +// 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'); +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..df22032 --- /dev/null +++ b/test/browser/stream2-large-read-stall.js @@ -0,0 +1,75 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +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 + // remaining buffer, then make sure that 'end' still emits. + + var READSIZE = 100; + var PUSHSIZE = 20; + var PUSHCOUNT = 1000; + var HWM = 50; + + 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..9efe15d --- /dev/null +++ b/test/browser/stream2-objects.js @@ -0,0 +1,314 @@ +// 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; +var shims = require('../../builtin/_shims.js'); +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; + shims.forEach(list, 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']; + shims.forEach(list, 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++; + }; + + shims.forEach(list, 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..fc0f1e8 --- /dev/null +++ b/test/browser/stream2-pipe-error-handling.js @@ -0,0 +1,107 @@ +// 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'); +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..788fcd3 --- /dev/null +++ b/test/browser/stream2-push.js @@ -0,0 +1,132 @@ +// 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; +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, 'reading 1'); + source.emit('data', chunk); + t.ok(reading, 'reading 2'); + source.emit('data', chunk); + t.ok(reading, 'reading 3'); + source.emit('data', chunk); + t.ok(reading, 'reading 4'); + source.emit('data', chunk); + t.ok(!reading, 'not reading 5'); + if (set++ < 5) + setTimeout(data, 100); + else + end(); + } + + function finish() { + t.deepEqual(written, expectWritten); + } + + function end() { + source.emit('end'); + t.ok(!reading, 'not reading end'); + writer.end(stream.read()); + setTimeout(function() { + t.ok(ended, 'end emitted'); + t.end(); + }, 100); + } +}); \ 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..2a53c1c --- /dev/null +++ b/test/browser/stream2-read-sync-stack.js @@ -0,0 +1,51 @@ +// 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; +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..4b89bb7 --- /dev/null +++ b/test/browser/stream2-readable-empty-buffer-no-eof.js @@ -0,0 +1,118 @@ +// 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 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(shims.bind(r.read, r, 0), 10); + return r.push(new Buffer(0)); // Not-EOF! + case 3: + setTimeout(shims.bind(r.read, r, 0), 10); + return timers.setImmediate(function() { + return r.push(new Buffer(0)); + }); + case 4: + setTimeout(shims.bind(r.read, 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..7a00536 --- /dev/null +++ b/test/browser/stream2-readable-from-list.js @@ -0,0 +1,84 @@ +// 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; +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..e82a72c --- /dev/null +++ b/test/browser/stream2-readable-legacy-drain.js @@ -0,0 +1,79 @@ +// 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; + +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..7113ca8 --- /dev/null +++ b/test/browser/stream2-readable-non-empty-end.js @@ -0,0 +1,77 @@ +// 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; +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; + 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..c01cb1b --- /dev/null +++ b/test/browser/stream2-readable-wrap-empty.js @@ -0,0 +1,46 @@ +// 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; +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..0f12898 --- /dev/null +++ b/test/browser/stream2-set-encoding.js @@ -0,0 +1,321 @@ +// 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; +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(shims.bind(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); + }, 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..2d63913 --- /dev/null +++ b/test/browser/stream2-transform.js @@ -0,0 +1,418 @@ +// 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; +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(shims.map(tx._writableState.buffer, 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(shims.bind(function() { + this.state += s.charAt(0); + if (this.state.length === 3) { + pt.push(new Buffer(this.state)); + this.state = ''; + } + cb(); + }, 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; + emits++; + }); + + var i = 0; + + pt.write(new Buffer('foog')); + + pt.write(new Buffer('bark')); + + t.equal(emits, 1); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5) + '', 'null'); + + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + + t.equal(emits, 2); + + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5), null); + + pt.end(); + + t.equal(emits, 3); + + t.equal(pt.read(5).toString(), 'l'); + t.equal(pt.read(5), null); + + t.equal(emits, 3); + t.end(); +}); + +test('passthrough event emission reordered', function(t) { + var pt = new PassThrough; + var emits = 0; + pt.on('readable', function() { + emits++; + }); + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + t.equal(emits, 1); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5), null); + + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'arkba'); + + t.equal(pt.read(5), null); + + 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) { + 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) { + 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; + }); + + shims.forEach(objects, 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) { + 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; + }); + + shims.forEach(objects, 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..5d5c24f --- /dev/null +++ b/test/browser/stream2-unpipe-drain.js @@ -0,0 +1,84 @@ +// 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'); +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..3d1cb39 --- /dev/null +++ b/test/browser/stream2-unpipe-leak.js @@ -0,0 +1,70 @@ +// 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'); +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..fe9ae00 --- /dev/null +++ b/test/browser/stream2-writable.js @@ -0,0 +1,340 @@ +// 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 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(shims.bind(function() { + this.buffer.push(chunk.toString()); + this.written += chunk.length; + cb(); + }, 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(); + }); + + shims.forEach(chunks, 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'); + }); + + shims.forEach(chunks, 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'); + }); + + shims.forEach(chunks, 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 = shims.reduce(shims.map(chunks, function(chunk, i) { + return [i, function(er) { + callbacks._called[i] = chunk; + }]; + }), 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(); + }); + }); + + shims.forEach(chunks, 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(); +}); diff --git a/test/browser/string-decoder-simple.js b/test/browser/string-decoder-simple.js new file mode 100644 index 0000000..d7c535b --- /dev/null +++ b/test/browser/string-decoder-simple.js @@ -0,0 +1,96 @@ +// 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; +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(); +}); 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(); +}); 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(); +}); 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(); +}); diff --git a/test/browser/url-simple.js b/test/browser/url-simple.js new file mode 100644 index 0000000..439b271 --- /dev/null +++ b/test/browser/url-simple.js @@ -0,0 +1,1435 @@ +// 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'); +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 = { + '//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]; + + shims.forEach(shims.keys(actual), function (i) { + if (expected[i] === undefined && actual[i] === null) { + expected[i] = null; + } + }); + + t.deepEqual(objectify(actual), objectify(expected)); + t.deepEqual(objectify(spaced), objectify(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(objectify(actual), objectify(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) { + shims.forEach(relativeTests, 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 + shims.forEach([ + undefined, + null, + true, + false, + 0.0, + 0, + [], + {} + ], 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) { + shims.forEach(relativeTests2, 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] + shims.forEach(relativeTests, function(relativeTest) { + var actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]), + expected = url.parse(relativeTest[2]); + + + t.deepEqual(objectify(actual), objectify(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); + } + shims.forEach(relativeTests2, function(relativeTest) { + var actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]), + expected = url.parse(relativeTest[2]); + + t.deepEqual(objectify(actual), objectify(expected)); + + var expected = relativeTest[2], + actual = url.format(actual); + + t.equal(actual, expected, + 'format(' + relativeTest[1] + ') == ' + expected + + '\nactual:' + actual); + }); + + t.end(); +}); diff --git a/test/browser/util-inspect.js b/test/browser/util-inspect.js new file mode 100644 index 0000000..bd52760 --- /dev/null +++ b/test/browser/util-inspect.js @@ -0,0 +1,42 @@ +// 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 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..ac7b8da --- /dev/null +++ b/test/browser/util-is.js @@ -0,0 +1,83 @@ +// 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 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(shims.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(shims.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(shims.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(shims.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(); +}); 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); +}; diff --git a/test/node/node-test.js b/test/node/node-test.js new file mode 100644 index 0000000..2347a26 --- /dev/null +++ b/test/node/node-test.js @@ -0,0 +1,47 @@ + +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(), [ + '_shims', + '_stream_duplex', + '_stream_passthrough', + '_stream_readable', + '_stream_transform', + '_stream_writable', + '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', + 'timers', + 'tls', + 'tty', + 'url', + 'util', + 'vm', + 'zlib' + ]); + t.end(); +});