Skip to content

Loading…

self-contained debuggable scripts #92

Open
wants to merge 7 commits into from

3 participants

@mgutz

Consider merging this topic branch. This adds the ability to easily debug from a test module by simply adding require('nodeunit').test() to end of file.

@mgutz

If you're wondering what all the stack stuff is, here's a comparison. I did not touch utils#betterErrors so this is isolated to the inline runner. Basically, nodeunit library stack traces were removed since it's not related to the code I'm testing.

Before:

✖ should new up a user

AssertionError: 100 == 200
    at Object.equal (/Users/mgutz/code/nodeunit/lib/types.js:81:39)
    at /Users/mgutz/barc/code/chat-io/src/server/test/userTest.coffee:15:19
    at /Users/mgutz/barc/code/chat-io/src/server/models/mongodb/user.coffee:64:16
    at Object.use (/Users/mgutz/barc/code/chat-io/src/server/data/mongodbStore.coffee:52:14)
    at Object.use (/Users/mgutz/barc/code/chat-io/src/server/models/mongodb/user.coffee:61:20)
    at /Users/mgutz/barc/code/chat-io/src/server/test/userTest.coffee:14:10
    at Object.runTest (/Users/mgutz/code/nodeunit/lib/core.js:64:9)
    at /Users/mgutz/code/nodeunit/lib/core.js:103:25
    at /Users/mgutz/code/nodeunit/deps/async.js:508:13
    at /Users/mgutz/code/nodeunit/deps/async.js:118:13

After:

✖ should new up a user

  AssertionError: 100 == 200
    ⇢  ~/barc/code/chat-io/src/server/test/userTest.coffee:15:19
    at ~/barc/code/chat-io/src/server/models/mongodb/user.coffee:64:16
    at Object.use (~/barc/code/chat-io/src/server/data/mongodbStore.coffee:52:14)
    at Object.use (~/barc/code/chat-io/src/server/models/mongodb/user.coffee:61:20)
    ⇢  ~/barc/code/chat-io/src/server/test/userTest.coffee:14:10
@allanmboyd

I might easily be missing something but the stack updates look like they could be useful for lots of reporters.....so why not put them into utils#betterErrors?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 20, 2011
  1. @mgutz

    self-contained debuggable scripts

    mgutz committed
  2. @mgutz

    removed log statement

    mgutz committed
  3. @mgutz

    better stack trace

    mgutz committed
  4. @mgutz
Commits on Aug 21, 2011
  1. @mgutz
  2. @mgutz

    lint fixes

    mgutz committed
  3. @mgutz

    nodelint fixes

    mgutz committed
Showing with 292 additions and 4 deletions.
  1. +1 −0 .gitignore
  2. +28 −1 README.md
  3. +2 −2 bin/nodeunit
  4. +12 −1 lib/nodeunit.js
  5. +4 −0 lib/reporters/index.js
  6. +245 −0 lib/reporters/inline.js
View
1 .gitignore
@@ -3,3 +3,4 @@ stamp-build
*~
gmon.out
v8.log
+node_modules
View
29 README.md
@@ -238,6 +238,33 @@ bin/nodeunit.json for current available options.
* __--help__ - show nodeunit help
+### Debugging
+
+A tests module may be converted to a self-contained script by adding the following
+line to the end of the module:
+
+ require('nodeunit').test();
+
+For example, `selfContained.js` might look like this
+
+ test1: function (test) {
+ test.equals(this.foo, 'bar');
+ test.done();
+ }
+
+ require('nodeunit').test();
+
+To run a self-contained script:
+
+ node selfContained.js
+
+Or, to debug:
+
+ node debug selfContained.js
+
+Self-contained scripts are also compatible with `nodeunit`.
+
+
Running tests in the browser
----------------------------
@@ -416,7 +443,7 @@ module to ensure that test functions are actually run, and a basic level of
nodeunit functionality is available.
To run the nodeunit tests do:
-
+
make test
__Note:__ There was a bug in node v0.2.0 causing the tests to hang, upgrading
View
4 bin/nodeunit
@@ -74,7 +74,7 @@ args.forEach(function (arg) {
}).filter(function (reporter_file) {
return reporter_file !== 'index';
});
- console.log('Build-in reporters: ');
+ console.log('Built-in reporters: ');
reporters.forEach(function (reporter_file) {
var reporter = require('../lib/reporters/' + reporter_file);
console.log(' * ' + reporter_file + (reporter.info ? ': ' + reporter.info : ''));
@@ -102,7 +102,7 @@ if (files.length === 0) {
if (config_file) {
content = fs.readFileSync(config_file, 'utf8');
var custom_options = JSON.parse(content);
-
+
for (var option in custom_options) {
if (typeof option === 'string') {
options[option] = custom_options[option];
View
13 lib/nodeunit.js
@@ -44,7 +44,7 @@ exports.testrunner = {
for (var k in core) {
exports[k] = core[k];
-};
+}
/**
@@ -80,3 +80,14 @@ exports.runFiles = function (paths, opt) {
});
};
+
+
+/**
+ * Runs a file as a self-contained test, that is the file can be executed by node
+ * directly. The file may also be run safely by nodeunit.
+ *
+ * @api public
+ */
+exports.test = function () {
+ require('./reporters/inline').run();
+};
View
4 lib/reporters/index.js
@@ -4,6 +4,10 @@ module.exports = {
'skip_passed': require('./skip_passed'),
'minimal': require('./minimal'),
'html': require('./html')
+
+ // inline not listed since it should only run as part of a self-contained
+ // script
+
// browser test reporter is not listed because it cannot be used
// with the command line tool, only inside a browser.
};
View
245 lib/reporters/inline.js
@@ -0,0 +1,245 @@
+/*jslint node:true, indent:4 */
+/*global console: true */
+
+/*!
+ * Nodeunit
+ * Copyright (c) 2010 Caolan McMahon
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies
+ */
+
+var nodeunit = require('../nodeunit'),
+ util = require('util'),
+ utils = require('../utils'),
+ fs = require('fs'),
+ track = require('../track'),
+ path = require('path'),
+ AssertionError = require('../assert').AssertionError;
+
+var error, ok, bold, assertion_message, home = process.env.HOME;
+
+
+/**
+ * Determines if `nodeunit` is running.
+ */
+var isNodeUnitRunning = function () {
+ var arg;
+ // silently return if being run by nodeunit
+ for (var i = 0; i < process.argv.length; i += 1) {
+ arg = process.argv[i];
+ // path ends with nodeunit
+ if (arg.lastIndexOf('nodeunit') === (arg.length - 'nodeunit'.length)) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Removes nodeunit specic line trace from stack and colors any
+ * line from current test module.
+ *
+ * @param {String} stack Error stack.
+ * @param {Object} mod The test module.
+ * @param {Boolean} isAssertion Indicates if stack is related to an assertion.
+ * @returns {String} Returns the modified stack trace.
+ */
+var betterStack = function (stack, mod, isAssertion) {
+ var line, lines = stack.split('\n');
+ var first = true;
+ var result = [];
+
+ // assertion errors are treated differently by caller
+ if (isAssertion) {
+ first = false;
+ lines = lines.slice(1);
+ }
+
+ for (var i = 0; i < lines.length; i += 1) {
+ line = lines[i];
+ // ignore nodeunit trace and last entry which is the tick callback
+ if (line.indexOf('nodeunit/') < 0 && (i < lines.length - 1)) {
+ if (line.indexOf(mod.filename) >= 0) {
+ line = line.replace('at', bold(assertion_message('')));
+ //line += bold(assertion_message(" ⬅"));
+ }
+ else if (first) {
+ line = assertion_message(' ' + line);
+ first = false;
+ }
+ result.push(line.replace(home, "~"));
+ }
+ }
+
+ return result.join('\n');
+};
+
+
+/**
+ * Improves formatting of AssertionError messages to make deepEqual etc more
+ * readable.
+ *
+ * @param {Object} assertion
+ * @param {Object} mod The test module.
+ * @return {Object}
+ * @api public
+ */
+
+var betterErrors = function (assertion, mod) {
+ if (!assertion.error) {
+ return;
+ }
+
+ var e = assertion.error;
+ // deepEqual error message is a bit sucky, lets improve it!
+ // e.actual and e.expected could be null or undefined, so
+ // using getOwnPropertyDescriptor to see if they exist:
+ if (Object.getOwnPropertyDescriptor(e, 'actual') &&
+ Object.getOwnPropertyDescriptor(e, 'expected')) {
+
+ // alexgorbatchev 2010-10-22 :: Added a bit of depth to inspection
+ var actual = util.inspect(e.actual, false, 10).replace(/\n$/, '');
+ var expected = util.inspect(e.expected, false, 10).replace(/\n$/, '');
+ var multiline = (
+ actual.indexOf('\n') !== -1 ||
+ expected.indexOf('\n') !== -1
+ );
+ var spacing = (multiline ? '\n' : ' ');
+ e._message = e.message;
+ e.stack = (
+ assertion_message(' ' + e.name + ':' + spacing +
+ actual + spacing + e.operator + spacing +
+ expected
+ ) + '\n' +
+ betterStack(e.stack, mod, true)
+ );
+ }
+ else {
+ assertion.error.stack = betterStack(assertion.error.stack, mod, false);
+ }
+ return assertion;
+};
+
+/**
+ * Reporter info string
+ */
+
+exports.info = "Inline tests reporter";
+
+
+/**
+ * Run all tests from the module which invoked this function.
+ *
+ * @api public
+ */
+
+exports.run = function (options) {
+ if (isNodeUnitRunning()) {
+ return;
+ }
+
+ var current = module;
+ while (current && current.id !== '.') {
+ current = current.parent;
+ }
+ if (require.main !== current) {
+ // silently return when being run by nodeunit
+ return;
+ }
+
+ var name = path.basename(current.filename).split('.')[0];
+ var modules = {};
+ modules[name] = current.exports;
+
+
+ if (!options) {
+ // load default options
+ var content = fs.readFileSync(
+ __dirname + '/../../bin/nodeunit.json', 'utf8'
+ );
+ options = JSON.parse(content);
+ }
+
+ error = function (str) {
+ return options.error_prefix + str + options.error_suffix;
+ };
+ ok = function (str) {
+ return options.ok_prefix + str + options.ok_suffix;
+ };
+ bold = function (str) {
+ return options.bold_prefix + str + options.bold_suffix;
+ };
+ assertion_message = function (str) {
+ return options.assertion_prefix + str + options.assertion_suffix;
+ };
+
+ var start = new Date().getTime();
+ var tracker = track.createTracker(function (tracker) {
+ if (tracker.unfinished()) {
+ console.log('');
+ console.log(error(bold(
+ 'FAILURES: Undone tests (or their setups/teardowns): '
+ )));
+ var names = tracker.names();
+ for (var i = 0; i < names.length; i += 1) {
+ console.log('- ' + names[i]);
+ }
+ console.log('');
+ console.log('To fix this, make sure all tests call test.done()');
+ process.reallyExit(tracker.unfinished());
+ }
+ });
+
+ nodeunit.runModules(modules, {
+ testspec: options.testspec,
+ moduleStart: function (name) {
+ console.log('\n' + bold(name));
+ },
+ testDone: function (name, assertions) {
+ tracker.remove(name);
+
+ if (!assertions.failures()) {
+ console.log('' + name);
+ }
+ else {
+ console.log(error('' + name) + '\n');
+ assertions.forEach(function (a) {
+ if (a.failed()) {
+ a = betterErrors(a, current, assertion_message);
+ if (a.error instanceof AssertionError && a.message) {
+ console.log(
+ 'Assertion Message: ' +
+ assertion_message(a.message)
+ );
+ }
+ console.log(a.error.stack + '\n');
+ }
+ });
+ }
+ },
+ done: function (assertions, end) {
+ end = end || new Date().getTime();
+ var duration = end - start;
+ if (assertions.failures()) {
+ console.log(
+ '\n' + bold(error('FAILURES: ')) + assertions.failures() +
+ '/' + assertions.length + ' assertions failed (' +
+ assertions.duration + 'ms)'
+ );
+ }
+ else {
+ console.log(
+ '\n' + bold(ok('OK: ')) + assertions.length +
+ ' assertions (' + assertions.duration + 'ms)'
+ );
+ }
+ },
+ testStart: function (name) {
+ tracker.put(name);
+ }
+ });
+};
Something went wrong with that request. Please try again.