/js/lib/Socket.IO-node/support/expresso/bin/expresso
#! | 856 lines | 751 code | 105 blank | 0 comment | 0 complexity | 4c8f0d8373092338ca4e1393d65ff4e3 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
- #!/usr/bin/env node
- /*
- * Expresso
- * Copyright(c) TJ Holowaychuk <tj@vision-media.ca>
- * (MIT Licensed)
- */
-
- /**
- * Module dependencies.
- */
- var assert = require('assert'),
- childProcess = require('child_process'),
- http = require('http'),
- path = require('path'),
- sys = require('sys'),
- cwd = process.cwd(),
- fs = require('fs'),
- defer;
- /**
- * Expresso version.
- */
- var version = '0.7.2';
- /**
- * Failure count.
- */
- var failures = 0;
- /**
- * Number of tests executed.
- */
- var testcount = 0;
- /**
- * Whitelist of tests to run.
- */
-
- var only = [];
- /**
- * Boring output.
- */
-
- var boring = false;
- /**
- * Growl notifications.
- */
- var growl = false;
- /**
- * Server port.
- */
- var port = 5555;
- /**
- * Execute serially.
- */
- var serial = false;
- /**
- * Default timeout.
- */
- var timeout = 2000;
- /**
- * Quiet output.
- */
- var quiet = false;
- /**
- * Usage documentation.
- */
- var usage = ''
- + '[bold]{Usage}: expresso [options] <file ...>'
- + '\n'
- + '\n[bold]{Options}:'
- + '\n -g, --growl Enable growl notifications'
- + '\n -c, --coverage Generate and report test coverage'
- + '\n -q, --quiet Suppress coverage report if 100%'
- + '\n -t, --timeout MS Timeout in milliseconds, defaults to 2000'
- + '\n -r, --require PATH Require the given module path'
- + '\n -o, --only TESTS Execute only the comma sperated TESTS (can be set several times)'
- + '\n -I, --include PATH Unshift the given path to require.paths'
- + '\n -p, --port NUM Port number for test servers, starts at 5555'
- + '\n -s, --serial Execute tests serially'
- + '\n -b, --boring Suppress ansi-escape colors'
- + '\n -v, --version Output version number'
- + '\n -h, --help Display help information'
- + '\n';
- // Parse arguments
- var files = [],
- args = process.argv.slice(2);
- while (args.length) {
- var arg = args.shift();
- switch (arg) {
- case '-h':
- case '--help':
- print(usage + '\n');
- process.exit(1);
- break;
- case '-v':
- case '--version':
- sys.puts(version);
- process.exit(1);
- break;
- case '-i':
- case '-I':
- case '--include':
- if (arg = args.shift()) {
- require.paths.unshift(arg);
- } else {
- throw new Error('--include requires a path');
- }
- break;
- case '-o':
- case '--only':
- if (arg = args.shift()) {
- only = only.concat(arg.split(/ *, */));
- } else {
- throw new Error('--only requires comma-separated test names');
- }
- break;
- case '-p':
- case '--port':
- if (arg = args.shift()) {
- port = parseInt(arg, 10);
- } else {
- throw new Error('--port requires a number');
- }
- break;
- case '-r':
- case '--require':
- if (arg = args.shift()) {
- require(arg);
- } else {
- throw new Error('--require requires a path');
- }
- break;
- case '-t':
- case '--timeout':
- if (arg = args.shift()) {
- timeout = parseInt(arg, 10);
- } else {
- throw new Error('--timeout requires an argument');
- }
- break;
- case '-c':
- case '--cov':
- case '--coverage':
- defer = true;
- childProcess.exec('rm -fr lib-cov && node-jscoverage lib lib-cov', function(err){
- if (err) throw err;
- require.paths.unshift('lib-cov');
- run(files);
- })
- break;
- case '-q':
- case '--quiet':
- quiet = true;
- break;
- case '-b':
- case '--boring':
- boring = true;
- break;
- case '-g':
- case '--growl':
- growl = true;
- break;
- case '-s':
- case '--serial':
- serial = true;
- break;
- default:
- if (/\.js$/.test(arg)) {
- files.push(arg);
- }
- break;
- }
- }
- /**
- * Colorized sys.error().
- *
- * @param {String} str
- */
- function print(str){
- sys.error(colorize(str));
- }
- /**
- * Colorize the given string using ansi-escape sequences.
- * Disabled when --boring is set.
- *
- * @param {String} str
- * @return {String}
- */
- function colorize(str){
- var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
- return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str){
- return boring
- ? str
- : '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
- });
- }
- // Alias deepEqual as eql for complex equality
- assert.eql = assert.deepEqual;
- /**
- * Assert that `val` is null.
- *
- * @param {Mixed} val
- * @param {String} msg
- */
- assert.isNull = function(val, msg) {
- assert.strictEqual(null, val, msg);
- };
- /**
- * Assert that `val` is not null.
- *
- * @param {Mixed} val
- * @param {String} msg
- */
- assert.isNotNull = function(val, msg) {
- assert.notStrictEqual(null, val, msg);
- };
- /**
- * Assert that `val` is undefined.
- *
- * @param {Mixed} val
- * @param {String} msg
- */
- assert.isUndefined = function(val, msg) {
- assert.strictEqual(undefined, val, msg);
- };
- /**
- * Assert that `val` is not undefined.
- *
- * @param {Mixed} val
- * @param {String} msg
- */
- assert.isDefined = function(val, msg) {
- assert.notStrictEqual(undefined, val, msg);
- };
- /**
- * Assert that `obj` is `type`.
- *
- * @param {Mixed} obj
- * @param {String} type
- * @api public
- */
- assert.type = function(obj, type, msg){
- var real = typeof obj;
- msg = msg || 'typeof ' + sys.inspect(obj) + ' is ' + real + ', expected ' + type;
- assert.ok(type === real, msg);
- };
- /**
- * Assert that `str` matches `regexp`.
- *
- * @param {String} str
- * @param {RegExp} regexp
- * @param {String} msg
- */
- assert.match = function(str, regexp, msg) {
- msg = msg || sys.inspect(str) + ' does not match ' + sys.inspect(regexp);
- assert.ok(regexp.test(str), msg);
- };
- /**
- * Assert that `val` is within `obj`.
- *
- * Examples:
- *
- * assert.includes('foobar', 'bar');
- * assert.includes(['foo', 'bar'], 'foo');
- *
- * @param {String|Array} obj
- * @param {Mixed} val
- * @param {String} msg
- */
- assert.includes = function(obj, val, msg) {
- msg = msg || sys.inspect(obj) + ' does not include ' + sys.inspect(val);
- assert.ok(obj.indexOf(val) >= 0, msg);
- };
- /**
- * Assert length of `val` is `n`.
- *
- * @param {Mixed} val
- * @param {Number} n
- * @param {String} msg
- */
- assert.length = function(val, n, msg) {
- msg = msg || sys.inspect(val) + ' has length of ' + val.length + ', expected ' + n;
- assert.equal(n, val.length, msg);
- };
- /**
- * Assert response from `server` with
- * the given `req` object and `res` assertions object.
- *
- * @param {Server} server
- * @param {Object} req
- * @param {Object|Function} res
- * @param {String} msg
- */
- assert.response = function(server, req, res, msg){
- // Check that the server is ready or defer
- if (!server.fd) {
- if (!('__deferred' in server)) {
- server.__deferred = [];
- }
- server.__deferred.push(arguments);
- if (!server.__started) {
- server.listen(server.__port = port++, '127.0.0.1', function(){
- if (server.__deferred) {
- process.nextTick(function(){
- server.__deferred.forEach(function(args){
- assert.response.apply(assert, args);
- });
- });
- }
- });
- server.__started = true;
- }
- return;
- }
- // Callback as third or fourth arg
- var callback = typeof res === 'function'
- ? res
- : typeof msg === 'function'
- ? msg
- : function(){};
- // Default messate to test title
- if (typeof msg === 'function') msg = null;
- msg = msg || assert.testTitle;
- msg += '. ';
- // Pending responses
- server.__pending = server.__pending || 0;
- server.__pending++;
- // Create client
- if (!server.fd) {
- server.listen(server.__port = port++, '127.0.0.1', issue);
- } else {
- issue();
- }
- function issue(){
- if (!server.client)
- server.client = http.createClient(server.__port);
- // Issue request
- var timer,
- client = server.client,
- method = req.method || 'GET',
- status = res.status || res.statusCode,
- data = req.data || req.body,
- requestTimeout = req.timeout || 0;
- var request = client.request(method, req.url, req.headers);
- // Timeout
- if (requestTimeout) {
- timer = setTimeout(function(){
- --server.__pending || server.close();
- delete req.timeout;
- assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
- }, requestTimeout);
- }
- if (data) request.write(data);
- request.on('response', function(response){
- response.body = '';
- response.setEncoding('utf8');
- response.on('data', function(chunk){ response.body += chunk; });
- response.on('end', function(){
- --server.__pending || server.close();
- if (timer) clearTimeout(timer);
- // Assert response body
- if (res.body !== undefined) {
- var eql = res.body instanceof RegExp
- ? res.body.test(response.body)
- : res.body === response.body;
- assert.ok(
- eql,
- msg + 'Invalid response body.\n'
- + ' Expected: ' + sys.inspect(res.body) + '\n'
- + ' Got: ' + sys.inspect(response.body)
- );
- }
- // Assert response status
- if (typeof status === 'number') {
- assert.equal(
- response.statusCode,
- status,
- msg + colorize('Invalid response status code.\n'
- + ' Expected: [green]{' + status + '}\n'
- + ' Got: [red]{' + response.statusCode + '}')
- );
- }
- // Assert response headers
- if (res.headers) {
- var keys = Object.keys(res.headers);
- for (var i = 0, len = keys.length; i < len; ++i) {
- var name = keys[i],
- actual = response.headers[name.toLowerCase()],
- expected = res.headers[name],
- eql = expected instanceof RegExp
- ? expected.test(actual)
- : expected == actual;
- assert.ok(
- eql,
- msg + colorize('Invalid response header [bold]{' + name + '}.\n'
- + ' Expected: [green]{' + expected + '}\n'
- + ' Got: [red]{' + actual + '}')
- );
- }
- }
- // Callback
- callback(response);
- });
- });
- request.end();
- }
- };
- /**
- * Pad the given string to the maximum width provided.
- *
- * @param {String} str
- * @param {Number} width
- * @return {String}
- */
- function lpad(str, width) {
- str = String(str);
- var n = width - str.length;
- if (n < 1) return str;
- while (n--) str = ' ' + str;
- return str;
- }
- /**
- * Pad the given string to the maximum width provided.
- *
- * @param {String} str
- * @param {Number} width
- * @return {String}
- */
- function rpad(str, width) {
- str = String(str);
- var n = width - str.length;
- if (n < 1) return str;
- while (n--) str = str + ' ';
- return str;
- }
- /**
- * Report test coverage.
- *
- * @param {Object} cov
- */
- function reportCoverage(cov) {
- // Stats
- print('\n [bold]{Test Coverage}\n');
- var sep = ' +------------------------------------------+----------+------+------+--------+',
- lastSep = ' +----------+------+------+--------+';
- sys.puts(sep);
- sys.puts(' | filename | coverage | LOC | SLOC | missed |');
- sys.puts(sep);
- for (var name in cov) {
- var file = cov[name];
- if (Array.isArray(file)) {
- sys.print(' | ' + rpad(name, 40));
- sys.print(' | ' + lpad(file.coverage.toFixed(2), 8));
- sys.print(' | ' + lpad(file.LOC, 4));
- sys.print(' | ' + lpad(file.SLOC, 4));
- sys.print(' | ' + lpad(file.totalMisses, 6));
- sys.print(' |\n');
- }
- }
- sys.puts(sep);
- sys.print(' ' + rpad('', 40));
- sys.print(' | ' + lpad(cov.coverage.toFixed(2), 8));
- sys.print(' | ' + lpad(cov.LOC, 4));
- sys.print(' | ' + lpad(cov.SLOC, 4));
- sys.print(' | ' + lpad(cov.totalMisses, 6));
- sys.print(' |\n');
- sys.puts(lastSep);
- // Source
- for (var name in cov) {
- if (name.match(/\.js$/)) {
- var file = cov[name];
- if ((file.coverage < 100) || !quiet) {
- print('\n [bold]{' + name + '}:');
- print(file.source);
- sys.print('\n');
- }
- }
- }
- }
- /**
- * Populate code coverage data.
- *
- * @param {Object} cov
- */
- function populateCoverage(cov) {
- cov.LOC =
- cov.SLOC =
- cov.totalFiles =
- cov.totalHits =
- cov.totalMisses =
- cov.coverage = 0;
- for (var name in cov) {
- var file = cov[name];
- if (Array.isArray(file)) {
- // Stats
- ++cov.totalFiles;
- cov.totalHits += file.totalHits = coverage(file, true);
- cov.totalMisses += file.totalMisses = coverage(file, false);
- file.totalLines = file.totalHits + file.totalMisses;
- cov.SLOC += file.SLOC = file.totalLines;
- if (!file.source) file.source = [];
- cov.LOC += file.LOC = file.source.length;
- file.coverage = (file.totalHits / file.totalLines) * 100;
- // Source
- var width = file.source.length.toString().length;
- file.source = file.source.map(function(line, i){
- ++i;
- var hits = file[i] === 0 ? 0 : (file[i] || ' ');
- if (!boring) {
- if (hits === 0) {
- hits = '\x1b[31m' + hits + '\x1b[0m';
- line = '\x1b[41m' + line + '\x1b[0m';
- } else {
- hits = '\x1b[32m' + hits + '\x1b[0m';
- }
- }
- return '\n ' + lpad(i, width) + ' | ' + hits + ' | ' + line;
- }).join('');
- }
- }
- cov.coverage = (cov.totalHits / cov.SLOC) * 100;
- }
- /**
- * Total coverage for the given file data.
- *
- * @param {Array} data
- * @return {Type}
- */
- function coverage(data, val) {
- var n = 0;
- for (var i = 0, len = data.length; i < len; ++i) {
- if (data[i] !== undefined && data[i] == val) ++n;
- }
- return n;
- }
- /**
- * Test if all files have 100% coverage
- *
- * @param {Object} cov
- * @return {Boolean}
- */
- function hasFullCoverage(cov) {
- for (var name in cov) {
- var file = cov[name];
- if (file instanceof Array) {
- if (file.coverage !== 100) {
- return false;
- }
- }
- }
- return true;
- }
- /**
- * Run the given test `files`, or try _test/*_.
- *
- * @param {Array} files
- */
- function run(files) {
- cursor(false);
- if (!files.length) {
- try {
- files = fs.readdirSync('test').map(function(file){
- return 'test/' + file;
- });
- } catch (err) {
- print('\n failed to load tests in [bold]{./test}\n');
- ++failures;
- process.exit(1);
- }
- }
- runFiles(files);
- }
- /**
- * Show the cursor when `show` is true, otherwise hide it.
- *
- * @param {Boolean} show
- */
- function cursor(show) {
- if (show) {
- sys.print('\x1b[?25h');
- } else {
- sys.print('\x1b[?25l');
- }
- }
- /**
- * Run the given test `files`.
- *
- * @param {Array} files
- */
- function runFiles(files) {
- if (serial) {
- (function next(){
- if (files.length) {
- runFile(files.shift(), next);
- }
- })();
- } else {
- files.forEach(runFile);
- }
- }
- /**
- * Run tests for the given `file`, callback `fn()` when finished.
- *
- * @param {String} file
- * @param {Function} fn
- */
- function runFile(file, fn) {
- if (file.match(/\.js$/)) {
- var title = path.basename(file),
- file = path.join(cwd, file),
- mod = require(file.replace(/\.js$/, ''));
- (function check(){
- var len = Object.keys(mod).length;
- if (len) {
- runSuite(title, mod, fn);
- } else {
- setTimeout(check, 20);
- }
- })();
- }
- }
- /**
- * Report `err` for the given `test` and `suite`.
- *
- * @param {String} suite
- * @param {String} test
- * @param {Error} err
- */
- function error(suite, test, err) {
- ++failures;
- var name = err.name,
- stack = err.stack ? err.stack.replace(err.name, '') : '',
- label = test === 'uncaught'
- ? test
- : suite + ' ' + test;
- print('\n [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n');
- }
- /**
- * Run the given tests, callback `fn()` when finished.
- *
- * @param {String} title
- * @param {Object} tests
- * @param {Function} fn
- */
- var dots = 0;
- function runSuite(title, tests, fn) {
- // Keys
- var keys = only.length
- ? only.slice(0)
- : Object.keys(tests);
- // Setup
- var setup = tests.setup || function(fn){ fn(); };
- // Iterate tests
- (function next(){
- if (keys.length) {
- var key,
- test = tests[key = keys.shift()];
- // Non-tests
- if (key === 'setup') return next();
- // Run test
- if (test) {
- try {
- ++testcount;
- assert.testTitle = key;
- if (serial) {
- sys.print('.');
- if (++dots % 25 === 0) sys.print('\n');
- setup(function(){
- if (test.length < 1) {
- test();
- next();
- } else {
- var id = setTimeout(function(){
- throw new Error("'" + key + "' timed out");
- }, timeout);
- test(function(){
- clearTimeout(id);
- next();
- });
- }
- });
- } else {
- test(function(fn){
- process.on('beforeExit', function(){
- try {
- fn();
- } catch (err) {
- error(title, key, err);
- }
- });
- });
- }
- } catch (err) {
- error(title, key, err);
- }
- }
- if (!serial) next();
- } else if (serial) {
- fn();
- }
- })();
- }
- /**
- * Report exceptions.
- */
- function report() {
- cursor(true);
- process.emit('beforeExit');
- if (failures) {
- print('\n [bold]{Failures}: [red]{' + failures + '}\n\n');
- notify('Failures: ' + failures);
- } else {
- if (serial) print('');
- print('\n [green]{100%} ' + testcount + ' tests\n');
- notify('100% ok');
- }
- if (typeof _$jscoverage === 'object') {
- populateCoverage(_$jscoverage);
- if (!hasFullCoverage(_$jscoverage) || !quiet) {
- reportCoverage(_$jscoverage);
- }
- }
- }
- /**
- * Growl notify the given `msg`.
- *
- * @param {String} msg
- */
- function notify(msg) {
- if (growl) {
- childProcess.exec('growlnotify -name Expresso -m "' + msg + '"');
- }
- }
- // Report uncaught exceptions
- process.on('uncaughtException', function(err){
- error('uncaught', 'uncaught', err);
- });
- // Show cursor
- ['INT', 'TERM', 'QUIT'].forEach(function(sig){
- process.on('SIG' + sig, function(){
- cursor(true);
- process.exit(1);
- });
- });
- // Report test coverage when available
- // and emit "beforeExit" event to perform
- // final assertions
- var orig = process.emit;
- process.emit = function(event){
- if (event === 'exit') {
- report();
- process.reallyExit(failures);
- }
- orig.apply(this, arguments);
- };
- // Run test files
- if (!defer) run(files);