_mocha 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #!/usr/bin/env node
  2. /**
  3. * Module dependencies.
  4. */
  5. var program = require('commander')
  6. , sprintf = require('util').format
  7. , path = require('path')
  8. , fs = require('fs')
  9. , glob = require('glob')
  10. , resolve = path.resolve
  11. , exists = fs.existsSync || path.existsSync
  12. , Mocha = require('../')
  13. , utils = Mocha.utils
  14. , reporters = Mocha.reporters
  15. , interfaces = Mocha.interfaces
  16. , Base = reporters.Base
  17. , join = path.join
  18. , basename = path.basename
  19. , cwd = process.cwd()
  20. , mocha = new Mocha;
  21. /**
  22. * Save timer references to avoid Sinon interfering (see GH-237).
  23. */
  24. var Date = global.Date
  25. , setTimeout = global.setTimeout
  26. , setInterval = global.setInterval
  27. , clearTimeout = global.clearTimeout
  28. , clearInterval = global.clearInterval;
  29. /**
  30. * Files.
  31. */
  32. var files = [];
  33. /**
  34. * Globals.
  35. */
  36. var globals = [];
  37. /**
  38. * Requires.
  39. */
  40. var requires = [];
  41. /**
  42. * Images.
  43. */
  44. var images = {
  45. fail: __dirname + '/../images/error.png'
  46. , pass: __dirname + '/../images/ok.png'
  47. };
  48. // options
  49. program
  50. .version(JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version)
  51. .usage('[debug] [options] [files]')
  52. .option('-r, --require <name>', 'require the given module')
  53. .option('-R, --reporter <name>', 'specify the reporter to use', 'dot')
  54. .option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd')
  55. .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
  56. .option('-i, --invert', 'inverts --grep matches')
  57. .option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]')
  58. .option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]')
  59. .option('-w, --watch', 'watch files for changes')
  60. .option('-c, --colors', 'force enabling of colors')
  61. .option('-C, --no-colors', 'force disabling of colors')
  62. .option('-G, --growl', 'enable growl notification support')
  63. .option('-d, --debug', "enable node's debugger, synonym for node --debug")
  64. .option('-b, --bail', "bail after first test failure")
  65. .option('-A, --async-only', "force all tests to take a callback (async)")
  66. .option('--recursive', 'include sub directories')
  67. .option('--debug-brk', "enable node's debugger breaking on the first line")
  68. .option('--globals <names>', 'allow the given comma-delimited global [names]', list, [])
  69. .option('--check-leaks', 'check for global variable leaks')
  70. .option('--interfaces', 'display available interfaces')
  71. .option('--reporters', 'display available reporters')
  72. .option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, [])
  73. program.name = 'mocha';
  74. // init command
  75. program
  76. .command('init <path>')
  77. .description('initialize a client-side mocha setup at <path>')
  78. .action(function(path){
  79. var mkdir = require('mkdirp');
  80. mkdir.sync(path);
  81. var css = fs.readFileSync(join(__dirname, '..', 'mocha.css'));
  82. var js = fs.readFileSync(join(__dirname, '..', 'mocha.js'));
  83. var tmpl = fs.readFileSync(join(__dirname, '..', 'lib/template.html'));
  84. fs.writeFileSync(join(path, 'mocha.css'), css);
  85. fs.writeFileSync(join(path, 'mocha.js'), js);
  86. fs.writeFileSync(join(path, 'tests.js'), '');
  87. fs.writeFileSync(join(path, 'index.html'), tmpl);
  88. process.exit(0);
  89. });
  90. // --globals
  91. program.on('globals', function(val){
  92. globals = globals.concat(list(val));
  93. });
  94. // --reporters
  95. program.on('reporters', function(){
  96. console.log();
  97. console.log(' dot - dot matrix');
  98. console.log(' doc - html documentation');
  99. console.log(' spec - hierarchical spec list');
  100. console.log(' json - single json object');
  101. console.log(' progress - progress bar');
  102. console.log(' list - spec-style listing');
  103. console.log(' tap - test-anything-protocol');
  104. console.log(' landing - unicode landing strip');
  105. console.log(' xunit - xunit reporter');
  106. console.log(' teamcity - teamcity ci support');
  107. console.log(' html-cov - HTML test coverage');
  108. console.log(' json-cov - JSON test coverage');
  109. console.log(' min - minimal reporter (great with --watch)');
  110. console.log(' json-stream - newline delimited json events');
  111. console.log(' markdown - markdown documentation (github flavour)');
  112. console.log(' nyan - nyan cat!');
  113. console.log();
  114. process.exit();
  115. });
  116. // --interfaces
  117. program.on('interfaces', function(){
  118. console.log('');
  119. console.log(' bdd');
  120. console.log(' tdd');
  121. console.log(' qunit');
  122. console.log(' exports');
  123. console.log('');
  124. process.exit();
  125. });
  126. // -r, --require
  127. module.paths.push(cwd, join(cwd, 'node_modules'));
  128. program.on('require', function(mod){
  129. var abs = exists(mod) || exists(mod + '.js');
  130. if (abs) mod = resolve(mod);
  131. requires.push(mod);
  132. });
  133. // mocha.opts support
  134. try {
  135. var opts = fs.readFileSync('test/mocha.opts', 'utf8')
  136. .trim()
  137. .split(/\s+/);
  138. process.argv = process.argv
  139. .slice(0, 2)
  140. .concat(opts.concat(process.argv.slice(2)));
  141. } catch (err) {
  142. // ignore
  143. }
  144. // parse args
  145. program.parse(process.argv);
  146. // infinite stack traces
  147. Error.stackTraceLimit = Infinity; // TODO: config
  148. // reporter
  149. mocha.reporter(program.reporter);
  150. // interface
  151. mocha.ui(program.ui);
  152. // load reporter
  153. try {
  154. Reporter = require('../lib/reporters/' + program.reporter);
  155. } catch (err) {
  156. try {
  157. Reporter = require(program.reporter);
  158. } catch (err) {
  159. throw new Error('reporter "' + program.reporter + '" does not exist');
  160. }
  161. }
  162. // --no-colors
  163. if (!program.colors) Base.useColors = false;
  164. // --colors
  165. if (~process.argv.indexOf('--colors') ||
  166. ~process.argv.indexOf('-c')) {
  167. Base.useColors = true;
  168. }
  169. // --slow <ms>
  170. if (program.slow) mocha.suite.slow(program.slow);
  171. // --timeout
  172. if (program.timeout) mocha.suite.timeout(program.timeout);
  173. // --bail
  174. mocha.suite.bail(program.bail);
  175. // --grep
  176. if (program.grep) mocha.grep(new RegExp(program.grep));
  177. // --invert
  178. if (program.invert) mocha.invert();
  179. // --check-leaks
  180. if (program.checkLeaks) mocha.checkLeaks();
  181. // --growl
  182. if (program.growl) mocha.growl();
  183. // --async-only
  184. if (program.asyncOnly) mocha.asyncOnly();
  185. // --globals
  186. mocha.globals(globals);
  187. // custom compiler support
  188. var extensions = ['js'];
  189. program.compilers.forEach(function(c) {
  190. var compiler = c.split(':')
  191. , ext = compiler[0]
  192. , mod = compiler[1];
  193. if (mod[0] == '.') mod = join(process.cwd(), mod);
  194. require(mod);
  195. extensions.push(ext);
  196. });
  197. var re = new RegExp('\\.(' + extensions.join('|') + ')$');
  198. // requires
  199. requires.forEach(function(mod) {
  200. require(mod);
  201. });
  202. // files
  203. var files = []
  204. , args = program.args;
  205. // default files to test/*.{js,coffee}
  206. if (!args.length) args.push('test');
  207. args.forEach(function(arg){
  208. files = files.concat(lookupFiles(arg, program.recursive));
  209. });
  210. // resolve
  211. files = files.map(function(path){
  212. return resolve(path);
  213. });
  214. // --watch
  215. if (program.watch) {
  216. console.log();
  217. hideCursor();
  218. process.on('SIGINT', function(){
  219. showCursor();
  220. console.log('\n');
  221. process.exit();
  222. });
  223. var spinner = 'win32' == process.platform
  224. ? ['|','/','-','\\']
  225. : ['◜','◠','◝','◞','◡','◟'];
  226. var frames = spinner.map(function(c) {
  227. return sprintf(' \u001b[96m%s \u001b[90mwatching\u001b[0m', c);
  228. });
  229. var watchFiles = utils.files(cwd);
  230. function loadAndRun() {
  231. try {
  232. mocha.files = files;
  233. mocha.run(function(){
  234. play(frames);
  235. });
  236. } catch(e) {
  237. console.log(e.stack);
  238. }
  239. }
  240. function purge() {
  241. watchFiles.forEach(function(file){
  242. delete require.cache[file];
  243. });
  244. }
  245. loadAndRun();
  246. utils.watch(watchFiles, function(){
  247. purge();
  248. stop()
  249. mocha.suite = mocha.suite.clone();
  250. mocha.ui(program.ui);
  251. loadAndRun();
  252. });
  253. return;
  254. }
  255. // load
  256. mocha.files = files;
  257. mocha.run(process.exit);
  258. // enable growl notifications
  259. function growl(runner, reporter) {
  260. var notify = require('growl');
  261. runner.on('end', function(){
  262. var stats = reporter.stats;
  263. if (stats.failures) {
  264. var msg = stats.failures + ' of ' + runner.total + ' tests failed';
  265. notify(msg, { name: 'mocha', title: 'Failed', image: images.fail });
  266. } else {
  267. notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
  268. name: 'mocha'
  269. , title: 'Passed'
  270. , image: images.pass
  271. });
  272. }
  273. });
  274. }
  275. /**
  276. * Parse list.
  277. */
  278. function list(str) {
  279. return str.split(/ *, */);
  280. }
  281. /**
  282. * Hide the cursor.
  283. */
  284. function hideCursor(){
  285. process.stdout.write('\u001b[?25l');
  286. };
  287. /**
  288. * Show the cursor.
  289. */
  290. function showCursor(){
  291. process.stdout.write('\u001b[?25h');
  292. };
  293. /**
  294. * Stop play()ing.
  295. */
  296. function stop() {
  297. process.stdout.write('\u001b[2K');
  298. clearInterval(play.timer);
  299. }
  300. /**
  301. * Lookup file names at the given `path`.
  302. */
  303. function lookupFiles(path, recursive) {
  304. var files = [];
  305. if (!exists(path)) {
  306. if (exists(path + '.js')) {
  307. path += '.js'
  308. } else {
  309. return glob.sync(path);
  310. }
  311. }
  312. var stat = fs.statSync(path);
  313. if (stat.isFile()) return path;
  314. fs.readdirSync(path).forEach(function(file){
  315. file = join(path, file);
  316. var stat = fs.statSync(file);
  317. if (stat.isDirectory()) {
  318. if (recursive) files = files.concat(lookupFiles(file, recursive));
  319. return
  320. }
  321. if (!stat.isFile() || !re.test(file) || basename(file)[0] == '.') return;
  322. files.push(file);
  323. });
  324. return files;
  325. }
  326. /**
  327. * Play the given array of strings.
  328. */
  329. function play(arr, interval) {
  330. var len = arr.length
  331. , interval = interval || 100
  332. , i = 0;
  333. play.timer = setInterval(function(){
  334. var str = arr[i++ % len];
  335. process.stdout.write('\u001b[0G' + str);
  336. }, interval);
  337. }