diff --git a/lib/assert.js b/lib/assert.js index 6f6f73df40..e0720c8d10 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -1,21 +1,72 @@ -var sys = require('sys'); -var util = require('util'); +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// 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 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 pSlice = Array.prototype.slice; + +// 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. + var assert = exports; -assert.AssertionError = function (options) { +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({message: message, actual: actual, expected: expected}) + +assert.AssertionError = function AssertionError(options) { this.name = "AssertionError"; this.message = options.message; this.actual = options.actual; this.expected = options.expected; this.operator = options.operator; - - Error.captureStackTrace(this, fail); + //v8 specific + if(Error.captureStackTrace){ + Error.captureStackTrace(this,fail); + //node specific, removes the node machinery stack frames + if(typeof(__filename) !== undefined){ + var stack = this.stack.split('\n'); + for (var i = stack.length - 1; i >= 0; i--) { + if(stack[i].indexOf(__filename) != -1){ + this.stack = stack.slice(0,i+2).join('\n'); + break; + } + } + } + } }; -sys.inherits(assert.AssertionError, Error); + +assert.AssertionError.prototype.toString = function(){ + if(this.message) + return [this.name+":", this.message].join(' '); + else + return [this.name+":", JSON.stringify(this.expected ), this.operator , JSON.stringify(this.actual)].join(" "); +} // assert.AssertionError instanceof Error -// assert.AssertionError.prototype = Object.create(Error.prototype); +assert.AssertionError.__proto__ = Error.prototype; // At present only the three keys mentioned above are used and // understood by the spec. Implementations or sub modules can pass @@ -37,6 +88,9 @@ function fail(actual, expected, message, operator) { }); } +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + // 4. Pure assertion tests whether a value is truthy, as determined // by !!guard. // assert.ok(guard, message_opt); @@ -44,7 +98,7 @@ function fail(actual, expected, message, operator) { // message_opt);. To test strictly for the value true, use // assert.strictEqual(true, guard, message_opt);. -assert.ok = function (value, message) { +assert.ok = function ok(value, message) { if (!!!value) fail(value, true, message, "=="); }; @@ -53,7 +107,7 @@ assert.ok = function (value, message) { // ==. // assert.equal(actual, expected, message_opt); -assert.equal = function (actual, expected, message) { +assert.equal = function equal(actual, expected, message) { if (actual != expected) fail(actual, expected, message, "=="); }; @@ -62,7 +116,7 @@ assert.equal = function (actual, expected, message) { // 6. The non-equality assertion tests for whether two objects are not equal // with != assert.notEqual(actual, expected, message_opt); -assert.notEqual = function (actual, expected, message) { +assert.notEqual = function notEqual(actual, expected, message) { if (actual == expected) fail(actual, expected, message, "!="); }; @@ -70,13 +124,12 @@ assert.notEqual = function (actual, expected, message) { // 7. The equivalence assertion tests a deep equality relation. // assert.deepEqual(actual, expected, message_opt); -exports.deepEqual = function (actual, expected, message) { - if (!deepEqual(actual, expected)) +exports.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) fail(actual, expected, message, "deepEqual"); }; -function deepEqual(actual, expected) { - +function _deepEqual(actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) { return true; @@ -85,7 +138,7 @@ function deepEqual(actual, expected) { // equivalent if it is also a Date object that refers to the same time. } else if (actual instanceof Date && expected instanceof Date) { - return actual.toValue() === expected.toValue(); + return actual.getTime() === expected.getTime(); // 7.3. Other pairs that do not both pass typeof value == "object", // equivalence is determined by ==. @@ -100,43 +153,73 @@ function deepEqual(actual, expected) { // corresponding key, and an identical "prototype" property. Note: this // accounts for both named and indexed properties on Arrays. } else { - return actual.prototype === expected.prototype && objEquiv(actual, expected); + return objEquiv(actual, expected); } } -function objEquiv(a, b, stack) { - return ( - !util.no(a) && !util.no(b) && - arrayEquiv( - util.sort(util.object.keys(a)), - util.sort(util.object.keys(b)) - ) && - util.object.keys(a).every(function (key) { - return deepEqual(a[key], b[key], stack); - }) - ); +function isUndefinedOrNull (value) { + return value === null || value === undefined; } -function arrayEquiv(a, b, stack) { - return util.isArrayLike(b) && - a.length == b.length && - util.zip(a, b).every(util.apply(function (a, b) { - return deepEqual(a, b, stack); - })); +function isArguments (object){ + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv (a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical "prototype" property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if(isArguments(a)){ + if(!isArguments(b)){ + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a,b); + } + try{ + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates hasOwnProperty) + if(ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if(ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if(!_deepEqual(a[key], b[key] )) + return false; + } + return true; } // 8. The non-equivalence assertion tests for any deep inequality. // assert.notDeepEqual(actual, expected, message_opt); -exports.notDeepEqual = function (actual, expected, message) { - if (deepEqual(actual, expected)) +exports.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) fail(actual, expected, message, "notDeepEqual"); }; // 9. The strict equality assertion tests strict equality, as determined by ===. // assert.strictEqual(actual, expected, message_opt); -assert.strictEqual = function (actual, expected, message) { +assert.strictEqual = function strictEqual(actual, expected, message) { if (actual !== expected) fail(actual, expected, message, "==="); }; @@ -144,7 +227,7 @@ assert.strictEqual = function (actual, expected, message) { // 10. The strict non-equality assertion tests for strict inequality, as determined by !==. // assert.notStrictEqual(actual, expected, message_opt); -assert.notStrictEqual = function (actual, expected, message) { +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { if (actual === expected) fail(actual, expected, message, "!=="); }; @@ -152,16 +235,31 @@ assert.notStrictEqual = function (actual, expected, message) { // 11. Expected to throw an error: // assert.throws(block, Error_opt, message_opt); -assert["throws"] = function (block, Error, message) { - var threw = false, - exception = null; - - if (typeof Error == "string") { - message = Error; - Error = undefined; - } else { - Error = message; - message = ""; +assert.throws = function(block, /*optional*/error, /*optional*/message){ + var args = [true] + _throws.apply(this, args.concat(pSlice.call(arguments))); +} + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message){ + var args = [false] + _throws.apply(this, args.concat(pSlice.call(arguments))); +} + +var _throws = function (shouldThrow, block, err, message) { + var exception = null + threw = false + typematters = true; + message = message || ""; + + //handle optional arguments + if(arguments.length == 3){ + if(typeof(err) == "string"){ + message = err; + typematters = false; + } + } else if(arguments.length == 2) { + typematters = false; } try { @@ -170,7 +268,19 @@ assert["throws"] = function (block, Error, message) { threw = true; exception = e; } - - if (!threw) - fail("Expected exception" + (message ? ": " + message : "")); + + if(shouldThrow && !threw){ + fail("Missing expected exception"+ + (err && err.name ? " ("+err.name+")." : '.') + + (message ? " " + message : "")); + } + if(!shouldThrow && threw && typematters && exception instanceof err){ + fail("Got unwanted exception" + + (err && err.name ? " ("+err.name+")." : '.') + + (message ? " " + message : "")); + } + if((shouldThrow && threw && typematters && !(exception instanceof err)) || + (!shouldThrow && threw)){ + throw exception; + } }; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index 18fe85087e..0000000000 --- a/lib/util.js +++ /dev/null @@ -1,1033 +0,0 @@ - -// a decorator for functions that curry "polymorphically", -// that is, that return a function that can be tested -// against various objects if they're only "partially -// completed", or fewer arguments than needed are used. -// -// this enables the idioms: -// [1, 2, 3].every(lt(4)) eq true -// [1, 2, 3].map(add(1)) eq [2, 3, 4] -// [{}, {}, {}].forEach(set('a', 10)) -// -exports.operator = function (name, length, block) { - var operator = function () { - var args = exports.array(arguments); - var completion = function (object) { - if ( - typeof object == "object" && - object !== null && // seriously? typeof null == "object" - name in object && // would throw if object === null - // not interested in literal objects: - !Object.prototype.hasOwnProperty.call(object, name) - ) - return object[name].apply(object, args); - return block.apply( - this, - [object].concat(args) - ); - }; - if (arguments.length < length) { - // polymoprhic curry, delayed completion - return completion; - } else { - // immediate completion - return completion.call(this, args.shift()); - } - }; - operator.name = name; - operator.displayName = name; - operator.length = length; - operator.operator = block; - return operator; -}; - -exports.no = function (value) { - return value === null || value === undefined; -}; - -// object - -exports.object = exports.operator('toObject', 1, function (object) { - var items = object; - if (!items.length) - items = exports.items(object); - var copy = {}; - for (var i = 0; i < items.length; i++) { - var item = items[i]; - var key = item[0]; - var value = item[1]; - copy[key] = value; - } - return copy; -}); - -exports.object.copy = function (object) { - var copy = {}; - exports.object.keys(object).forEach(function (key) { - copy[key] = object[key]; - }); - return copy; -}; - -exports.object.deepCopy = function (object) { - var copy = {}; - exports.object.keys(object).forEach(function (key) { - copy[key] = exports.deepCopy(object[key]); - }); - return copy; -}; - -exports.object.eq = function (a, b, stack) { - return ( - !exports.no(a) && !exports.no(b) && - exports.array.eq( - exports.sort(exports.object.keys(a)), - exports.sort(exports.object.keys(b)) - ) && - exports.object.keys(a).every(function (key) { - return exports.eq(a[key], b[key], stack); - }) - ); -}; - -exports.object.len = function (object) { - return exports.object.keys(object).length; -}; - -exports.object.has = function (object, key) { - return Object.prototype.hasOwnProperty.call(object, key); -}; - -exports.object.keys = function (object) { - var keys = []; - for (var key in object) { - if (exports.object.has(object, key)) - keys.push(key); - } - return keys; -}; - -exports.object.values = function (object) { - var values = []; - exports.object.keys(object).forEach(function (key) { - values.push(object[key]); - }); - return values; -}; - -exports.object.items = function (object) { - var items = []; - exports.object.keys(object).forEach(function (key) { - items.push([key, object[key]]); - }); - return items; -}; - -/** - * Updates an object with the properties from another object. - * This function is variadic requiring a minimum of two arguments. - * The first argument is the object to update. Remaining arguments - * are considered the sources for the update. If multiple sources - * contain values for the same property, the last one with that - * property in the arguments list wins. - * - * example usage: - * util.update({}, { hello: "world" }); // -> { hello: "world" } - * util.update({}, { hello: "world" }, { hello: "le monde" }); // -> { hello: "le monde" } - * - * @returns Updated object - * @type Object - * - */ -exports.object.update = function () { - return variadicHelper(arguments, function(target, source) { - var key; - for (key in source) { - if (exports.object.has(source, key)) { - target[key] = source[key]; - } - } - }); -}; - -exports.object.complete = function () { - return variadicHelper(arguments, function(target, source) { - var key; - for (key in source) { - if ( - exports.object.has(source, key) && - !exports.object.has(target, key) - ) { - target[key] = source[key]; - } - } - }); -}; - -exports.object.repr = function (object) { - return "{" + - exports.object.keys(object) - .map(function (key) { - return exports.enquote(key) + ": " + - exports.repr(object[key]); - }).join(", ") + - "}"; -}; - -/** - * @param args Arguments list of the calling function - * First argument should be a callback that takes target and source parameters. - * Second argument should be target. - * Remaining arguments are treated a sources. - * - * @returns Target - * @type Object - */ -var variadicHelper = function (args, callback) { - var sources = Array.prototype.slice.call(args); - var target = sources.shift(); - - sources.forEach(function(source) { - callback(target, source); - }); - - return target; -}; - -// array - -exports.array = function (array) { - if (exports.no(array)) - return []; - if (!exports.isArrayLike(array)) { - if ( - array.toArray && - !Object.prototype.hasOwnProperty.call(array, 'toArray') - ) { - return array.toArray(); - } else if ( - array.forEach && - !Object.prototype.hasOwnProperty.call(array, 'forEach') - ) { - var results = []; - array.forEach(function (value) { - results.push(value); - }); - return results; - } else { - return exports.items(array); - } - } - return Array.prototype.slice.call(array); -}; - -exports.array.coerce = function (array) { - if (!Array.isArray(array)) - return exports.array(array); - return array; -}; - -exports.isArrayLike = function(object) { - return Array.isArray(object) || exports.isArguments(object); -}; - -// from http://code.google.com/p/google-caja/wiki/NiceNeighbor -// by "kangax" -// -// Mark Miller posted a solution that will work in ES5 compliant -// implementations, that may provide future insight: -// (http://groups.google.com/group/narwhaljs/msg/116097568bae41c6) -exports.isArguments = function (object) { - // ES5 reliable positive - if (Object.prototype.toString.call(object) == "[object Arguments]") - return true; - // for ES5, we will still need a way to distinguish false negatives - // from the following code (in ES5, it is possible to create - // an object that satisfies all of these constraints but is - // not an Arguments object). - // callee should exist - if ( - !typeof object == "object" || - !Object.prototype.hasOwnProperty.call(object, 'callee') || - !object.callee || - // It should be a Function object ([[Class]] === 'Function') - Object.prototype.toString.call(object.callee) !== '[object Function]' || - typeof object.length != 'number' - ) - return false; - for (var name in object) { - // both "callee" and "length" should be { DontEnum } - if (name === 'callee' || name === 'length') return false; - } - return true; -}; - -exports.array.copy = exports.array; - -exports.array.deepCopy = function (array) { - return array.map(exports.deepCopy); -}; - -exports.array.len = function (array) { - return array.length; -}; - -exports.array.has = function (array, value) { - return Array.prototype.indexOf.call(array, value) >= 0; -}; - -exports.array.put = function (array, key, value) { - array.splice(key, 0, value); - return array; -}; - -exports.array.del = function (array, begin, end) { - array.splice(begin, end === undefined ? 1 : (end - begin)); - return array; -}; - -exports.array.eq = function (a, b, stack) { - return exports.isArrayLike(b) && - a.length == b.length && - exports.zip(a, b).every(exports.apply(function (a, b) { - return exports.eq(a, b, stack); - })); -}; - -exports.array.lt = function (a, b) { - var length = Math.max(a.length, b.length); - for (var i = 0; i < length; i++) - if (!exports.eq(a[i], b[i])) - return exports.lt(a[i], b[i]); - return false; -}; - -exports.array.repr = function (array) { - return "[" + exports.map(array, exports.repr).join(', ') + "]"; -}; - -exports.array.first = function (array) { - return array[0]; -}; - -exports.array.last = function (array) { - return array[array.length - 1]; -}; - -exports.apply = exports.operator('apply', 2, function (args, block) { - return block.apply(this, args); -}); - -exports.copy = exports.operator('copy', 1, function (object) { - if (exports.no(object)) - return object; - if (exports.isArrayLike(object)) - return exports.array.copy(object); - if (typeof object == 'object') - return exports.object.copy(object); - return object; -}); - -exports.deepCopy = exports.operator('deepCopy', 1, function (object) { - if (exports.no(object)) - return object; - if (exports.isArrayLike(object)) - return exports.array.deepCopy(object); - if (typeof object == 'object') - return exports.object.deepCopy(object); - return object; -}); - -exports.repr = exports.operator('repr', 1, function (object) { - if (exports.no(object)) - return String(object); - if (exports.isArrayLike(object)) - return exports.array.repr(object); - if (typeof object == 'object') - return exports.object.repr(object); - if (typeof object == 'string') - return exports.enquote(object); - return object.toString(); -}); - -exports.keys = exports.operator('keys', 1, function (object) { - if (exports.isArrayLike(object)) - return exports.range(object.length); - else if (typeof object == 'object') - return exports.object.keys(object); - return []; -}); - -exports.values = exports.operator('values', 1, function (object) { - if (exports.isArrayLike(object)) - return exports.array(object); - else if (typeof object == 'object') - return exports.object.values(object); - return []; -}); - -exports.items = exports.operator('items', 1, function (object) { - if (exports.isArrayLike(object) || typeof object == "string") - return exports.enumerate(object); - else if (typeof object == 'object') - return exports.object.items(object); - return []; -}); - -exports.len = exports.operator('len', 1, function (object) { - if (exports.isArrayLike(object)) - return exports.array.len(object); - else if (typeof object == 'object') - return exports.object.len(object); -}); - -exports.has = exports.operator('has', 2, function (object, value) { - if (exports.isArrayLike(object)) - return exports.array.has(object, value); - else if (typeof object == 'object') - return exports.object.has(object, value); - return false; -}); - -exports.get = exports.operator('get', 2, function (object, key, value) { - if (typeof object == "string") { - if (!typeof key == "number") - throw new Error("TypeError: String keys must be numbers"); - if (!exports.has(exports.range(object.length), key)) { - if (arguments.length == 3) - return value; - throw new Error("KeyError: " + exports.repr(key)); - } - return object.charAt(key); - } - if (typeof object == "object") { - if (!exports.object.has(object, key)) { - if (arguments.length == 3) - return value; - throw new Error("KeyError: " + exports.repr(key)); - } - return object[key]; - } - throw new Error("Object does not have keys: " + exports.repr(object)); -}); - -exports.set = exports.operator('set', 3, function (object, key, value) { - object[key] = value; - return object; -}); - -exports.getset = exports.operator('getset', 3, function (object, key, value) { - if (!exports.has(object, key)) - exports.set(object, key, value); - return exports.get(object, key); -}); - -exports.del = exports.operator('del', 2, function (object, begin, end) { - if (exports.isArrayLike(object)) - return exports.array.del(object, begin, end); - delete object[begin]; - return object; -}); - -exports.cut = exports.operator('cut', 2, function (object, key) { - var result = exports.get(object, key); - exports.del(object, key); - return result; -}); - -exports.put = exports.operator('put', 2, function (object, key, value) { - if (exports.isArrayLike(object)) - return exports.array.put(object, key, value); - return exports.set(object, key, value); -}); - -exports.first = exports.operator('first', 1, function (object) { - return object[0]; -}); - -exports.last = exports.operator('last', 1, function (object) { - return object[object.length - 1]; -}); - -exports.update = exports.operator('update', 2, function () { - var args = Array.prototype.slice.call(arguments); - return exports.object.update.apply(this, args); -}); - -exports.complete = exports.operator('complete', 2, function (target, source) { - var args = Array.prototype.slice.call(arguments); - return exports.object.complete.apply(this, args); -}); - -// TODO insert -// TODO remove -// TODO discard - -exports.range = function () { - var start = 0, stop = 0, step = 1; - if (arguments.length == 1) { - stop = arguments[0]; - } else { - start = arguments[0]; - stop = arguments[1]; - step = arguments[2] || 1; - } - var range = []; - for (var i = start; i < stop; i += step) - range.push(i); - return range; -}; - -exports.forEach = function (array, block) { - Array.prototype.forEach.call( - exports.array.coerce(array), - block - ); -}; - -exports.forEachApply = function (array, block) { - Array.prototype.forEach.call( - exports.array.coerce(array), - exports.apply(block) - ); -}; - -exports.map = function (array, block, context) { - return Array.prototype.map.call( - exports.array.coerce(array), - block, - context - ); -}; - -exports.mapApply = function (array, block) { - return Array.prototype.map.call( - exports.array.coerce(array), - exports.apply(block) - ); -}; - -exports.every = exports.operator('every', 2, function (array, block, context) { - return exports.all(exports.map(array, block, context)); -}); - -exports.some = exports.operator('some', 2, function (array, block, context) { - return exports.any(exports.map(array, block, context)); -}); - -exports.all = exports.operator('all', 1, function (array) { - array = exports.array.coerce(array); - for (var i = 0; i < array.length; i++) - if (!array[i]) - return false; - return true; -}); - -exports.any = exports.operator('all', 1, function (array) { - array = exports.array.coerce(array); - for (var i = 0; i < array.length; i++) - if (array[i]) - return true; - return false; -}); - -exports.reduce = exports.operator('reduce', 2, function (array, block, basis) { - array = exports.array.coerce(array); - return array.reduce.apply(array, arguments); -}); - -exports.reduceRight = exports.operator('reduceRight', 2, function (array, block, basis) { - array = exports.array.coerce(array); - return array.reduceRight.apply(array, arguments); -}); - -exports.zip = function () { - return exports.transpose(arguments); -}; - -exports.transpose = function (array) { - array = exports.array.coerce(array); - var transpose = []; - var length = Math.min.apply(this, exports.map(array, function (row) { - return row.length; - })); - for (var i = 0; i < array.length; i++) { - var row = array[i]; - for (var j = 0; j < length; j++) { - var cell = row[j]; - if (!transpose[j]) - transpose[j] = []; - transpose[j][i] = cell; - } - } - return transpose; -}; - -exports.enumerate = function (array, start) { - array = exports.array.coerce(array); - if (exports.no(start)) - start = 0; - return exports.zip( - exports.range(start, start + array.length), - array - ); -}; - -// arithmetic, transitive, and logical operators - -exports.is = function (a, b) { - return a === b; -}; - -exports.eq = exports.operator('eq', 2, function (a, b, stack) { - if (!stack) - stack = []; - if (a === b) - return true; - if (typeof a !== typeof b) - return false; - if (exports.no(a)) - return exports.no(b); - if (typeof a == "date") - return a.valueOf() == b.valueOf(); - if (typeof a == "regexp") - return a.source == b.source && - a.global == b.global && - a.ignoreCase == b.ignoreCase && - a.multiline == b.multiline; - if (typeof a == "function") { - var caller = stack[stack.length - 1]; - return caller !== Object && - typeof caller != "undefined"; - } - if (exports.isArrayLike(a)) - return exports.array.eq( - a, b, - stack.concat([a.constructor]) - ); - if (typeof a == 'object') - return exports.object.eq( - a, b, - stack.concat([a.constructor]) - ); - return a == b; -}); - -exports.ne = exports.operator('ne', 2, function (a, b) { - return !exports.eq(a, b); -}); - -exports.lt = exports.operator('lt', 2, function (a, b) { - if (exports.no(a) != exports.no(b)) - return exports.no(a) > exports.no(b); - if (exports.isArrayLike(a) && exports.isArrayLike(b)) - return exports.array.lt(a, b); - return a < b; -}); - -exports.gt = exports.operator('gt', 2, function (a, b) { - return !(exports.lt(a, b) || exports.eq(a, b)); -}); - -exports.le = exports.operator(2, 'le', function (a, b) { - return exports.lt(a, b) || exports.eq(a, b); -}); - -exports.ge = exports.operator(2, 'ge', function (a, b) { - return !exports.lt(a, b); -}); - -exports.mul = exports.operator(2, 'mul', function (a, b) { - if (typeof a == "string") - return exports.string.mul(a, b); - return a * b; -}); - -/*** by - returns a `comparator` that compares - values based on the values resultant from - a given `relation`. - accepts a `relation` and an optional comparator. - - To sort a list of objects based on their - "a" key:: - - objects.sort(by(get("a"))) - - To get those in descending order:: - - objects.sort(by(get("a")), desc) - - `by` returns a comparison function that also tracks - the arguments you used to construct it. This permits - `sort` and `sorted` to perform a Schwartzian transform - which can increase the performance of the sort - by a factor of 2. -*/ -exports.by = function (relation) { - var compare = arguments[1]; - if (exports.no(compare)) - compare = exports.compare; - var comparator = function (a, b) { - a = relation(a); - b = relation(b); - return compare(a, b); - }; - comparator.by = relation; - comparator.compare = compare; - return comparator; -}; - -exports.compare = exports.operator(2, 'compare', function (a, b) { - if (exports.no(a) != exports.no(b)) - return exports.no(b) - exports.no(a); - if (typeof a == "number" && typeof b == "number") - return a - b; - return exports.eq(a, b) ? 0 : exports.lt(a, b) ? -1 : 1; -}); - -/*** sort - an in-place array sorter that uses a deep comparison - function by default (compare), and improves performance if - you provide a comparator returned by "by", using a - Schwartzian transform. -*/ -exports.sort = function (array, compare) { - if (exports.no(compare)) - compare = exports.compare; - if (compare.by) { - /* schwartzian transform */ - array.splice.apply( - array, - [0, array.length].concat( - array.map(function (value) { - return [compare.by(value), value]; - }).sort(function (a, b) { - return compare.compare(a[0], b[0]); - }).map(function (pair) { - return pair[1]; - }) - ) - ); - } else { - array.sort(compare); - } - return array; -}; - -/*** sorted - returns a sorted copy of an array using a deep - comparison function by default (compare), and - improves performance if you provide a comparator - returned by "by", using a Schwartzian transform. -*/ -exports.sorted = function (array, compare) { - return exports.sort(exports.array.copy(array), compare); -}; - -exports.hash = exports.operator(1, 'hash', function (object) { - return '' + object; -}); - -exports.unique = exports.operator(1, 'unique', function (array, eq, hash) { - var visited = {}; - if (!eq) eq = exports.eq; - if (!hash) hash = exports.hash; - return array.filter(function (value) { - var bucket = exports.getset(visited, hash(value), []); - var finds = bucket.filter(function (other) { - return eq(value, other); - }); - if (!finds.length) - bucket.push(value); - return !finds.length; - }); -}); - -// string - -exports.string = exports.operator(1, 'toString', function (object) { - return '' + object; -}); - -exports.string.mul = function (string, n) { - return exports.range(n).map(function () { - return string; - }).join(''); -}; - -/*** escape - escapes all characters of a string that are - special to JavaScript and many other languages. - Recognizes all of the relevant - control characters and formats all other - non-printable characters as Hex byte escape - sequences or Unicode escape sequences depending - on their size. - - Pass ``true`` as an optional second argument and - ``escape`` produces valid contents for escaped - JSON strings, wherein non-printable-characters are - all escaped with the Unicode ``\u`` notation. -*/ -/* more Steve Levithan flagrence */ -var escapeExpression = /[^ !#-[\]-~]/g; -/* from Doug Crockford's JSON library */ -var escapePatterns = { - '\b': '\\b', '\t': '\\t', - '\n': '\\n', '\f': '\\f', '\r': '\\r', - '"' : '\\"', '\\': '\\\\' -}; -exports.escape = function (value, strictJson) { - if (typeof value != "string") - throw new Error( - module.path + - "#escape: requires a string. got " + - exports.repr(value) - ); - return value.replace( - escapeExpression, - function (match) { - if (escapePatterns[match]) - return escapePatterns[match]; - match = match.charCodeAt(); - if (!strictJson && match < 256) - return "\\x" + exports.padBegin(match.toString(16), 2); - return '\\u' + exports.padBegin(match.toString(16), 4); - } - ); -}; - -/*** enquote - transforms a string into a string literal, escaping - all characters of a string that are special to - JavaScript and and some other languages. - - ``enquote`` uses double quotes to be JSON compatible. - - Pass ``true`` as an optional second argument to - be strictly JSON compliant, wherein all - non-printable-characters are represented with - Unicode escape sequences. -*/ -exports.enquote = function (value, strictJson) { - return '"' + exports.escape(value, strictJson) + '"'; -}; - -/*** expand - transforms tabs to an equivalent number of spaces. -*/ -// TODO special case for \r if it ever matters -exports.expand = function (str, tabLength) { - str = String(str); - tabLength = tabLength || 4; - var output = [], - tabLf = /[\t\n]/g, - lastLastIndex = 0, - lastLfIndex = 0, - charsAddedThisLine = 0, - tabOffset, match; - while (match = tabLf.exec(str)) { - if (match[0] == "\t") { - tabOffset = ( - tabLength - 1 - - ( - (match.index - lastLfIndex) + - charsAddedThisLine - ) % tabLength - ); - charsAddedThisLine += tabOffset; - output.push( - str.slice(lastLastIndex, match.index) + - exports.mul(" ", tabOffset + 1) - ); - } else if (match[0] === "\n") { - output.push(str.slice(lastLastIndex, tabLf.lastIndex)); - lastLfIndex = tabLf.lastIndex; - charsAddedThisLine = 0; - } - lastLastIndex = tabLf.lastIndex; - } - return output.join("") + str.slice(lastLastIndex); -}; - -var trimBeginExpression = /^\s\s*/g; -exports.trimBegin = function (value) { - return String(value).replace(trimBeginExpression, ""); -}; - -var trimEndExpression = /\s\s*$/g; -exports.trimEnd = function (value) { - return String(value).replace(trimEndExpression, ""); -}; - -exports.trim = function (value) { - return String(value).replace(trimBeginExpression, "").replace(trimEndExpression, ""); -}; - -/* generates padBegin and padEnd */ -var augmentor = function (augment) { - return function (value, length, pad) { - if (exports.no(pad)) pad = '0'; - if (exports.no(length)) length = 2; - value = String(value); - while (value.length < length) { - value = augment(value, pad); - } - return value; - }; -}; - -/*** padBegin - - accepts: - - a `String` or `Number` value - - a minimum length of the resultant `String`: - by default, 2 - - a pad string: by default, ``'0'``. - - returns a `String` of the value padded up to at least - the minimum length. adds the padding to the begining - side of the `String`. - -*/ -exports.padBegin = augmentor(function (value, pad) { - return pad + value; -}); - -/*** padEnd - - accepts: - - a `String` or `Number` value - - a minimum length of the resultant `String`: - by default, 2 - - a pad string: by default, ``'0'``. - - returns a `String` of the value padded up to at least - the minimum length. adds the padding to the end - side of the `String`. - -*/ -exports.padEnd = augmentor(function (value, pad) { - return value + pad; -}); - -/*** splitName - splits a string into an array of words from an original - string. -*/ -// thanks go to Steve Levithan for this regular expression -// that, in addition to splitting any normal-form identifier -// in any case convention, splits XMLHttpRequest into -// "XML", "Http", and "Request" -var splitNameExpression = /[a-z]+|[A-Z](?:[a-z]+|[A-Z]*(?![a-z]))|[.\d]+/g; -exports.splitName = function (value) { - return String(value).match(splitNameExpression); -}; - -/*** joinName - joins a list of words with a given delimiter - between alphanumeric words. -*/ -exports.joinName = function (delimiter, parts) { - if (exports.no(delimiter)) delimiter = '_'; - parts.unshift([]); - return parts.reduce(function (parts, part) { - if ( - part.match(/\d/) && - exports.len(parts) && parts[parts.length-1].match(/\d/) - ) { - return parts.concat([delimiter + part]); - } else { - return parts.concat([part]); - } - }).join(''); -}; - -/*** upper - converts a name to ``UPPER CASE`` using - a given delimiter between numeric words. - - see: - - `lower` - - `camel` - - `title` - -*/ -exports.upper = function (value, delimiter) { - if (exports.no(delimiter)) - return value.toUpperCase(); - return exports.splitName(value).map(function (part) { - return part.toUpperCase(); - }).join(delimiter); -}; - -/*** lower - converts a name to a ``lower case`` using - a given delimiter between numeric words. - - see: - - `upper` - - `camel` - - `title` - -*/ -exports.lower = function (value, delimiter) { - if (exports.no(delimiter)) - return String(value).toLowerCase(); - return exports.splitName(value).map(function (part) { - return part.toLowerCase(); - }).join(delimiter); -}; - -/*** camel - converts a name to ``camel Case`` using - a given delimiter between numeric words. - - see: - - `lower` - - `upper` - - `title` - -*/ -exports.camel = function (value, delimiter) { - return exports.joinName( - delimiter, - exports.mapApply( - exports.enumerate(exports.splitName(value)), - function (n, part) { - if (n) { - return ( - part.substring(0, 1).toUpperCase() + - part.substring(1).toLowerCase() - ); - } else { - return part.toLowerCase(); - } - } - ) - ); -}; - -/*** title - converts a name to ``Title Case`` using - a given delimiter between numeric words. - - see: - - `lower` - - `upper` - - `camel` - -*/ -exports.title = function (value, delimiter) { - return exports.joinName( - delimiter, - exports.splitName(value).map(function (part) { - return ( - part.substring(0, 1).toUpperCase() + - part.substring(1).toLowerCase() - ); - }) - ); -}; - diff --git a/test/mjsunit/common.js b/test/mjsunit/common.js index 29fe8f5d9d..fc56968e7b 100644 --- a/test/mjsunit/common.js +++ b/test/mjsunit/common.js @@ -13,4 +13,3 @@ process.mixin(exports, sys); exports.assert = require('assert'); exports.posix = require("posix"); exports.path = path; - diff --git a/test/mjsunit/test-assert.js b/test/mjsunit/test-assert.js new file mode 100644 index 0000000000..67b1a5e097 --- /dev/null +++ b/test/mjsunit/test-assert.js @@ -0,0 +1,127 @@ +process.mixin(require("./common")); + +//strangely meta, no? + +var a = require('assert'); + + +function makeBlock(f){ + var args = Array.prototype.slice.call(arguments,1); + return function(){ + return f.apply(this,args); + } +} + +assert.ok(a.AssertionError instanceof Error, "a.AssertionError instanceof Error") + +assert.throws(makeBlock(a.ok, false), a.AssertionError, "ok(false)"); +assert.doesNotThrow(makeBlock(a.ok, true), a.AssertionError, "ok(true)"); +assert.doesNotThrow(makeBlock(a.ok, "test"),"ok('test')"); + +assert.throws(makeBlock(a.equal, true, false), a.AssertionError, 'equal'); +assert.doesNotThrow(makeBlock(a.equal, null, null), 'equal'); +assert.doesNotThrow(makeBlock(a.equal, undefined, undefined), 'equal'); +assert.doesNotThrow(makeBlock(a.equal, null, undefined), 'equal'); +assert.doesNotThrow(makeBlock(a.equal, true, true), 'equal'); +assert.doesNotThrow(makeBlock(a.equal, 2, "2"), 'equal'); + +assert.doesNotThrow(makeBlock(a.notEqual, true, false), 'notEqual'); +assert.throws(makeBlock(a.notEqual, true, true), a.AssertionError, 'notEqual'); + +assert.throws(makeBlock(a.strictEqual, 2, "2"), a.AssertionError, 'strictEqual'); +assert.throws(makeBlock(a.strictEqual, null, undefined), a.AssertionError, 'strictEqual'); + +assert.doesNotThrow(makeBlock(a.notStrictEqual, 2, "2"), 'notStrictEqual'); + +//deepEquals joy! +//7.2 +assert.doesNotThrow(makeBlock(a.deepEqual, new Date(2000,3,14), new Date(2000,3,14)), 'deepEqual date'); +assert.throws( makeBlock(a.deepEqual, new Date(), new Date(2000,3,14)), a.AssertionError, 'deepEqual date'); +//7.3 +assert.doesNotThrow(makeBlock(a.deepEqual, 4, "4"), 'deepEqual == check'); +assert.doesNotThrow(makeBlock(a.deepEqual, true, 1), 'deepEqual == check'); +assert.throws( makeBlock(a.deepEqual, 4, "5"), a.AssertionError, 'deepEqual == check'); +//7.4 +// having the same number of owned properties && the same set of keys +assert.doesNotThrow(makeBlock(a.deepEqual, {a:4}, {a:4})); +assert.doesNotThrow(makeBlock(a.deepEqual, {a:4,b:"2"}, {a:4,b:"2"})); +assert.doesNotThrow(makeBlock(a.deepEqual, [4], ["4"])); +assert.throws( makeBlock(a.deepEqual, {a:4}, {a:4,b:true}), a.AssertionError); +assert.doesNotThrow(makeBlock(a.deepEqual, ["a"], {0:"a"})); +//(although not necessarily the same order), +assert.doesNotThrow(makeBlock(a.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" +assert.throws( makeBlock(a.deepEqual, Object.keys(a1), Object.keys(a2)), a.AssertionError); +assert.doesNotThrow(makeBlock(a.deepEqual, a1, a2)); + +// having an identical prototype property +var nbRoot = { + toString: function(){return this.first+' '+this.last;} +} +var nameBuilder = function(first,last){ + this.first = first; + this.last = last; + return this; +} +nameBuilder.prototype = nbRoot; +var nameBuilder2 = function(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'); + +assert.doesNotThrow(makeBlock(a.deepEqual, nb1, nb2)); + +nameBuilder2.prototype = Object; +nb2 = new nameBuilder2('Ryan','Dahl'); +assert.throws( makeBlock(a.deepEqual, nb1, nb2), a.AssertionError); + +//String literal + object blew up my implementation... +assert.throws( makeBlock(a.deepEqual, 'a', {}), a.AssertionError); + +//Testing the throwing +function thrower(errorConstructor){ + throw new errorConstructor('test'); +} +aethrow = makeBlock(thrower, a.AssertionError); +aethrow = makeBlock(thrower, a.AssertionError); +//the basic calls work +assert.throws( makeBlock(thrower, a.AssertionError), a.AssertionError, 'message'); +assert.throws( makeBlock(thrower, a.AssertionError), a.AssertionError); +assert.throws( makeBlock(thrower, a.AssertionError)); +//if not passing an error, catch all. +assert.throws( makeBlock(thrower, TypeError)); +//when passing a type, only catch errors of the appropriate type +var threw = false; +try{ + a.throws( makeBlock(thrower, TypeError), a.AssertionError); +}catch (e){ + threw = true; + assert.ok(e instanceof TypeError, 'type'); +} +assert.equal(true,threw,'a.throws with an explicit error is eating extra errors', a.AssertionError); +threw = false; +//doesNotThrow should pass through all errors +try{ + a.doesNotThrow(makeBlock(thrower, TypeError), a.AssertionError); +}catch(e){ + threw = true + assert.ok(e instanceof TypeError); +} +assert.equal(true,threw,'a.doesNotThrow with an explicit error is eating extra errors'); +//the key difference is that throwing our correct error makes an assertion error +try{ + a.doesNotThrow(makeBlock(thrower, TypeError), TypeError); +}catch(e){ + threw = true + assert.ok(e instanceof a.AssertionError); +} +assert.equal(true,threw,'a.doesNotThrow is not catching type matching errors');