/*! * Nodeunit * Copyright (c) 2010 Caolan McMahon * MIT Licensed */ /** * Module dependencies */ var async = require('../deps/async'), fs = require('fs'), util = require('util'), Script = require('vm').Script, http = require('http'); /** * Detect if coffee-script, iced-coffeescript, or streamline are available and * the respective file extensions to the search filter in modulePaths if it is. */ var extensions = [ 'js' ]; // js is always supported: add it unconditionally var extensionPattern; try { require('coffee' + '-script/register'); extensions.push('coffee'); } catch (e) { } try { require('iced-coffee' + '-script/register'); extensions.push('iced'); } catch (e) { } try { require('stream' + 'line').register(); extensions.push('_coffee'); extensions.push('_js'); } catch (e) { } extensionPattern = new RegExp('\\.(?:' + extensions.join('|') + ')$'); /** * Finds all modules at each path in an array, If a path is a directory, it * returns all supported file types inside it. This only reads 1 level deep in * the directory and does not recurse through sub-directories. * * The extension (.js, .coffee etc) is stripped from the filenames so they can * simply be require()'ed. * * @param {Array} paths * @param {Function} callback * @api public */ exports.modulePaths = function (paths, callback) { async.concat(paths, function (p, cb) { fs.stat(p, function (err, stats) { if (err) { return cb(err); } if (stats.isFile()) { return cb(null, [p]); } if (stats.isDirectory()) { fs.readdir(p, function (err, files) { if (err) { return cb(err); } // filter out any filenames with unsupported extensions var modules = files.filter(function (filename) { return extensionPattern.exec(filename); }); // remove extension from module name and prepend the // directory path var fullpaths = modules.map(function (filename) { var mod_name = filename.replace(extensionPattern, ''); return [p, mod_name].join('/'); }); // sort filenames here, because Array.map changes order fullpaths.sort(); cb(null, fullpaths); }); } }); }, callback); }; /** * Evaluates JavaScript files in a sandbox, returning the context. The first * argument can either be a single filename or an array of filenames. If * multiple filenames are given their contents are concatenated before * evalution. The second argument is an optional context to use for the sandbox. * * @param files * @param {Object} sandbox * @return {Object} * @api public */ exports.sandbox = function (files, /*optional*/sandbox) { var source, script, result; if (!(files instanceof Array)) { files = [files]; } source = files.map(function (file) { return fs.readFileSync(file, 'utf8'); }).join(''); if (!sandbox) { sandbox = {}; } script = new Script(source); result = script.runInNewContext(sandbox); return sandbox; }; /** * Provides a http request, response testing environment. * * Example: * * var httputil = require('nodeunit').utils.httputil * exports.testSomething = function(test) { * httputil(function (req, resp) { * resp.writeHead(200, {}); * resp.end('test data'); * }, * function(server, client) { * client.fetch('GET', '/', {}, function(resp) { * test.equal('test data', resp.body); * server.close(); * test.done(); * }) * }); * }; * * @param {Function} cgi * @param {Function} envReady * @api public */ exports.httputil = function (cgi, envReady) { var hostname = process.env.HOSTNAME || 'localhost'; var port = process.env.PORT || 3000; var server = http.createServer(cgi); server.listen(port, hostname); var client = http.createClient(port, hostname); client.fetch = function (method, path, headers, respReady) { var request = this.request(method, path, headers); request.end(); request.on('response', function (response) { response.setEncoding('utf8'); response.on('data', function (chunk) { if (response.body) { response.body += chunk; } else { response.body = chunk; } }); response.on('end', function () { if (response.headers['content-type'] === 'application/json') { response.bodyAsObject = JSON.parse(response.body); } respReady(response); }); }); }; process.nextTick(function () { if (envReady && typeof envReady === 'function') { envReady(server, client); } }); }; /** * Improves formatting of AssertionError messages to make deepEqual etc more * readable. * * @param {Object} assertion * @return {Object} * @api public */ exports.betterErrors = function (assertion) { if (!assertion.error) { return assertion; } var e = assertion.error; if (e.actual && e.expected) { 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 = ( e.name + ':' + spacing + actual + spacing + e.operator + spacing + expected + '\n' + e.stack.split('\n').slice(1).join('\n') ); } return assertion; };