]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/node_modules/qs/support/expresso/bin/expresso
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / qs / support / expresso / bin / expresso
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
13 var 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
26 var version = '0.7.2';
27
28 /**
29  * Failure count.
30  */
31
32 var failures = 0;
33
34
35 /**
36  * Number of tests executed.
37  */
38
39 var testcount = 0;
40
41 /**
42  * Whitelist of tests to run.
43  */
44  
45 var only = [];
46
47 /**
48  * Boring output.
49  */
50  
51 var boring = false;
52
53 /**
54  * Growl notifications.
55  */
56
57 var growl = false;
58
59 /**
60  * Server port.
61  */
62
63 var port = 5555;
64
65 /**
66  * Execute serially.
67  */
68
69 var serial = false;
70
71 /**
72  * Default timeout.
73  */
74
75 var timeout = 2000;
76
77 /**
78  * Quiet output.
79  */
80
81 var quiet = false;
82
83 /**
84  * Usage documentation.
85  */
86
87 var 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
107 var files = [],
108     args = process.argv.slice(2);
109
110 while (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
204 function 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
216 function 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
227 assert.eql = assert.deepEqual;
228
229 /**
230  * Assert that `val` is null.
231  *
232  * @param {Mixed} val
233  * @param {String} msg
234  */
235
236 assert.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
247 assert.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
258 assert.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
269 assert.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
281 assert.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
295 assert.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
313 assert.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
326 assert.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
341 assert.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
477 function 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
493 function 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
507 function 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
553 function 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
599 function 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
614 function 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
632 function 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
654 function 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
668 function 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
687 function 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
711 function 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
729 var dots = 0;
730 function 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
795 function 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
820 function notify(msg) {
821     if (growl) {
822         childProcess.exec('growlnotify -name Expresso -m "' + msg + '"');
823     }
824 }
825
826 // Report uncaught exceptions
827
828 process.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
845 var orig = process.emit;
846 process.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
856 if (!defer) run(files);