PageRenderTime 39ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

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