diff --git a/README.md b/README.md index 82ad54b..a5b6ba2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a conversion of the [log4js](http://log4js.berlios.de/index.html) framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code -and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size. +and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size. It also enhances the default console logging functions (console.log, console.debug, etc) so that they use log4js and can be directed to a file, with log rolling etc - which is handy if you have some third party modules that use console.log but want that output included in your application log files. NOTE: since v0.2.0 require('log4js') returns a function, so you need to call that function in your code before you can use it. I've done this to make testing easier (allows dependency injection). @@ -12,7 +12,7 @@ npm install log4js ## tests -Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. I am slowly porting the previous tests from jspec (run those with `node tests.js`), since jspec is no longer maintained. +Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. ## usage @@ -20,6 +20,9 @@ Minimalist version: var log4js = require('log4js')(); var logger = log4js.getLogger(); logger.debug("Some debug messages"); +Even more minimalist version: + require('log4js')(); + console.debug("Some debug messages"); By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see: [2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages diff --git a/lib/log4js.js b/lib/log4js.js index 5ef7f4f..8e88217 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -45,12 +45,12 @@ * Website: http://log4js.berlios.de */ module.exports = function (fileSystem, standardOutput, configPaths) { - var fs = fileSystem || require('fs'), - standardOutput = standardOutput || console.log, - configPaths = configPaths || require.paths, - sys = require('sys'), - events = require('events'), + var events = require('events'), path = require('path'), + sys = require('sys'), + fs = fileSystem || require('fs'), + standardOutput = standardOutput || sys.puts, + configPaths = configPaths || require.paths, DEFAULT_CATEGORY = '[default]', ALL_CATEGORIES = '[all]', loggers = {}, @@ -279,9 +279,13 @@ module.exports = function (fileSystem, standardOutput, configPaths) { this.startTime = new Date(); this.categoryName = categoryName; this.message = message; - this.exception = exception; this.level = level; this.logger = logger; + if (exception && exception.message && exception.name) { + this.exception = exception; + } else if (exception) { + this.exception = new Error(sys.inspect(exception)); + } } /** @@ -614,8 +618,26 @@ module.exports = function (fileSystem, standardOutput, configPaths) { }; + function replaceConsole(logger) { + function replaceWith (fn) { + return function() { + fn.apply(logger, arguments); + } + } + + console.log = replaceWith(logger.info); + console.debug = replaceWith(logger.debug); + console.trace = replaceWith(logger.trace); + console.info = replaceWith(logger.info); + console.warn = replaceWith(logger.warn); + console.error = replaceWith(logger.error); + + } + //set ourselves up if we can find a default log4js.json configure(findConfiguration()); + //replace console.log, etc with log4js versions + replaceConsole(getLogger("console")); return { getLogger: getLogger, diff --git a/package.json b/package.json index e5d10f8..b2eb3e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.2.0", + "version": "0.2.2", "description": "Port of Log4js to work with node.", "keywords": [ "logging", diff --git a/spec/lib/images/bg.png b/spec/lib/images/bg.png deleted file mode 100644 index 947804f..0000000 Binary files a/spec/lib/images/bg.png and /dev/null differ diff --git a/spec/lib/images/hr.png b/spec/lib/images/hr.png deleted file mode 100644 index 4a94d12..0000000 Binary files a/spec/lib/images/hr.png and /dev/null differ diff --git a/spec/lib/images/loading.gif b/spec/lib/images/loading.gif deleted file mode 100644 index c69e937..0000000 Binary files a/spec/lib/images/loading.gif and /dev/null differ diff --git a/spec/lib/images/sprites.bg.png b/spec/lib/images/sprites.bg.png deleted file mode 100644 index dc8790f..0000000 Binary files a/spec/lib/images/sprites.bg.png and /dev/null differ diff --git a/spec/lib/images/sprites.png b/spec/lib/images/sprites.png deleted file mode 100644 index 010b98e..0000000 Binary files a/spec/lib/images/sprites.png and /dev/null differ diff --git a/spec/lib/images/vr.png b/spec/lib/images/vr.png deleted file mode 100644 index b2e7617..0000000 Binary files a/spec/lib/images/vr.png and /dev/null differ diff --git a/spec/lib/jspec.css b/spec/lib/jspec.css deleted file mode 100644 index 629d41c..0000000 --- a/spec/lib/jspec.css +++ /dev/null @@ -1,149 +0,0 @@ -body.jspec { - margin: 45px 0; - font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; - background: #efefef url(images/bg.png) top left repeat-x; - text-align: center; -} -#jspec { - margin: 0 auto; - padding-top: 30px; - width: 1008px; - background: url(images/vr.png) top left repeat-y; - text-align: left; -} -#jspec-top { - position: relative; - margin: 0 auto; - width: 1008px; - height: 40px; - background: url(images/sprites.bg.png) top left no-repeat; -} -#jspec-bottom { - margin: 0 auto; - width: 1008px; - height: 15px; - background: url(images/sprites.bg.png) bottom left no-repeat; -} -#jspec .loading { - margin-top: -45px; - width: 1008px; - height: 80px; - background: url(images/loading.gif) 50% 50% no-repeat; -} -#jspec-title { - position: absolute; - top: 15px; - left: 20px; - width: 160px; - font-size: 22px; - font-weight: normal; - background: url(images/sprites.png) 0 -126px no-repeat; - text-align: center; -} -#jspec-title em { - font-size: 10px; - font-style: normal; - color: #BCC8D1; -} -#jspec-report * { - margin: 0; - padding: 0; - background: none; - border: none; -} -#jspec-report { - padding: 15px 40px; - font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; - color: #7B8D9B; -} -#jspec-report.has-failures { - padding-bottom: 30px; -} -#jspec-report .hidden { - display: none; -} -#jspec-report .heading { - margin-bottom: 15px; -} -#jspec-report .heading span { - padding-right: 10px; -} -#jspec-report .heading .passes em { - color: #0ea0eb; -} -#jspec-report .heading .failures em { - color: #FA1616; -} -#jspec-report table { - font-size: 11px; - border-collapse: collapse; -} -#jspec-report td { - padding: 8px; - text-indent: 30px; - color: #7B8D9B; -} -#jspec-report tr.body { - display: none; -} -#jspec-report tr.body pre { - margin: 0; - padding: 0 0 5px 25px; -} -#jspec-report tr.even:hover + tr.body, -#jspec-report tr.odd:hover + tr.body { - display: block; -} -#jspec-report tr td:first-child em { - display: block; - clear: both; - font-style: normal; - font-weight: normal; - color: #7B8D9B; -} -#jspec-report tr.even:hover, -#jspec-report tr.odd:hover { - text-shadow: 1px 1px 1px #fff; - background: #F2F5F7; -} -#jspec-report td + td { - padding-right: 0; - width: 15px; -} -#jspec-report td.pass { - background: url(images/sprites.png) 3px -7px no-repeat; -} -#jspec-report td.fail { - background: url(images/sprites.png) 3px -158px no-repeat; - font-weight: bold; - color: #FC0D0D; -} -#jspec-report td.requires-implementation { - background: url(images/sprites.png) 3px -333px no-repeat; -} -#jspec-report tr.description td { - margin-top: 25px; - padding-top: 25px; - font-size: 12px; - font-weight: bold; - text-indent: 0; - color: #1a1a1a; -} -#jspec-report tr.description:first-child td { - border-top: none; -} -#jspec-report .assertion { - display: block; - float: left; - margin: 0 0 0 1px; - padding: 0; - width: 1px; - height: 5px; - background: #7B8D9B; -} -#jspec-report .assertion.failed { - background: red; -} -.jspec-sandbox { - display: none; -} \ No newline at end of file diff --git a/spec/lib/jspec.growl.js b/spec/lib/jspec.growl.js deleted file mode 100644 index a150257..0000000 --- a/spec/lib/jspec.growl.js +++ /dev/null @@ -1,115 +0,0 @@ - -// JSpec - Growl - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - Growl = { - - // --- Version - - version: '1.0.0', - - /** - * Execute the given _cmd_, returning an array of lines from stdout. - * - * Examples: - * - * Growl.exec('growlnotify', '-m', msg) - * - * @param {string ...} cmd - * @return {array} - * @api public - */ - - exec: function(cmd) { - var lines = [], line - with (JavaImporter(java.lang, java.io)) { - var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments)) - var stream = new DataInputStream(proccess.getInputStream()) - while (line = stream.readLine()) - lines.push(line + '') - stream.close() - } - return lines - }, - - /** - * Return the extension of the given _path_ or null. - * - * @param {string} path - * @return {string} - * @api private - */ - - extname: function(path) { - return path.lastIndexOf('.') != -1 ? - path.slice(path.lastIndexOf('.') + 1, path.length) : - null - }, - - /** - * Version of the 'growlnotify' binary. - * - * @return {string} - * @api private - */ - - binVersion: function() { - try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {} - }, - - /** - * Send growl notification _msg_ with _options_. - * - * Options: - * - * - title Notification title - * - sticky Make the notification stick (defaults to false) - * - name Application name (defaults to growlnotify) - * - image - * - path to an icon sets --iconpath - * - path to an image sets --image - * - capitalized word sets --appIcon - * - filename uses extname as --icon - * - otherwise treated as --icon - * - * Examples: - * - * Growl.notify('New email') - * Growl.notify('5 new emails', { title: 'Thunderbird' }) - * - * @param {string} msg - * @param {options} hash - * @api public - */ - - notify: function(msg, options) { - options = options || {} - var args = ['growlnotify', '-m', msg] - if (!this.binVersion()) throw new Error('growlnotify executable is required') - if (image = options.image) { - var flag, ext = this.extname(image) - flag = flag || ext == 'icns' && 'iconpath' - flag = flag || /^[A-Z]/.test(image) && 'appIcon' - flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image' - flag = flag || ext && (image = ext) && 'icon' - flag = flag || 'icon' - args.push('--' + flag, image) - } - if (options.sticky) args.push('--sticky') - if (options.name) args.push('--name', options.name) - if (options.title) args.push(options.title) - this.exec.apply(this, args) - } - } - - JSpec.include({ - name: 'Growl', - reporting: function(options){ - var stats = JSpec.stats - if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'}) - else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' }) - } - }) - -})() \ No newline at end of file diff --git a/spec/lib/jspec.jquery.js b/spec/lib/jspec.jquery.js deleted file mode 100644 index 3c1f784..0000000 --- a/spec/lib/jspec.jquery.js +++ /dev/null @@ -1,71 +0,0 @@ - -// JSpec - jQuery - Copyright TJ Holowaychuk (MIT Licensed) - -JSpec -.requires('jQuery', 'when using jspec.jquery.js') -.include({ - name: 'jQuery', - - // --- Initialize - - init : function() { - jQuery.ajaxSetup({ async: false }) - }, - - // --- Utilities - - utilities : { - element: jQuery, - elements: jQuery, - sandbox : function() { - return jQuery('
') - } - }, - - // --- Matchers - - matchers : { - have_tag : "jQuery(expected, actual).length == 1", - have_one : "alias have_tag", - have_tags : "jQuery(expected, actual).length > 1", - have_many : "alias have_tags", - have_child : "jQuery(actual).children(expected).length == 1", - have_children : "jQuery(actual).children(expected).length > 1", - have_text : "jQuery(actual).text() == expected", - have_value : "jQuery(actual).val() == expected", - be_enabled : "!jQuery(actual).attr('disabled')", - have_class : "jQuery(actual).hasClass(expected)", - - be_visible : function(actual) { - return jQuery(actual).css('display') != 'none' && - jQuery(actual).css('visibility') != 'hidden' && - jQuery(actual).attr('type') != 'hidden' - }, - - be_hidden : function(actual) { - return !JSpec.does(actual, 'be_visible') - }, - - have_classes : function(actual) { - return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){ - return !JSpec.does(actual, 'have_class', arg) - }) - }, - - have_attr : function(actual, attr, value) { - return value ? jQuery(actual).attr(attr) == value: - jQuery(actual).attr(attr) - }, - - 'be disabled selected checked' : function(attr) { - return 'jQuery(actual).attr("' + attr + '")' - }, - - 'have type id title alt href src sel rev name target' : function(attr) { - return function(actual, value) { - return JSpec.does(actual, 'have_attr', attr, value) - } - } - } -}) - diff --git a/spec/lib/jspec.js b/spec/lib/jspec.js deleted file mode 100644 index 742987c..0000000 --- a/spec/lib/jspec.js +++ /dev/null @@ -1,1773 +0,0 @@ - -// JSpec - Core - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - JSpec = { - version : '3.1.3', - assert : true, - cache : {}, - suites : [], - modules : [], - allSuites : [], - matchers : {}, - stubbed : [], - options : {}, - request : 'XMLHttpRequest' in this ? XMLHttpRequest : null, - stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 }, - - /** - * Default context in which bodies are evaluated. - * - * Replace context simply by setting JSpec.context - * to your own like below: - * - * JSpec.context = { foo : 'bar' } - * - * Contexts can be changed within any body, this can be useful - * in order to provide specific helper methods to specific suites. - * - * To reset (usually in after hook) simply set to null like below: - * - * JSpec.context = null - * - */ - - defaultContext : { - - /** - * Return an object used for proxy assertions. - * This object is used to indicate that an object - * should be an instance of _object_, not the constructor - * itself. - * - * @param {function} constructor - * @return {hash} - * @api public - */ - - an_instance_of : function(constructor) { - return { an_instance_of : constructor } - }, - - /** - * Load fixture at _path_. - * - * Fixtures are resolved as: - * - * - - * - .html - * - * @param {string} path - * @return {string} - * @api public - */ - - fixture : function(path) { - if (JSpec.cache[path]) return JSpec.cache[path] - return JSpec.cache[path] = - JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) || - JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html') - } - }, - - // --- Objects - - reporters : { - - /** - * Report to server. - * - * Options: - * - uri specific uri to report to. - * - verbose weither or not to output messages - * - failuresOnly output failure messages only - * - * @api public - */ - - Server : function(results, options) { - var uri = options.uri || 'http://' + window.location.host + '/results' - JSpec.post(uri, { - stats: JSpec.stats, - options: options, - results: map(results.allSuites, function(suite) { - if (suite.hasSpecs()) - return { - description: suite.description, - specs: map(suite.specs, function(spec) { - return { - description: spec.description, - message: !spec.passed() ? spec.failure().message : null, - status: spec.requiresImplementation() ? 'pending' : - spec.passed() ? 'pass' : - 'fail', - assertions: map(spec.assertions, function(assertion){ - return { - passed: assertion.passed - } - }) - } - }) - } - }) - }) - if ('close' in main) main.close() - }, - - /** - * Default reporter, outputting to the DOM. - * - * Options: - * - reportToId id of element to output reports to, defaults to 'jspec' - * - failuresOnly displays only suites with failing specs - * - * @api public - */ - - DOM : function(results, options) { - var id = option('reportToId') || 'jspec' - var report = document.getElementById(id) - var failuresOnly = option('failuresOnly') - var classes = results.stats.failures ? 'has-failures' : '' - if (!report) throw 'JSpec requires the element #' + id + ' to output its reports' - - function bodyContents(body) { - return JSpec. - escape(JSpec.contentsOf(body)). - replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }). - replace(/\r\n|\r|\n/gm, '
') - } - - report.innerHTML = '
\ - Passes: ' + results.stats.passes + ' \ - Failures: ' + results.stats.failures + ' \ - Duration: ' + results.duration + ' ms \ -
' + map(results.allSuites, function(suite) { - var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran - if (displaySuite && suite.hasSpecs()) - return '' + - map(suite.specs, function(i, spec) { - return '' + - (spec.requiresImplementation() ? - '' : - (spec.passed() && !failuresOnly) ? - '' : - !spec.passed() ? - '' : - '') + - '' - }).join('') + '' - }).join('') + '
' + escape(suite.description) + '
' + escape(spec.description) + '' + escape(spec.description)+ '' + spec.assertionsGraph() + '' + escape(spec.description) + - map(spec.failures(), function(a){ return '' + escape(a.message) + '' }).join('') + - '' + spec.assertionsGraph() + '
' + bodyContents(spec.body) + '
' - }, - - /** - * Terminal reporter. - * - * @api public - */ - - Terminal : function(results, options) { - failuresOnly = option('failuresOnly') - print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') + - color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + - color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n") - - function indent(string) { - return string.replace(/^(.)/gm, ' $1') - } - - each(results.allSuites, function(suite) { - var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran - if (displaySuite && suite.hasSpecs()) { - print(color(' ' + suite.description, 'bold')) - each(suite.specs, function(spec){ - var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){ - return graph + color('.', assertion.passed ? 'green' : 'red') - }) - if (spec.requiresImplementation()) - print(color(' ' + spec.description, 'blue') + assertionsGraph) - else if (spec.passed() && !failuresOnly) - print(color(' ' + spec.description, 'green') + assertionsGraph) - else if (!spec.passed()) - print(color(' ' + spec.description, 'red') + assertionsGraph + - "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n") - }) - print("") - } - }) - - quit(results.stats.failures) - }, - - /** - * Console reporter. - * - * @api public - */ - - Console : function(results, options) { - console.log('') - console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures) - each(results.allSuites, function(suite) { - if (suite.ran) { - console.group(suite.description) - each(suite.specs, function(spec){ - var assertionCount = spec.assertions.length + ':' - if (spec.requiresImplementation()) - console.warn(spec.description) - else if (spec.passed()) - console.log(assertionCount + ' ' + spec.description) - else - console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message) - }) - console.groupEnd() - } - }) - } - }, - - Assertion : function(matcher, actual, expected, negate) { - extend(this, { - message: '', - passed: false, - actual: actual, - negate: negate, - matcher: matcher, - expected: expected, - - // Report assertion results - - report : function() { - if (JSpec.assert) - this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++ - return this - }, - - // Run the assertion - - run : function() { - // TODO: remove unshifting - expected.unshift(actual) - this.result = matcher.match.apply(this, expected) - this.passed = negate ? !this.result : this.result - if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name) - return this - } - }) - }, - - ProxyAssertion : function(object, method, times, negate) { - var self = this - var old = object[method] - - // Proxy - - object[method] = function(){ - args = toArray(arguments) - result = old.apply(object, args) - self.calls.push({ args : args, result : result }) - return result - } - - // Times - - this.times = { - once : 1, - twice : 2 - }[times] || times || 1 - - extend(this, { - calls: [], - message: '', - defer: true, - passed: false, - negate: negate, - object: object, - method: method, - - // Proxy return value - - and_return : function(result) { - this.expectedResult = result - return this - }, - - // Proxy arguments passed - - with_args : function() { - this.expectedArgs = toArray(arguments) - return this - }, - - // Check if any calls have failing results - - anyResultsFail : function() { - return any(this.calls, function(call){ - return self.expectedResult.an_instance_of ? - call.result.constructor != self.expectedResult.an_instance_of: - !equal(self.expectedResult, call.result) - }) - }, - - // Check if any calls have passing results - - anyResultsPass : function() { - return any(this.calls, function(call){ - return self.expectedResult.an_instance_of ? - call.result.constructor == self.expectedResult.an_instance_of: - equal(self.expectedResult, call.result) - }) - }, - - // Return the passing result - - passingResult : function() { - return this.anyResultsPass().result - }, - - // Return the failing result - - failingResult : function() { - return this.anyResultsFail().result - }, - - // Check if any arguments fail - - anyArgsFail : function() { - return any(this.calls, function(call){ - return any(self.expectedArgs, function(i, arg){ - if (arg == null) return call.args[i] == null - return arg.an_instance_of ? - call.args[i].constructor != arg.an_instance_of: - !equal(arg, call.args[i]) - - }) - }) - }, - - // Check if any arguments pass - - anyArgsPass : function() { - return any(this.calls, function(call){ - return any(self.expectedArgs, function(i, arg){ - return arg.an_instance_of ? - call.args[i].constructor == arg.an_instance_of: - equal(arg, call.args[i]) - - }) - }) - }, - - // Return the passing args - - passingArgs : function() { - return this.anyArgsPass().args - }, - - // Return the failing args - - failingArgs : function() { - return this.anyArgsFail().args - }, - - // Report assertion results - - report : function() { - if (JSpec.assert) - this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures - return this - }, - - // Run the assertion - - run : function() { - var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' ) - - function times(n) { - return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n] - } - - if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail())) - this.message = methodString + ' to return ' + puts(this.expectedResult) + - ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult())) - - if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail())) - this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) + - ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs())) - - if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times) - this.message = methodString + ' to be called ' + times(this.times) + - ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length)) - - if (!this.message.length) - this.passed = true - - return this - } - }) - }, - - /** - * Specification Suite block object. - * - * @param {string} description - * @param {function} body - * @api private - */ - - Suite : function(description, body) { - var self = this - extend(this, { - body: body, - description: description, - suites: [], - specs: [], - ran: false, - hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] }, - - // Add a spec to the suite - - addSpec : function(description, body) { - var spec = new JSpec.Spec(description, body) - this.specs.push(spec) - JSpec.stats.specs++ // TODO: abstract - spec.suite = this - }, - - // Add a hook to the suite - - addHook : function(hook, body) { - this.hooks[hook].push(body) - }, - - // Add a nested suite - - addSuite : function(description, body) { - var suite = new JSpec.Suite(description, body) - JSpec.allSuites.push(suite) - suite.name = suite.description - suite.description = this.description + ' ' + suite.description - this.suites.push(suite) - suite.suite = this - }, - - // Invoke a hook in context to this suite - - hook : function(hook) { - if (this.suite) this.suite.hook(hook) - each(this.hooks[hook], function(body) { - JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ") - }) - }, - - // Check if nested suites are present - - hasSuites : function() { - return this.suites.length - }, - - // Check if this suite has specs - - hasSpecs : function() { - return this.specs.length - }, - - // Check if the entire suite passed - - passed : function() { - return !any(this.specs, function(spec){ - return !spec.passed() - }) - } - }) - }, - - /** - * Specification block object. - * - * @param {string} description - * @param {function} body - * @api private - */ - - Spec : function(description, body) { - extend(this, { - body: body, - description: description, - assertions: [], - - // Add passing assertion - - pass : function(message) { - this.assertions.push({ passed: true, message: message }) - if (JSpec.assert) ++JSpec.stats.passes - }, - - // Add failing assertion - - fail : function(message) { - this.assertions.push({ passed: false, message: message }) - if (JSpec.assert) ++JSpec.stats.failures - }, - - // Run deferred assertions - - runDeferredAssertions : function() { - each(this.assertions, function(assertion){ - if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion) - }) - }, - - // Find first failing assertion - - failure : function() { - return find(this.assertions, function(assertion){ - return !assertion.passed - }) - }, - - // Find all failing assertions - - failures : function() { - return select(this.assertions, function(assertion){ - return !assertion.passed - }) - }, - - // Weither or not the spec passed - - passed : function() { - return !this.failure() - }, - - // Weither or not the spec requires implementation (no assertions) - - requiresImplementation : function() { - return this.assertions.length == 0 - }, - - // Sprite based assertions graph - - assertionsGraph : function() { - return map(this.assertions, function(assertion){ - return '' - }).join('') - } - }) - }, - - Module : function(methods) { - extend(this, methods) - }, - - JSON : { - - /** - * Generic sequences. - */ - - meta : { - '\b' : '\\b', - '\t' : '\\t', - '\n' : '\\n', - '\f' : '\\f', - '\r' : '\\r', - '"' : '\\"', - '\\' : '\\\\' - }, - - /** - * Escapable sequences. - */ - - escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - - /** - * JSON encode _object_. - * - * @param {mixed} object - * @return {string} - * @api private - */ - - encode : function(object) { - var self = this - if (object == undefined || object == null) return 'null' - if (object === true) return 'true' - if (object === false) return 'false' - switch (typeof object) { - case 'number': return object - case 'string': return this.escapable.test(object) ? - '"' + object.replace(this.escapable, function (a) { - return typeof self.meta[a] === 'string' ? self.meta[a] : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) - }) + '"' : - '"' + object + '"' - case 'object': - if (object.constructor == Array) - return '[' + map(object, function(val){ - return self.encode(val) - }).join(', ') + ']' - else if (object) - return '{' + map(object, function(key, val){ - return self.encode(key) + ':' + self.encode(val) - }).join(', ') + '}' - } - return 'null' - } - }, - - // --- DSLs - - DSLs : { - snake : { - expect : function(actual){ - return JSpec.expect(actual) - }, - - describe : function(description, body) { - return JSpec.currentSuite.addSuite(description, body) - }, - - it : function(description, body) { - return JSpec.currentSuite.addSpec(description, body) - }, - - before : function(body) { - return JSpec.currentSuite.addHook('before', body) - }, - - after : function(body) { - return JSpec.currentSuite.addHook('after', body) - }, - - before_each : function(body) { - return JSpec.currentSuite.addHook('before_each', body) - }, - - after_each : function(body) { - return JSpec.currentSuite.addHook('after_each', body) - }, - - should_behave_like : function(description) { - return JSpec.shareBehaviorsOf(description) - } - } - }, - - // --- Methods - - /** - * Check if _value_ is 'stop'. For use as a - * utility callback function. - * - * @param {mixed} value - * @return {bool} - * @api public - */ - - haveStopped : function(value) { - return value === 'stop' - }, - - /** - * Include _object_ which may be a hash or Module instance. - * - * @param {hash, Module} object - * @return {JSpec} - * @api public - */ - - include : function(object) { - var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object) - this.modules.push(module) - if ('init' in module) module.init() - if ('utilities' in module) extend(this.defaultContext, module.utilities) - if ('matchers' in module) this.addMatchers(module.matchers) - if ('reporters' in module) extend(this.reporters, module.reporters) - if ('DSLs' in module) - each(module.DSLs, function(name, methods){ - JSpec.DSLs[name] = JSpec.DSLs[name] || {} - extend(JSpec.DSLs[name], methods) - }) - return this - }, - - /** - * Add a module hook _name_, which is immediately - * called per module with the _args_ given. An array of - * hook return values is returned. - * - * @param {name} string - * @param {...} args - * @return {array} - * @api private - */ - - hook : function(name, args) { - args = toArray(arguments, 1) - return inject(JSpec.modules, [], function(results, module){ - if (typeof module[name] == 'function') - results.push(JSpec.evalHook(module, name, args)) - }) - }, - - /** - * Eval _module_ hook _name_ with _args_. Evaluates in context - * to the module itself, JSpec, and JSpec.context. - * - * @param {Module} module - * @param {string} name - * @param {array} args - * @return {mixed} - * @api private - */ - - evalHook : function(module, name, args) { - hook('evaluatingHookBody', module, name) - try { return module[name].apply(module, args) } - catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) } - }, - - /** - * Same as hook() however accepts only one _arg_ which is - * considered immutable. This function passes the arg - * to the first module, then passes the return value of the last - * module called, to the following module. - * - * @param {string} name - * @param {mixed} arg - * @return {mixed} - * @api private - */ - - hookImmutable : function(name, arg) { - return inject(JSpec.modules, arg, function(result, module){ - if (typeof module[name] == 'function') - return JSpec.evalHook(module, name, [result]) - }) - }, - - /** - * Find a suite by its description or name. - * - * @param {string} description - * @return {Suite} - * @api private - */ - - findSuite : function(description) { - return find(this.allSuites, function(suite){ - return suite.name == description || suite.description == description - }) - }, - - /** - * Share behaviors (specs) of the given suite with - * the current suite. - * - * @param {string} description - * @api public - */ - - shareBehaviorsOf : function(description) { - if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite) - else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name' - }, - - /** - * Copy specs from one suite to another. - * - * @param {Suite} fromSuite - * @param {Suite} toSuite - * @api public - */ - - copySpecs : function(fromSuite, toSuite) { - each(fromSuite.specs, function(spec){ - spec.assertions = [] - toSuite.specs.push(spec) - }) - }, - - /** - * Convert arguments to an array. - * - * @param {object} arguments - * @param {int} offset - * @return {array} - * @api public - */ - - toArray : function(arguments, offset) { - return Array.prototype.slice.call(arguments, offset || 0) - }, - - /** - * Return ANSI-escaped colored string. - * - * @param {string} string - * @param {string} color - * @return {string} - * @api public - */ - - color : function(string, color) { - return "\u001B[" + { - bold : 1, - black : 30, - red : 31, - green : 32, - yellow : 33, - blue : 34, - magenta : 35, - cyan : 36, - white : 37 - }[color] + 'm' + string + "\u001B[0m" - }, - - /** - * Default matcher message callback. - * - * @api private - */ - - defaultMatcherMessage : function(actual, expected, negate, name) { - return 'expected ' + puts(actual) + ' to ' + - (negate ? 'not ' : '') + - name.replace(/_/g, ' ') + - ' ' + (expected.length > 1 ? - puts.apply(this, expected.slice(1)) : - '') - }, - - /** - * Normalize a matcher message. - * - * When no messge callback is present the defaultMatcherMessage - * will be assigned, will suffice for most matchers. - * - * @param {hash} matcher - * @return {hash} - * @api public - */ - - normalizeMatcherMessage : function(matcher) { - if (typeof matcher.message != 'function') - matcher.message = this.defaultMatcherMessage - return matcher - }, - - /** - * Normalize a matcher body - * - * This process allows the following conversions until - * the matcher is in its final normalized hash state. - * - * - '==' becomes 'actual == expected' - * - 'actual == expected' becomes 'return actual == expected' - * - function(actual, expected) { return actual == expected } becomes - * { match : function(actual, expected) { return actual == expected }} - * - * @param {mixed} body - * @return {hash} - * @api public - */ - - normalizeMatcherBody : function(body) { - switch (body.constructor) { - case String: - if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)] - if (body.length < 4) body = 'actual ' + body + ' expected' - return { match: function(actual, expected) { return eval(body) }} - - case Function: - return { match: body } - - default: - return body - } - }, - - /** - * Get option value. This method first checks if - * the option key has been set via the query string, - * otherwise returning the options hash value. - * - * @param {string} key - * @return {mixed} - * @api public - */ - - option : function(key) { - return (value = query(key)) !== null ? value : - JSpec.options[key] || null - }, - - /** - * Check if object _a_, is equal to object _b_. - * - * @param {object} a - * @param {object} b - * @return {bool} - * @api private - */ - - equal: function(a, b) { - if (typeof a != typeof b) return - if (a === b) return true - if (a instanceof RegExp) - return a.toString() === b.toString() - if (a instanceof Date) - return Number(a) === Number(b) - if (typeof a != 'object') return - if (a.length !== undefined) - if (a.length !== b.length) return - else - for (var i = 0, len = a.length; i < len; ++i) - if (!equal(a[i], b[i])) - return - for (var key in a) - if (!equal(a[key], b[key])) - return - return true - }, - - /** - * Return last element of an array. - * - * @param {array} array - * @return {object} - * @api public - */ - - last : function(array) { - return array[array.length - 1] - }, - - /** - * Convert object(s) to a print-friend string. - * - * @param {...} object - * @return {string} - * @api public - */ - - puts : function(object) { - if (arguments.length > 1) - return map(toArray(arguments), function(arg){ - return puts(arg) - }).join(', ') - if (object === undefined) return 'undefined' - if (object === null) return 'null' - if (object === true) return 'true' - if (object === false) return 'false' - if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name - if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector) - if (object.jquery) return object.get(0).outerHTML - if (object.nodeName) return object.outerHTML - switch (object.constructor) { - case Function: return object.name || object - case String: - return '"' + object - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + '"' - case Array: - return inject(object, '[', function(b, v){ - return b + ', ' + puts(v) - }).replace('[,', '[') + ' ]' - case Object: - object.__hit__ = true - return inject(object, '{', function(b, k, v) { - if (k == '__hit__') return b - return b + ', ' + k + ': ' + (v && v.__hit__ ? '' : puts(v)) - }).replace('{,', '{') + ' }' - default: - return object.toString() - } - }, - - /** - * Escape HTML. - * - * @param {string} html - * @return {string} - * @api public - */ - - escape : function(html) { - return html.toString() - .replace(/&/gmi, '&') - .replace(/"/gmi, '"') - .replace(/>/gmi, '>') - .replace(/ current) while (++current <= end) values.push(current) - else while (--current >= end) values.push(current) - return '[' + values + ']' - }, - - /** - * Report on the results. - * - * @api public - */ - - report : function() { - this.duration = Number(new Date) - this.start - hook('reporting', JSpec.options) - new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options) - }, - - /** - * Run the spec suites. Options are merged - * with JSpec options when present. - * - * @param {hash} options - * @return {JSpec} - * @api public - */ - - run : function(options) { - if (any(hook('running'), haveStopped)) return this - if (options) extend(this.options, options) - this.start = Number(new Date) - each(this.suites, function(suite) { JSpec.runSuite(suite) }) - return this - }, - - /** - * Run a suite. - * - * @param {Suite} suite - * @api public - */ - - runSuite : function(suite) { - this.currentSuite = suite - this.evalBody(suite.body) - suite.ran = true - hook('beforeSuite', suite), suite.hook('before') - each(suite.specs, function(spec) { - hook('beforeSpec', spec) - suite.hook('before_each') - JSpec.runSpec(spec) - hook('afterSpec', spec) - suite.hook('after_each') - }) - if (suite.hasSuites()) { - each(suite.suites, function(suite) { - JSpec.runSuite(suite) - }) - } - hook('afterSuite', suite), suite.hook('after') - this.stats.suitesFinished++ - }, - - /** - * Report a failure for the current spec. - * - * @param {string} message - * @api public - */ - - fail : function(message) { - JSpec.currentSpec.fail(message) - }, - - /** - * Report a passing assertion for the current spec. - * - * @param {string} message - * @api public - */ - - pass : function(message) { - JSpec.currentSpec.pass(message) - }, - - /** - * Run a spec. - * - * @param {Spec} spec - * @api public - */ - - runSpec : function(spec) { - this.currentSpec = spec - try { this.evalBody(spec.body) } - catch (e) { fail(e) } - spec.runDeferredAssertions() - destub() - this.stats.specsFinished++ - this.stats.assertions += spec.assertions.length - }, - - /** - * Require a dependency, with optional message. - * - * @param {string} dependency - * @param {string} message (optional) - * @return {JSpec} - * @api public - */ - - requires : function(dependency, message) { - hook('requiring', dependency, message) - try { eval(dependency) } - catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message } - return this - }, - - /** - * Query against the current query strings keys - * or the queryString specified. - * - * @param {string} key - * @param {string} queryString - * @return {string, null} - * @api private - */ - - query : function(key, queryString) { - var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1) - return inject(queryString.split('&'), null, function(value, pair){ - parts = pair.split('=') - return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value - }) - }, - - /** - * Throw a JSpec related error. - * - * @param {string} message - * @param {Exception} e - * @api public - */ - - error : function(message, e) { - throw (message ? message : '') + e.toString() + - (e.line ? ' near line ' + e.line : '') - }, - - /** - * Ad-hoc POST request for JSpec server usage. - * - * @param {string} uri - * @param {string} data - * @api private - */ - - post : function(uri, data) { - if (any(hook('posting', uri, data), haveStopped)) return - var request = this.xhr() - request.open('POST', uri, false) - request.setRequestHeader('Content-Type', 'application/json') - request.send(JSpec.JSON.encode(data)) - }, - - /** - * Instantiate an XMLHttpRequest. - * - * Here we utilize IE's lame ActiveXObjects first which - * allow IE access serve files via the file: protocol, otherwise - * we then default to XMLHttpRequest. - * - * @return {XMLHttpRequest, ActiveXObject} - * @api private - */ - - xhr : function() { - return this.ieXhr() || new JSpec.request - }, - - /** - * Return Microsoft piece of crap ActiveXObject. - * - * @return {ActiveXObject} - * @api public - */ - - ieXhr : function() { - function object(str) { - try { return new ActiveXObject(str) } catch(e) {} - } - return object('Msxml2.XMLHTTP.6.0') || - object('Msxml2.XMLHTTP.3.0') || - object('Msxml2.XMLHTTP') || - object('Microsoft.XMLHTTP') - }, - - /** - * Check for HTTP request support. - * - * @return {bool} - * @api private - */ - - hasXhr : function() { - return JSpec.request || 'ActiveXObject' in main - }, - - /** - * Try loading _file_ returning the contents - * string or null. Chain to locate / read a file. - * - * @param {string} file - * @return {string} - * @api public - */ - - tryLoading : function(file) { - try { return JSpec.load(file) } catch (e) {} - }, - - /** - * Load a _file_'s contents. - * - * @param {string} file - * @param {function} callback - * @return {string} - * @api public - */ - - load : function(file, callback) { - if (any(hook('loading', file), haveStopped)) return - if ('readFile' in main) - return readFile(file) - else if (this.hasXhr()) { - var request = this.xhr() - request.open('GET', file, false) - request.send(null) - if (request.readyState == 4 && - (request.status == 0 || - request.status.toString().charAt(0) == 2)) - return request.responseText - } - else - error("failed to load `" + file + "'") - }, - - /** - * Load, pre-process, and evaluate a file. - * - * @param {string} file - * @param {JSpec} - * @api public - */ - - exec : function(file) { - if (any(hook('executing', file), haveStopped)) return this - eval('with (JSpec){' + this.preprocess(this.load(file)) + '}') - return this - } - } - - // --- Utility functions - - var main = this - var find = JSpec.any - var utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \ - error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/) - while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift()) - if (!main.setTimeout) main.setTimeout = function(callback){ callback() } - - // --- Matchers - - addMatchers({ - equal : "===", - eql : "equal(actual, expected)", - be : "alias equal", - be_greater_than : ">", - be_less_than : "<", - be_at_least : ">=", - be_at_most : "<=", - be_a : "actual.constructor == expected", - be_an : "alias be_a", - be_an_instance_of : "actual instanceof expected", - be_null : "actual == null", - be_true : "actual == true", - be_false : "actual == false", - be_undefined : "typeof actual == 'undefined'", - be_type : "typeof actual == expected", - match : "typeof actual == 'string' ? actual.match(expected) : false", - respond_to : "typeof actual[expected] == 'function'", - have_length : "actual.length == expected", - be_within : "actual >= expected[0] && actual <= last(expected)", - have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)", - - receive : { defer : true, match : function(actual, method, times) { - proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate) - JSpec.currentSpec.assertions.push(proxy) - return proxy - }}, - - be_empty : function(actual) { - if (actual.constructor == Object && actual.length == undefined) - for (var key in actual) - return false; - return !actual.length - }, - - include : function(actual) { - for (state = true, i = 1; i < arguments.length; i++) { - arg = arguments[i] - switch (actual.constructor) { - case String: - case Number: - case RegExp: - case Function: - state = actual.toString().indexOf(arg) !== -1 - break - - case Object: - state = arg in actual - break - - case Array: - state = any(actual, function(value){ return equal(value, arg) }) - break - } - if (!state) return false - } - return true - }, - - throw_error : { match : function(actual, expected, message) { - try { actual() } - catch (e) { - this.e = e - var assert = function(arg) { - switch (arg.constructor) { - case RegExp : return arg.test(e.message || e.toString()) - case String : return arg == (e.message || e.toString()) - case Function : return e instanceof arg || e.name == arg.name - } - } - return message ? assert(expected) && assert(message) : - expected ? assert(expected) : - true - } - }, message : function(actual, expected, negate) { - // TODO: refactor when actual is not in expected [0] - var message_for = function(i) { - if (expected[i] == undefined) return 'exception' - switch (expected[i].constructor) { - case RegExp : return 'exception matching ' + puts(expected[i]) - case String : return 'exception of ' + puts(expected[i]) - case Function : return expected[i].name || 'Error' - } - } - exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '') - return 'expected ' + exception + (negate ? ' not ' : '' ) + - ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was') - }}, - - have : function(actual, length, property) { - return actual[property].length == length - }, - - have_at_least : function(actual, length, property) { - return actual[property].length >= length - }, - - have_at_most :function(actual, length, property) { - return actual[property].length <= length - }, - - have_within : function(actual, range, property) { - length = actual[property].length - return length >= range.shift() && length <= range.pop() - }, - - have_prop : function(actual, property, value) { - return actual[property] == null || - actual[property] instanceof Function ? false: - value == null ? true: - does(actual[property], 'eql', value) - }, - - have_property : function(actual, property, value) { - return actual[property] == null || - actual[property] instanceof Function ? false: - value == null ? true: - value === actual[property] - } - }) - -})() diff --git a/spec/lib/jspec.shell.js b/spec/lib/jspec.shell.js deleted file mode 100644 index cb19c69..0000000 --- a/spec/lib/jspec.shell.js +++ /dev/null @@ -1,39 +0,0 @@ - -// JSpec - Shell - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - var _quit = quit - - Shell = { - - // --- Global - - main: this, - - // --- Commands - - commands: { - quit: ['Terminate the shell', function(){ _quit() }], - exit: ['Terminate the shell', function(){ _quit() }], - p: ['Inspect an object', function(o){ return o.toSource() }] - }, - - /** - * Start the interactive shell. - * - * @api public - */ - - start : function() { - for (var name in this.commands) - if (this.commands.hasOwnProperty(name)) - this.commands[name][1].length ? - this.main[name] = this.commands[name][1] : - this.main.__defineGetter__(name, this.commands[name][1]) - } - } - - Shell.start() - -})() \ No newline at end of file diff --git a/spec/lib/jspec.timers.js b/spec/lib/jspec.timers.js deleted file mode 100644 index c88d10b..0000000 --- a/spec/lib/jspec.timers.js +++ /dev/null @@ -1,90 +0,0 @@ - -// JSpec - Mock Timers - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - /** - * Version. - */ - - mockTimersVersion = '1.0.2' - - /** - * Localized timer stack. - */ - - var timers = [] - - /** - * Set mock timeout with _callback_ and timeout of _ms_. - * - * @param {function} callback - * @param {int} ms - * @return {int} - * @api public - */ - - setTimeout = function(callback, ms) { - var id - return id = setInterval(function(){ - callback() - clearInterval(id) - }, ms) - } - - /** - * Set mock interval with _callback_ and interval of _ms_. - * - * @param {function} callback - * @param {int} ms - * @return {int} - * @api public - */ - - setInterval = function(callback, ms) { - callback.step = ms, callback.current = callback.last = 0 - return timers[timers.length] = callback, timers.length - } - - /** - * Destroy timer with _id_. - * - * @param {int} id - * @return {bool} - * @api public - */ - - clearInterval = clearTimeout = function(id) { - return delete timers[--id] - } - - /** - * Reset timers. - * - * @return {array} - * @api public - */ - - resetTimers = function() { - return timers = [] - } - - /** - * Increment each timers internal clock by _ms_. - * - * @param {int} ms - * @api public - */ - - tick = function(ms) { - for (var i = 0, len = timers.length; i < len; ++i) - if (timers[i] && (timers[i].current += ms)) - if (timers[i].current - timers[i].last >= timers[i].step) { - var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step) - var remainder = (timers[i].current - timers[i].last) % timers[i].step - timers[i].last = timers[i].current - remainder - while (times-- && timers[i]) timers[i]() - } - } - -})() \ No newline at end of file diff --git a/spec/lib/jspec.xhr.js b/spec/lib/jspec.xhr.js deleted file mode 100644 index 906e4c5..0000000 --- a/spec/lib/jspec.xhr.js +++ /dev/null @@ -1,193 +0,0 @@ - -// JSpec - XHR - Copyright TJ Holowaychuk (MIT Licensed) - -(function(){ - - // --- Original XMLHttpRequest - - var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ? - XMLHttpRequest : - function(){} - var OriginalActiveXObject = 'ActiveXObject' in this ? - ActiveXObject : - undefined - - // --- MockXMLHttpRequest - - var MockXMLHttpRequest = function() { - this.requestHeaders = {} - } - - MockXMLHttpRequest.prototype = { - status: 0, - async: true, - readyState: 0, - responseText: '', - abort: function(){}, - onreadystatechange: function(){}, - - /** - * Return response headers hash. - */ - - getAllResponseHeaders : function(){ - return this.responseHeaders - }, - - /** - * Return case-insensitive value for header _name_. - */ - - getResponseHeader : function(name) { - return this.responseHeaders[name.toLowerCase()] - }, - - /** - * Set case-insensitive _value_ for header _name_. - */ - - setRequestHeader : function(name, value) { - this.requestHeaders[name.toLowerCase()] = value - }, - - /** - * Open mock request. - */ - - open : function(method, url, async, user, password) { - this.user = user - this.password = password - this.url = url - this.readyState = 1 - this.method = method.toUpperCase() - if (async != undefined) this.async = async - if (this.async) this.onreadystatechange() - }, - - /** - * Send request _data_. - */ - - send : function(data) { - var self = this - this.data = data - this.readyState = 4 - if (this.method == 'HEAD') this.responseText = null - this.responseHeaders['content-length'] = (this.responseText || '').length - if(this.async) this.onreadystatechange() - lastRequest = function(){ - return self - } - } - } - - // --- Response status codes - - JSpec.statusCodes = { - 100: 'Continue', - 101: 'Switching Protocols', - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - 300: 'Multiple Choice', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 307: 'Temporary Redirect', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Request Entity Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 422: 'Unprocessable Entity', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported' - } - - /** - * Mock XMLHttpRequest requests. - * - * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' }) - * - * @return {hash} - * @api public - */ - - function mockRequest() { - return { and_return : function(body, type, status, headers) { - XMLHttpRequest = MockXMLHttpRequest - ActiveXObject = false - status = status || 200 - headers = headers || {} - headers['content-type'] = type - JSpec.extend(XMLHttpRequest.prototype, { - responseText: body, - responseHeaders: headers, - status: status, - statusText: JSpec.statusCodes[status] - }) - }} - } - - /** - * Unmock XMLHttpRequest requests. - * - * @api public - */ - - function unmockRequest() { - XMLHttpRequest = OriginalXMLHttpRequest - ActiveXObject = OriginalActiveXObject - } - - JSpec.include({ - name: 'Mock XHR', - - // --- Utilities - - utilities : { - mockRequest: mockRequest, - unmockRequest: unmockRequest - }, - - // --- Hooks - - afterSpec : function() { - unmockRequest() - }, - - // --- DSLs - - DSLs : { - snake : { - mock_request: mockRequest, - unmock_request: unmockRequest, - last_request: function(){ return lastRequest() } - } - } - - }) -})() \ No newline at end of file diff --git a/spec/spec.logging.js b/spec/spec.logging.js deleted file mode 100644 index 0b91e5a..0000000 --- a/spec/spec.logging.js +++ /dev/null @@ -1,144 +0,0 @@ -describe 'log4js' - before - extend(context, { - log4js : require("log4js")() - }); - end - - before_each - log4js.clearAppenders(); - event = ''; - logger = log4js.getLogger('tests'); - logger.setLevel("TRACE"); - logger.addListener("log", function (logEvent) { event = logEvent; }); - end - - describe 'addAppender' - before_each - appenderEvent = undefined; - appender = function(logEvent) { appenderEvent = logEvent; }; - end - - describe 'without a category' - it 'should register the function as a listener for all loggers' - log4js.addAppender(appender); - logger.debug("This is a test"); - appenderEvent.should.be event - end - - it 'should also register as an appender for loggers if an appender for that category is defined' - var otherEvent; - log4js.addAppender(appender); - log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese'); - - var cheeseLogger = log4js.getLogger('cheese'); - cheeseLogger.addListener("log", function (logEvent) { event = logEvent; }); - - cheeseLogger.debug('This is a test'); - - appenderEvent.should.be event - otherEvent.should.be event - - otherEvent = undefined; - appenderEvent = undefined; - log4js.getLogger('pants').debug("this should not be propagated to otherEvent"); - otherEvent.should.be undefined - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "this should not be propagated to otherEvent" - - cheeseLogger = null; - end - end - - describe 'with a category' - it 'should only register the function as a listener for that category' - log4js.addAppender(appender, 'tests'); - - logger.debug('this is a test'); - appenderEvent.should.be event - - appenderEvent = undefined; - log4js.getLogger('some other category').debug('Cheese'); - appenderEvent.should.be undefined - end - end - - describe 'with multiple categories' - it 'should register the function as a listener for all the categories' - log4js.addAppender(appender, 'tests', 'biscuits'); - - logger.debug('this is a test'); - appenderEvent.should.be event - appenderEvent = undefined; - - var otherLogger = log4js.getLogger('biscuits'); - otherLogger.debug("mmm... garibaldis"); - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "mmm... garibaldis" - appenderEvent = undefined; - - otherLogger = null; - - log4js.getLogger("something else").debug("pants"); - appenderEvent.should.be undefined - end - - it 'should register the function when the list of categories is an array' - log4js.addAppender(appender, ['tests', 'pants']); - - logger.debug('this is a test'); - appenderEvent.should.be event - appenderEvent = undefined; - - var otherLogger = log4js.getLogger('pants'); - otherLogger.debug("big pants"); - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "big pants" - appenderEvent = undefined; - - otherLogger = null; - - log4js.getLogger("something else").debug("pants"); - appenderEvent.should.be undefined - end - end - end - - describe 'basicLayout' - it 'should take a logevent and output a formatted string' - logger.debug('this is a test'); - var output = log4js.basicLayout(event); - output.should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - end - - it 'should output a stacktrace, message if the event has an error attached' - var error = new Error("Some made-up error"); - var stack = error.stack.split(/\n/); - - logger.debug('this is a test', error); - - var output = log4js.basicLayout(event); - var lines = output.split(/\n/); - lines.length.should.be stack.length+1 - lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - lines[1].should.match /\[.*?\] \[DEBUG\] tests - Error: Some made-up error/ - for (var i = 1; i < stack.length; i++) { - lines[i+1].should.eql stack[i] - } - end - - it 'should output a name and message if the event has something that pretends to be an error' - logger.debug('this is a test', { name: 'Cheese', message: 'Gorgonzola smells.' }); - var output = log4js.basicLayout(event); - var lines = output.split(/\n/); - lines.length.should.be 2 - lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - lines[1].should.match /\[.*?\] \[DEBUG\] tests - Cheese: Gorgonzola smells./ - end - end - - - - -end - diff --git a/test/logging.js b/test/logging.js index 8fd9d8a..0e4cc41 100644 --- a/test/logging.js +++ b/test/logging.js @@ -29,6 +29,8 @@ vows.describe('log4js').addBatch({ logger.trace("Trace event 1"); logger.trace("Trace event 2"); logger.warn("Warning event"); + logger.error("Aargh!", new Error("Pants are on fire!")); + logger.error("Simulated CouchDB problem", { err: 127, cause: "incendiary underwear" }); return events; }, @@ -39,9 +41,20 @@ vows.describe('log4js').addBatch({ }, 'should not emit events of a lower level': function(events) { - assert.length(events, 2); + assert.length(events, 4); assert.equal(events[1].level.toString(), 'WARN'); - } + }, + + 'should include the error if passed in': function (events) { + assert.instanceOf(events[2].exception, Error); + assert.equal(events[2].exception.message, 'Pants are on fire!'); + }, + + 'should convert things that claim to be errors into Error objects': function (events) { + assert.instanceOf(events[3].exception, Error); + assert.equal(events[3].exception.message, "{ err: 127, cause: 'incendiary underwear' }"); + }, + }, }, @@ -244,6 +257,91 @@ vows.describe('log4js').addBatch({ } }, + 'addAppender' : { + topic: function() { + var log4js = require('../lib/log4js')(); + log4js.clearAppenders(); + return log4js; + }, + 'without a category': { + 'should register the function as a listener for all loggers': function (log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); + log4js.addAppender(appender); + logger.debug("This is a test"); + assert.equal(appenderEvent.message, "This is a test"); + assert.equal(appenderEvent.categoryName, "tests"); + assert.equal(appenderEvent.level.toString(), "DEBUG"); + }, + 'should also register as an appender for loggers if an appender for that category is defined': function (log4js) { + var otherEvent, appenderEvent, cheeseLogger; + log4js.addAppender(function (evt) { appenderEvent = evt; }); + log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese'); + + cheeseLogger = log4js.getLogger('cheese'); + cheeseLogger.debug('This is a test'); + assert.deepEqual(appenderEvent, otherEvent); + assert.equal(otherEvent.message, 'This is a test'); + assert.equal(otherEvent.categoryName, 'cheese'); + + otherEvent = undefined; + appenderEvent = undefined; + log4js.getLogger('pants').debug("this should not be propagated to otherEvent"); + assert.isUndefined(otherEvent); + assert.equal(appenderEvent.message, "this should not be propagated to otherEvent"); + } + }, + + 'with a category': { + 'should only register the function as a listener for that category': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); + log4js.addAppender(appender, 'tests'); + logger.debug('this is a category test'); + assert.equal(appenderEvent.message, 'this is a category test'); + + appenderEvent = undefined; + log4js.getLogger('some other category').debug('Cheese'); + assert.isUndefined(appenderEvent); + } + }, + + 'with multiple categories': { + 'should register the function as a listener for all the categories': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger('tests'); + log4js.addAppender(appender, 'tests', 'biscuits'); + + logger.debug('this is a test'); + assert.equal(appenderEvent.message, 'this is a test'); + appenderEvent = undefined; + + var otherLogger = log4js.getLogger('biscuits'); + otherLogger.debug("mmm... garibaldis"); + assert.equal(appenderEvent.message, "mmm... garibaldis"); + + appenderEvent = undefined; + + log4js.getLogger("something else").debug("pants"); + assert.isUndefined(appenderEvent); + }, + 'should register the function when the list of categories is an array': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }; + log4js.addAppender(appender, ['tests', 'pants']); + + log4js.getLogger('tests').debug('this is a test'); + assert.equal(appenderEvent.message, 'this is a test'); + + appenderEvent = undefined; + + log4js.getLogger('pants').debug("big pants"); + assert.equal(appenderEvent.message, "big pants"); + + appenderEvent = undefined; + + log4js.getLogger("something else").debug("pants"); + assert.isUndefined(appenderEvent); + } + } + }, + 'default setup': { topic: function() { var pathsChecked = [], @@ -327,6 +425,55 @@ vows.describe('log4js').addBatch({ } }, + 'basicLayout': { + topic: function() { + var layout = require('../lib/log4js')().basicLayout, + event = { + message: 'this is a test', + startTime: new Date(2010, 11, 5, 14, 18, 30, 45), + categoryName: "tests", + level: { + colour: "green", + toString: function() { return "DEBUG"; } + } + }; + return [layout, event]; + }, + 'should take a logevent and output a formatted string': function(args) { + var layout = args[0], event = args[1]; + assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + }, + 'should output a stacktrace, message if the event has an error attached': function(args) { + var layout = args[0], event = args[1], output, lines, + error = new Error("Some made-up error"), + stack = error.stack.split(/\n/); + + event.exception = error; + output = layout(event); + lines = output.split(/\n/); + + assert.length(lines, stack.length+1); + assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + assert.equal(lines[1], "[2010-12-05 14:18:30.045] [DEBUG] tests - Error: Some made-up error"); + for (var i = 1; i < stack.length; i++) { + assert.equal(lines[i+1], stack[i]); + } + }, + 'should output a name and message if the event has something that pretends to be an error': function(args) { + var layout = args[0], event = args[1], output, lines; + event.exception = { + name: 'Cheese', + message: 'Gorgonzola smells.' + }; + output = layout(event); + lines = output.split(/\n/); + + assert.length(lines, 2); + assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + assert.equal(lines[1], "[2010-12-05 14:18:30.045] [DEBUG] tests - Cheese: Gorgonzola smells."); + } + }, + 'logLevelFilter': { topic: function() { var log4js = require('../lib/log4js')(), logEvents = [], logger; @@ -348,7 +495,7 @@ vows.describe('log4js').addBatch({ 'Date extensions': { topic: function() { - require('../lib/log4js'); + require('../lib/log4js')(); return new Date(2010, 0, 11, 14, 31, 30, 5); }, 'should add a toFormattedString method to Date': function(date) { @@ -357,6 +504,37 @@ vows.describe('log4js').addBatch({ 'should default to a format': function(date) { assert.equal(date.toFormattedString(), '2010-01-11 14:31:30.005'); } + }, + + 'console' : { + topic: function() { + return require('../lib/log4js')(); + }, + 'should replace console.log methods with log4js ones': function(log4js) { + var logEvent; + log4js.clearAppenders(); + log4js.addAppender(function(evt) { logEvent = evt; }); + + console.log("Some debug message someone put in a module"); + assert.equal(logEvent.message, "Some debug message someone put in a module"); + assert.equal(logEvent.level.toString(), "INFO"); + logEvent = undefined; + console.debug("Some debug"); + assert.equal(logEvent.message, "Some debug"); + assert.equal(logEvent.level.toString(), "DEBUG"); + logEvent = undefined; + console.error("An error"); + assert.equal(logEvent.message, "An error"); + assert.equal(logEvent.level.toString(), "ERROR"); + logEvent = undefined; + console.info("some info"); + assert.equal(logEvent.message, "some info"); + assert.equal(logEvent.level.toString(), "INFO"); + logEvent = undefined; + console.trace("tracing"); + assert.equal(logEvent.message, "tracing"); + assert.equal(logEvent.level.toString(), "TRACE"); + } } }).export(module); diff --git a/tests.js b/tests.js deleted file mode 100644 index 3090813..0000000 --- a/tests.js +++ /dev/null @@ -1,43 +0,0 @@ -require.paths.unshift("./spec/lib", "./lib"); -require("jspec"); - -var sys = require("sys"), fs = require("fs"); - -quit = process.exit -print = sys.puts - -readFile = function(path) { - var result; - try { - result = fs.readFileSync(path, "utf8"); - } catch (e) { - throw e; - } - return result; -} - -var specsFound = false; - -if (process.ARGV[2]) { - specsFound = true; - JSpec.exec('spec/spec.' + process.ARGV[2] + '.js'); -} else { - var files = fs.readdirSync('spec/'); - files.filter( - function (file) { - return file.indexOf('spec.') === 0; - } - ).forEach( - function(file) { - specsFound = true; - JSpec.exec('spec/'+file); - } - ); -} -if (specsFound) { - JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: false }); - JSpec.report(); -} else { - print("No tests to run. This makes me sad."); -} -