PageRenderTime 20ms CodeModel.GetById 12ms app.highlight 3ms RepoModel.GetById 1ms app.codeStats 1ms

/js/lib/Socket.IO-node/support/expresso/bin/expresso

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
#! | 856 lines | 751 code | 105 blank | 0 comment | 0 complexity | 4c8f0d8373092338ca4e1393d65ff4e3 MD5 | raw file
  1#!/usr/bin/env node
  2
  3/*
  4 * Expresso
  5 * Copyright(c) TJ Holowaychuk <tj@vision-media.ca>
  6 * (MIT Licensed)
  7 */
  8 
  9/**
 10 * Module dependencies.
 11 */
 12
 13var assert = require('assert'),
 14    childProcess = require('child_process'),
 15    http = require('http'),
 16    path = require('path'),
 17    sys = require('sys'),
 18    cwd = process.cwd(),
 19    fs = require('fs'),
 20    defer;
 21
 22/**
 23 * Expresso version.
 24 */
 25
 26var version = '0.7.2';
 27
 28/**
 29 * Failure count.
 30 */
 31
 32var failures = 0;
 33
 34
 35/**
 36 * Number of tests executed.
 37 */
 38
 39var testcount = 0;
 40
 41/**
 42 * Whitelist of tests to run.
 43 */
 44 
 45var only = [];
 46
 47/**
 48 * Boring output.
 49 */
 50 
 51var boring = false;
 52
 53/**
 54 * Growl notifications.
 55 */
 56
 57var growl = false;
 58
 59/**
 60 * Server port.
 61 */
 62
 63var port = 5555;
 64
 65/**
 66 * Execute serially.
 67 */
 68
 69var serial = false;
 70
 71/**
 72 * Default timeout.
 73 */
 74
 75var timeout = 2000;
 76
 77/**
 78 * Quiet output.
 79 */
 80
 81var quiet = false;
 82
 83/**
 84 * Usage documentation.
 85 */
 86
 87var usage = ''
 88    + '[bold]{Usage}: expresso [options] <file ...>'
 89    + '\n'
 90    + '\n[bold]{Options}:'
 91    + '\n  -g, --growl          Enable growl notifications'
 92    + '\n  -c, --coverage       Generate and report test coverage'
 93    + '\n  -q, --quiet          Suppress coverage report if 100%'
 94    + '\n  -t, --timeout MS     Timeout in milliseconds, defaults to 2000'
 95    + '\n  -r, --require PATH   Require the given module path'
 96    + '\n  -o, --only TESTS     Execute only the comma sperated TESTS (can be set several times)'
 97    + '\n  -I, --include PATH   Unshift the given path to require.paths'
 98    + '\n  -p, --port NUM       Port number for test servers, starts at 5555'
 99    + '\n  -s, --serial         Execute tests serially'
100    + '\n  -b, --boring         Suppress ansi-escape colors'
101    + '\n  -v, --version        Output version number'
102    + '\n  -h, --help           Display help information'
103    + '\n';
104
105// Parse arguments
106
107var files = [],
108    args = process.argv.slice(2);
109
110while (args.length) {
111    var arg = args.shift();
112    switch (arg) {
113        case '-h':
114        case '--help':
115            print(usage + '\n');
116            process.exit(1);
117            break;
118        case '-v':
119        case '--version':
120            sys.puts(version);
121            process.exit(1);
122            break;
123        case '-i':
124        case '-I':
125        case '--include':
126            if (arg = args.shift()) {
127                require.paths.unshift(arg);
128            } else {
129                throw new Error('--include requires a path');
130            }
131            break;
132        case '-o':
133        case '--only':
134            if (arg = args.shift()) {
135                only = only.concat(arg.split(/ *, */));
136            } else {
137                throw new Error('--only requires comma-separated test names');
138            }
139            break;
140        case '-p':
141        case '--port':
142            if (arg = args.shift()) {
143                port = parseInt(arg, 10);
144            } else {
145                throw new Error('--port requires a number');
146            }
147            break;
148        case '-r':
149        case '--require':
150            if (arg = args.shift()) {
151                require(arg);
152            } else {
153                throw new Error('--require requires a path');
154            }
155            break;
156        case '-t':
157        case '--timeout':
158          if (arg = args.shift()) {
159            timeout = parseInt(arg, 10);
160          } else {
161            throw new Error('--timeout requires an argument');
162          }
163          break;
164        case '-c':
165        case '--cov':
166        case '--coverage':
167            defer = true;
168            childProcess.exec('rm -fr lib-cov && node-jscoverage lib lib-cov', function(err){
169                if (err) throw err;
170                require.paths.unshift('lib-cov');
171                run(files);
172            })
173            break;
174        case '-q':
175        case '--quiet':
176            quiet = true;
177            break;
178        case '-b':
179        case '--boring':
180        	boring = true;
181        	break;
182        case '-g':
183        case '--growl':
184            growl = true;
185            break;
186        case '-s':
187        case '--serial':
188            serial = true;
189            break;
190        default:
191            if (/\.js$/.test(arg)) {
192                files.push(arg);
193            }
194            break;
195    }
196}
197
198/**
199 * Colorized sys.error().
200 *
201 * @param {String} str
202 */
203
204function print(str){
205    sys.error(colorize(str));
206}
207
208/**
209 * Colorize the given string using ansi-escape sequences.
210 * Disabled when --boring is set.
211 *
212 * @param {String} str
213 * @return {String}
214 */
215
216function colorize(str){
217    var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
218    return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str){
219        return boring
220            ? str
221            : '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
222    });
223}
224
225// Alias deepEqual as eql for complex equality
226
227assert.eql = assert.deepEqual;
228
229/**
230 * Assert that `val` is null.
231 *
232 * @param {Mixed} val
233 * @param {String} msg
234 */
235
236assert.isNull = function(val, msg) {
237    assert.strictEqual(null, val, msg);
238};
239
240/**
241 * Assert that `val` is not null.
242 *
243 * @param {Mixed} val
244 * @param {String} msg
245 */
246
247assert.isNotNull = function(val, msg) {
248    assert.notStrictEqual(null, val, msg);
249};
250
251/**
252 * Assert that `val` is undefined.
253 *
254 * @param {Mixed} val
255 * @param {String} msg
256 */
257
258assert.isUndefined = function(val, msg) {
259    assert.strictEqual(undefined, val, msg);
260};
261
262/**
263 * Assert that `val` is not undefined.
264 *
265 * @param {Mixed} val
266 * @param {String} msg
267 */
268
269assert.isDefined = function(val, msg) {
270    assert.notStrictEqual(undefined, val, msg);
271};
272
273/**
274 * Assert that `obj` is `type`.
275 *
276 * @param {Mixed} obj
277 * @param {String} type
278 * @api public
279 */
280
281assert.type = function(obj, type, msg){
282    var real = typeof obj;
283    msg = msg || 'typeof ' + sys.inspect(obj) + ' is ' + real + ', expected ' + type;
284    assert.ok(type === real, msg);
285};
286
287/**
288 * Assert that `str` matches `regexp`.
289 *
290 * @param {String} str
291 * @param {RegExp} regexp
292 * @param {String} msg
293 */
294
295assert.match = function(str, regexp, msg) {
296    msg = msg || sys.inspect(str) + ' does not match ' + sys.inspect(regexp);
297    assert.ok(regexp.test(str), msg);
298};
299
300/**
301 * Assert that `val` is within `obj`.
302 *
303 * Examples:
304 *
305 *    assert.includes('foobar', 'bar');
306 *    assert.includes(['foo', 'bar'], 'foo');
307 *
308 * @param {String|Array} obj
309 * @param {Mixed} val
310 * @param {String} msg
311 */
312
313assert.includes = function(obj, val, msg) {
314    msg = msg || sys.inspect(obj) + ' does not include ' + sys.inspect(val);
315    assert.ok(obj.indexOf(val) >= 0, msg);
316};
317
318/**
319 * Assert length of `val` is `n`.
320 *
321 * @param {Mixed} val
322 * @param {Number} n
323 * @param {String} msg
324 */
325
326assert.length = function(val, n, msg) {
327    msg = msg || sys.inspect(val) + ' has length of ' + val.length + ', expected ' + n;
328    assert.equal(n, val.length, msg);
329};
330
331/**
332 * Assert response from `server` with
333 * the given `req` object and `res` assertions object.
334 *
335 * @param {Server} server
336 * @param {Object} req
337 * @param {Object|Function} res
338 * @param {String} msg
339 */
340
341assert.response = function(server, req, res, msg){
342    // Check that the server is ready or defer
343    if (!server.fd) {
344        if (!('__deferred' in server)) {
345            server.__deferred = [];
346        }
347        server.__deferred.push(arguments);
348        if (!server.__started) {
349            server.listen(server.__port = port++, '127.0.0.1', function(){
350                if (server.__deferred) {
351                    process.nextTick(function(){
352                        server.__deferred.forEach(function(args){
353                          assert.response.apply(assert, args);
354                        });
355                    });
356                }
357            });
358            server.__started = true;
359        }
360        return;
361    }
362
363    // Callback as third or fourth arg
364    var callback = typeof res === 'function'
365        ? res
366        : typeof msg === 'function'
367            ? msg
368            : function(){};
369
370    // Default messate to test title
371    if (typeof msg === 'function') msg = null;
372    msg = msg || assert.testTitle;
373    msg += '. ';
374
375    // Pending responses
376    server.__pending = server.__pending || 0;
377    server.__pending++;
378
379    // Create client
380    if (!server.fd) {
381        server.listen(server.__port = port++, '127.0.0.1', issue);
382    } else {
383        issue();
384    }
385
386    function issue(){
387        if (!server.client)
388            server.client = http.createClient(server.__port);
389
390        // Issue request
391        var timer,
392            client = server.client,
393            method = req.method || 'GET',
394            status = res.status || res.statusCode,
395            data = req.data || req.body,
396            requestTimeout = req.timeout || 0;
397
398        var request = client.request(method, req.url, req.headers);
399
400        // Timeout
401        if (requestTimeout) {
402            timer = setTimeout(function(){
403                --server.__pending || server.close();
404                delete req.timeout;
405                assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
406            }, requestTimeout);
407        }
408
409        if (data) request.write(data);
410        request.on('response', function(response){
411            response.body = '';
412            response.setEncoding('utf8');
413            response.on('data', function(chunk){ response.body += chunk; });
414            response.on('end', function(){
415                --server.__pending || server.close();
416                if (timer) clearTimeout(timer);
417
418                // Assert response body
419                if (res.body !== undefined) {
420                    var eql = res.body instanceof RegExp
421                      ? res.body.test(response.body)
422                      : res.body === response.body;
423                    assert.ok(
424                        eql,
425                        msg + 'Invalid response body.\n'
426                            + '    Expected: ' + sys.inspect(res.body) + '\n'
427                            + '    Got: ' + sys.inspect(response.body)
428                    );
429                }
430
431                // Assert response status
432                if (typeof status === 'number') {
433                    assert.equal(
434                        response.statusCode,
435                        status,
436                        msg + colorize('Invalid response status code.\n'
437                            + '    Expected: [green]{' + status + '}\n'
438                            + '    Got: [red]{' + response.statusCode + '}')
439                    );
440                }
441
442                // Assert response headers
443                if (res.headers) {
444                    var keys = Object.keys(res.headers);
445                    for (var i = 0, len = keys.length; i < len; ++i) {
446                        var name = keys[i],
447                            actual = response.headers[name.toLowerCase()],
448                            expected = res.headers[name],
449                            eql = expected instanceof RegExp
450                              ? expected.test(actual)
451                              : expected == actual;
452                        assert.ok(
453                            eql,
454                            msg + colorize('Invalid response header [bold]{' + name + '}.\n'
455                                + '    Expected: [green]{' + expected + '}\n'
456                                + '    Got: [red]{' + actual + '}')
457                        );
458                    }
459                }
460
461                // Callback
462                callback(response);
463            });
464        });
465        request.end();
466      }
467};
468
469/**
470 * Pad the given string to the maximum width provided.
471 *
472 * @param  {String} str
473 * @param  {Number} width
474 * @return {String}
475 */
476
477function lpad(str, width) {
478    str = String(str);
479    var n = width - str.length;
480    if (n < 1) return str;
481    while (n--) str = ' ' + str;
482    return str;
483}
484
485/**
486 * Pad the given string to the maximum width provided.
487 *
488 * @param  {String} str
489 * @param  {Number} width
490 * @return {String}
491 */
492
493function rpad(str, width) {
494    str = String(str);
495    var n = width - str.length;
496    if (n < 1) return str;
497    while (n--) str = str + ' ';
498    return str;
499}
500
501/**
502 * Report test coverage.
503 *
504 * @param  {Object} cov
505 */
506
507function reportCoverage(cov) {
508    // Stats
509    print('\n   [bold]{Test Coverage}\n');
510    var sep = '   +------------------------------------------+----------+------+------+--------+',
511        lastSep = '                                              +----------+------+------+--------+';
512    sys.puts(sep);
513    sys.puts('   | filename                                 | coverage | LOC  | SLOC | missed |');
514    sys.puts(sep);
515    for (var name in cov) {
516        var file = cov[name];
517        if (Array.isArray(file)) {
518            sys.print('   | ' + rpad(name, 40));
519            sys.print(' | ' + lpad(file.coverage.toFixed(2), 8));
520            sys.print(' | ' + lpad(file.LOC, 4));
521            sys.print(' | ' + lpad(file.SLOC, 4));
522            sys.print(' | ' + lpad(file.totalMisses, 6));
523            sys.print(' |\n');
524        }
525    }
526    sys.puts(sep);
527    sys.print('     ' + rpad('', 40));
528    sys.print(' | ' + lpad(cov.coverage.toFixed(2), 8));
529    sys.print(' | ' + lpad(cov.LOC, 4));
530    sys.print(' | ' + lpad(cov.SLOC, 4));
531    sys.print(' | ' + lpad(cov.totalMisses, 6));
532    sys.print(' |\n');
533    sys.puts(lastSep);
534    // Source
535    for (var name in cov) {
536        if (name.match(/\.js$/)) {
537            var file = cov[name];
538            if ((file.coverage < 100) || !quiet) {
539               print('\n   [bold]{' + name + '}:'); 
540               print(file.source);
541               sys.print('\n');
542            }
543        }
544    }
545}
546
547/**
548 * Populate code coverage data.
549 *
550 * @param  {Object} cov
551 */
552
553function populateCoverage(cov) {
554    cov.LOC = 
555    cov.SLOC =
556    cov.totalFiles =
557    cov.totalHits =
558    cov.totalMisses = 
559    cov.coverage = 0;
560    for (var name in cov) {
561        var file = cov[name];
562        if (Array.isArray(file)) {
563            // Stats
564            ++cov.totalFiles;
565            cov.totalHits += file.totalHits = coverage(file, true);
566            cov.totalMisses += file.totalMisses = coverage(file, false);
567            file.totalLines = file.totalHits + file.totalMisses;
568            cov.SLOC += file.SLOC = file.totalLines;
569            if (!file.source) file.source = [];
570            cov.LOC += file.LOC = file.source.length;
571            file.coverage = (file.totalHits / file.totalLines) * 100;
572            // Source
573            var width = file.source.length.toString().length;
574            file.source = file.source.map(function(line, i){
575                ++i;
576                var hits = file[i] === 0 ? 0 : (file[i] || ' ');
577                if (!boring) {
578                    if (hits === 0) {
579                        hits = '\x1b[31m' + hits + '\x1b[0m';
580                        line = '\x1b[41m' + line + '\x1b[0m';
581                    } else {
582                        hits = '\x1b[32m' + hits + '\x1b[0m';
583                    }
584                }
585                return '\n     ' + lpad(i, width) + ' | ' + hits + ' | ' + line;
586            }).join('');
587        }
588    }
589    cov.coverage = (cov.totalHits / cov.SLOC) * 100;
590}
591
592/**
593 * Total coverage for the given file data.
594 *
595 * @param  {Array} data
596 * @return {Type}
597 */
598
599function coverage(data, val) {
600    var n = 0;
601    for (var i = 0, len = data.length; i < len; ++i) {
602        if (data[i] !== undefined && data[i] == val) ++n;
603    }
604    return n;  
605}
606
607/**
608 * Test if all files have 100% coverage
609 *
610 * @param  {Object} cov
611 * @return {Boolean}
612 */
613
614function hasFullCoverage(cov) {
615  for (var name in cov) {
616    var file = cov[name];
617    if (file instanceof Array) {
618      if (file.coverage !== 100) {
619	      return false;
620      }
621    }
622  }
623  return true;
624}
625
626/**
627 * Run the given test `files`, or try _test/*_.
628 *
629 * @param  {Array} files
630 */
631
632function run(files) {
633    cursor(false);
634    if (!files.length) {
635        try {
636            files = fs.readdirSync('test').map(function(file){
637                return 'test/' + file;
638            });
639        } catch (err) {
640            print('\n  failed to load tests in [bold]{./test}\n');
641            ++failures;
642            process.exit(1);
643        }
644    }
645    runFiles(files);
646}
647
648/**
649 * Show the cursor when `show` is true, otherwise hide it.
650 *
651 * @param {Boolean} show
652 */
653
654function cursor(show) {
655    if (show) {
656        sys.print('\x1b[?25h');
657    } else {
658        sys.print('\x1b[?25l');
659    }
660}
661
662/**
663 * Run the given test `files`.
664 *
665 * @param {Array} files
666 */
667
668function runFiles(files) {
669    if (serial) {
670        (function next(){
671            if (files.length) {
672                runFile(files.shift(), next);
673            }
674        })();
675    } else {
676      files.forEach(runFile);
677    }
678}
679
680/**
681 * Run tests for the given `file`, callback `fn()` when finished.
682 *
683 * @param {String} file
684 * @param {Function} fn
685 */
686
687function runFile(file, fn) {
688    if (file.match(/\.js$/)) {
689        var title = path.basename(file),
690            file = path.join(cwd, file),
691            mod = require(file.replace(/\.js$/, ''));
692        (function check(){
693           var len = Object.keys(mod).length;
694           if (len) {
695               runSuite(title, mod, fn);
696           } else {
697               setTimeout(check, 20);
698           }
699        })();
700    }
701}
702
703/**
704 * Report `err` for the given `test` and `suite`.
705 *
706 * @param {String} suite
707 * @param {String} test
708 * @param {Error} err
709 */
710
711function error(suite, test, err) {
712    ++failures;
713    var name = err.name,
714        stack = err.stack ? err.stack.replace(err.name, '') : '',
715        label = test === 'uncaught'
716            ? test
717            : suite + ' ' + test;
718    print('\n   [bold]{' + label + '}: [red]{' + name + '}' + stack + '\n');
719}
720
721/**
722 * Run the given tests, callback `fn()` when finished.
723 *
724 * @param  {String} title
725 * @param  {Object} tests
726 * @param  {Function} fn
727 */
728
729var dots = 0;
730function runSuite(title, tests, fn) {
731    // Keys
732    var keys = only.length
733        ? only.slice(0)
734        : Object.keys(tests);
735
736    // Setup
737    var setup = tests.setup || function(fn){ fn(); };
738
739    // Iterate tests
740    (function next(){
741        if (keys.length) {
742            var key,
743                test = tests[key = keys.shift()];
744            // Non-tests
745            if (key === 'setup') return next();
746
747            // Run test
748            if (test) {
749                try {
750                    ++testcount;
751                    assert.testTitle = key;
752                    if (serial) {
753                        sys.print('.');
754                        if (++dots % 25 === 0) sys.print('\n');
755                        setup(function(){
756                            if (test.length < 1) {
757                                test();
758                                next();
759                            } else {
760                                var id = setTimeout(function(){
761                                    throw new Error("'" + key + "' timed out");
762                                }, timeout);
763                                test(function(){
764                                    clearTimeout(id);
765                                    next();
766                                });
767                            } 
768                        });
769                    } else {
770                        test(function(fn){
771                            process.on('beforeExit', function(){
772                                try {
773                                    fn();
774                                } catch (err) {
775                                    error(title, key, err);
776                                }
777                            });
778                        });
779                    }
780                } catch (err) {
781                    error(title, key, err);
782                }
783            }
784            if (!serial) next();
785        } else if (serial) {
786          fn();
787        }
788    })();
789}
790
791/**
792 * Report exceptions.
793 */
794
795function report() {
796    cursor(true);
797    process.emit('beforeExit');
798    if (failures) {
799        print('\n   [bold]{Failures}: [red]{' + failures + '}\n\n');
800        notify('Failures: ' + failures);
801    } else {
802        if (serial) print('');
803        print('\n   [green]{100%} ' + testcount + ' tests\n');
804        notify('100% ok');
805    }
806    if (typeof _$jscoverage === 'object') {
807        populateCoverage(_$jscoverage);
808        if (!hasFullCoverage(_$jscoverage) || !quiet) {
809            reportCoverage(_$jscoverage);
810        }
811    }
812}
813
814/**
815 * Growl notify the given `msg`.
816 *
817 * @param {String} msg
818 */
819
820function notify(msg) {
821    if (growl) {
822        childProcess.exec('growlnotify -name Expresso -m "' + msg + '"');
823    }
824}
825
826// Report uncaught exceptions
827
828process.on('uncaughtException', function(err){
829    error('uncaught', 'uncaught', err);
830});
831
832// Show cursor
833
834['INT', 'TERM', 'QUIT'].forEach(function(sig){
835    process.on('SIG' + sig, function(){
836        cursor(true);
837        process.exit(1);
838    });
839});
840
841// Report test coverage when available
842// and emit "beforeExit" event to perform
843// final assertions
844
845var orig = process.emit;
846process.emit = function(event){
847    if (event === 'exit') {
848        report();
849        process.reallyExit(failures);
850    }
851    orig.apply(this, arguments);
852};
853
854// Run test files
855
856if (!defer) run(files);