run_from_console.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #!/usr/bin/env node
  2. var ansi = require('ansi');
  3. var program = require('commander');
  4. var path = require('path');
  5. var fs = require('fs');
  6. var make_list = function(val) {
  7. return val.split(',');
  8. };
  9. program
  10. .option('-t, --tests <testlist>', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, [])
  11. .option('-a, --print-all', 'Print all tests, not just the failures')
  12. .option('--disable-color', 'Explicitly disable color')
  13. .option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)')
  14. .option('-i, --auto-inject <includefiles>', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null)
  15. .option('-p, --provider <name>', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper')
  16. .option('-g, --generate-html', 'Instead of running the tests, just return the path to the generated HTML file, then wait for user interaction to exit (should be used with .js tests).')
  17. .option('-o, --open-in-browser', 'Open the generated HTML files in a web browser using the "open" module (must be used with the "-g"/"--generate-html" option).')
  18. .option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)')
  19. .option('-d, --debug', 'Show debug output (the "console" event) from the provider')
  20. .option('-r, --relative', 'Use relative paths in the generated HTML file')
  21. .option('--debugger <port>', 'Enable the remote debugger for CasperJS')
  22. .parse(process.argv);
  23. if (program.tests.length === 0) {
  24. program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); });
  25. program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in
  26. console.log('using files %s', program.tests);
  27. }
  28. var file_paths = [];
  29. var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true);
  30. var get_path = function (/* arguments */) {
  31. if (program.relative) {
  32. return path.join.apply(null, arguments);
  33. } else {
  34. var args = Array.prototype.slice.call(arguments);
  35. args.unshift(__dirname, '..');
  36. return path.resolve.apply(null, args);
  37. }
  38. };
  39. var get_path_cwd = function (/* arguments */) {
  40. if (program.relative) {
  41. var part_path = path.join.apply(null, arguments);
  42. return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path));
  43. } else {
  44. var args = Array.prototype.slice.call(arguments);
  45. args.unshift(process.cwd());
  46. return path.resolve.apply(null, args);
  47. }
  48. };
  49. if (all_js && !program.autoInject) {
  50. var all_modules = {};
  51. // uses the first instance of the string 'requires local modules: '
  52. program.tests.forEach(function (testname) {
  53. var full_path = path.resolve(process.cwd(), testname);
  54. var content = fs.readFileSync(full_path).toString();
  55. var ind = content.indexOf('requires local modules: ');
  56. if (ind > -1) {
  57. ind += 'requires local modules: '.length;
  58. var eol = content.indexOf('\n', ind);
  59. var modules = content.slice(ind, eol).split(/,\s*/);
  60. modules.forEach(function (mod) {
  61. all_modules[get_path('include/', mod) + '.js'] = 1;
  62. });
  63. }
  64. var fakes_ind = content.indexOf('requires test modules: ');
  65. if (fakes_ind > -1) {
  66. fakes_ind += 'requires test modules: '.length;
  67. var fakes_eol = content.indexOf('\n', fakes_ind);
  68. var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/);
  69. fakes_modules.forEach(function (mod) {
  70. all_modules[get_path('tests/', mod) + '.js'] = 1;
  71. });
  72. }
  73. });
  74. program.autoInject = Object.keys(all_modules);
  75. }
  76. if (program.autoInject) {
  77. var temp = require('temp');
  78. temp.track();
  79. var template = {
  80. header: "<html>\n<head>\n<meta charset='utf-8' />\n<link rel='stylesheet' href='" + get_path('node_modules/mocha/mocha.css') + "'/>\n</head>\n<body><div id='mocha'></div>",
  81. script_tag: function(p) { return "<script src='" + p + "'></script>"; },
  82. footer: "<script>\nmocha.checkLeaks();\nmocha.globals(['navigator', 'create', 'ClientUtils', '__utils__']);\nmocha.run(function () { window.__mocha_done = true; });\n</script>\n</body>\n</html>"
  83. };
  84. template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js'));
  85. template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
  86. template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
  87. template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
  88. template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
  89. template.header += "\n<script>mocha.setup('bdd');</script>";
  90. template.header = program.autoInject.reduce(function(acc, sn) {
  91. return acc + "\n" + template.script_tag(get_path_cwd(sn));
  92. }, template.header);
  93. file_paths = program.tests.map(function(jsn, ind) {
  94. var templ = template.header;
  95. templ += "\n";
  96. templ += template.script_tag(get_path_cwd(jsn));
  97. templ += template.footer;
  98. var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' });
  99. fs.writeSync(tempfile.fd, templ);
  100. fs.closeSync(tempfile.fd);
  101. return tempfile.path;
  102. });
  103. }
  104. else {
  105. file_paths = program.tests.map(function(fn) {
  106. return path.resolve(process.cwd(), fn);
  107. });
  108. }
  109. var use_ansi = false;
  110. if (program.color) use_ansi = true;
  111. else if (program.disableColor) use_ansi = false;
  112. else if (process.stdout.isTTY) use_ansi = true;
  113. var cursor = ansi(process.stdout, { enabled: use_ansi });
  114. if (program.outputHtml) {
  115. file_paths.forEach(function(path, path_ind) {
  116. fs.readFile(path, function(err, data) {
  117. if (err) {
  118. console.warn(error.stack);
  119. return;
  120. }
  121. if (use_ansi) {
  122. cursor
  123. .bold()
  124. .write(program.tests[path_ind])
  125. .reset()
  126. .write("\n")
  127. .write(Array(program.tests[path_ind].length+1).join('='))
  128. .write("\n\n");
  129. }
  130. cursor
  131. .write(data)
  132. .write("\n\n");
  133. });
  134. });
  135. }
  136. if (program.generateHtml) {
  137. var open_browser;
  138. if (program.openInBrowser) {
  139. open_browser = require('open');
  140. }
  141. file_paths.forEach(function(path, path_ind) {
  142. cursor
  143. .bold()
  144. .write(program.tests[path_ind])
  145. .write(": ")
  146. .reset()
  147. .write(path)
  148. .write("\n");
  149. if (program.openInBrowser) {
  150. open_browser(path);
  151. }
  152. });
  153. console.log('');
  154. }
  155. if (program.generateHtml) {
  156. process.stdin.resume(); // pause until C-c
  157. process.on('SIGINT', function() {
  158. process.stdin.pause(); // exit
  159. });
  160. }
  161. if (!program.outputHtml && !program.generateHtml) {
  162. var failure_count = 0;
  163. var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js'));
  164. cursor
  165. .write("Running tests ")
  166. .bold()
  167. .write(program.tests.join(', '))
  168. .reset()
  169. .grey()
  170. .write(' using provider '+prov.name)
  171. .reset()
  172. .write("\n");
  173. //console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
  174. var provider = prov.provide_emitter(file_paths, program.debugger);
  175. provider.on('test_ready', function(test_json) {
  176. console.log('');
  177. filename = program.tests[test_json.file_ind];
  178. cursor.bold();
  179. console.log('Results for %s:', filename);
  180. console.log(Array('Results for :'.length+filename.length+1).join('='));
  181. cursor.reset();
  182. console.log('');
  183. cursor
  184. .write(''+test_json.num_tests+' tests run, ')
  185. .green()
  186. .write(''+test_json.num_passes+' passed');
  187. if (test_json.num_slow > 0) {
  188. cursor
  189. .reset()
  190. .write(' (');
  191. cursor
  192. .yellow()
  193. .write(''+test_json.num_slow+' slow')
  194. .reset()
  195. .write(')');
  196. }
  197. cursor
  198. .reset()
  199. .write(', ');
  200. cursor
  201. .red()
  202. .write(''+test_json.num_fails+' failed');
  203. if (test_json.num_skipped > 0) {
  204. cursor
  205. .reset()
  206. .write(', ')
  207. .grey()
  208. .write(''+test_json.num_skipped+' skipped');
  209. }
  210. cursor
  211. .reset()
  212. .write(' -- duration: '+test_json.duration+"s\n");
  213. console.log('');
  214. if (test_json.num_fails > 0 || program.printAll) {
  215. var extract_error_lines = function (err) {
  216. // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
  217. var err_lines = err.split('\n');
  218. if (err_lines.length == 1) {
  219. return err_lines[0];
  220. } else {
  221. var ind;
  222. for (ind = 0; ind < err_lines.length; ind++) {
  223. var at_ind = err_lines[ind].trim().indexOf('at ');
  224. if (at_ind === 0) {
  225. break;
  226. }
  227. }
  228. return err_lines.slice(0, ind).join('\n');
  229. }
  230. };
  231. var traverse_tree = function(indentation, node) {
  232. if (node.type == 'suite') {
  233. if (!node.has_subfailures && !program.printAll) return;
  234. if (indentation === 0) {
  235. cursor.bold();
  236. console.log(node.name);
  237. console.log(Array(node.name.length+1).join('-'));
  238. cursor.reset();
  239. }
  240. else {
  241. cursor
  242. .write(Array(indentation+3).join('#'))
  243. .bold()
  244. .write(' '+node.name+' ')
  245. .reset()
  246. .write(Array(indentation+3).join('#'))
  247. .write("\n");
  248. }
  249. console.log('');
  250. for (var i = 0; i < node.children.length; i++) {
  251. traverse_tree(indentation+1, node.children[i]);
  252. }
  253. }
  254. else {
  255. if (!node.pass) {
  256. cursor.magenta();
  257. console.log('- failed: '+node.text+test_json.replay);
  258. cursor.red();
  259. console.log(' '+extract_error_lines(node.error));
  260. cursor.reset();
  261. console.log('');
  262. }
  263. else if (program.printAll) {
  264. if (node.skipped) {
  265. cursor
  266. .grey()
  267. .write('- skipped: '+node.text);
  268. }
  269. else {
  270. if (node.slow) cursor.yellow();
  271. else cursor.green();
  272. cursor
  273. .write('- pass: '+node.text)
  274. .grey()
  275. .write(' ('+node.duration+') ');
  276. }
  277. /*if (node.slow) cursor.yellow();
  278. else cursor.green();*/
  279. cursor
  280. //.write(test_json.replay)
  281. .reset()
  282. .write("\n");
  283. console.log('');
  284. }
  285. }
  286. };
  287. for (var i = 0; i < test_json.suites.length; i++) {
  288. traverse_tree(0, test_json.suites[i]);
  289. }
  290. }
  291. if (test_json.num_fails === 0) {
  292. cursor.fg.green();
  293. console.log('all tests passed :-)');
  294. cursor.reset();
  295. }
  296. });
  297. if (program.debug) {
  298. provider.on('console', function(line) {
  299. // log to stderr
  300. console.error(line);
  301. });
  302. }
  303. provider.on('error', function(line) {
  304. // log to stderr
  305. console.error('ERROR: ' + line);
  306. });
  307. /*gprom.finally(function(ph) {
  308. ph.exit();
  309. // exit with a status code that actually gives information
  310. if (program.exitWithFailureCount) process.exit(failure_count);
  311. });*/
  312. }