text: /*!
   * 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;
  };
  