run_from_console.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. .parse(process.argv);
  22. if (program.tests.length === 0) {
  23. program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); });
  24. program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in
  25. console.log('using files %s', program.tests);
  26. }
  27. var file_paths = [];
  28. var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true);
  29. var get_path = function (/* arguments */) {
  30. if (program.relative) {
  31. return path.join.apply(null, arguments);
  32. } else {
  33. var args = Array.prototype.slice.call(arguments);
  34. args.unshift(__dirname, '..');
  35. return path.resolve.apply(null, args);
  36. }
  37. };
  38. var get_path_cwd = function (/* arguments */) {
  39. if (program.relative) {
  40. var part_path = path.join.apply(null, arguments);
  41. return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path));
  42. } else {
  43. var args = Array.prototype.slice.call(arguments);
  44. args.unshift(process.cwd());
  45. return path.resolve.apply(null, args);
  46. }
  47. };
  48. if (all_js && !program.autoInject) {
  49. var all_modules = {};
  50. // uses the first instance of the string 'requires local modules: '
  51. program.tests.forEach(function (testname) {
  52. var full_path = path.resolve(process.cwd(), testname);
  53. var content = fs.readFileSync(full_path).toString();
  54. var ind = content.indexOf('requires local modules: ');
  55. if (ind > -1) {
  56. ind += 'requires local modules: '.length;
  57. var eol = content.indexOf('\n', ind);
  58. var modules = content.slice(ind, eol).split(/,\s*/);
  59. modules.forEach(function (mod) {
  60. all_modules[get_path('include/', mod) + '.js'] = 1;
  61. });
  62. }
  63. var fakes_ind = content.indexOf('requires test modules: ');
  64. if (fakes_ind > -1) {
  65. fakes_ind += 'requires test modules: '.length;
  66. var fakes_eol = content.indexOf('\n', fakes_ind);
  67. var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/);
  68. fakes_modules.forEach(function (mod) {
  69. all_modules[get_path('tests/', mod) + '.js'] = 1;
  70. });
  71. }
  72. });
  73. program.autoInject = Object.keys(all_modules);
  74. }
  75. if (program.autoInject) {
  76. var temp = require('temp');
  77. temp.track();
  78. var template = {
  79. 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>",
  80. script_tag: function(p) { return "<script src='" + p + "'></script>"; },
  81. footer: "<script>\nmocha.checkLeaks();\nmocha.globals(['navigator', 'create', 'ClientUtils', '__utils__']);\nmocha.run(function () { window.__mocha_done = true; });\n</script>\n</body>\n</html>"
  82. };
  83. template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js'));
  84. template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
  85. template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
  86. template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
  87. template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
  88. template.header += "\n<script>mocha.setup('bdd');</script>";
  89. template.header = program.autoInject.reduce(function(acc, sn) {
  90. return acc + "\n" + template.script_tag(get_path_cwd(sn));
  91. }, template.header);
  92. file_paths = program.tests.map(function(jsn, ind) {
  93. var templ = template.header;
  94. templ += "\n";
  95. templ += template.script_tag(get_path_cwd(jsn));
  96. templ += template.footer;
  97. var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' });
  98. fs.writeSync(tempfile.fd, templ);
  99. fs.closeSync(tempfile.fd);
  100. return tempfile.path;
  101. });
  102. }
  103. else {
  104. file_paths = program.tests.map(function(fn) {
  105. return path.resolve(process.cwd(), fn);
  106. });
  107. }
  108. var use_ansi = false;
  109. if (program.color) use_ansi = true;
  110. else if (program.disableColor) use_ansi = false;
  111. else if (process.stdout.isTTY) use_ansi = true;
  112. var cursor = ansi(process.stdout, { enabled: use_ansi });
  113. if (program.outputHtml) {
  114. file_paths.forEach(function(path, path_ind) {
  115. fs.readFile(path, function(err, data) {
  116. if (err) {
  117. console.warn(error.stack);
  118. return;
  119. }
  120. if (use_ansi) {
  121. cursor
  122. .bold()
  123. .write(program.tests[path_ind])
  124. .reset()
  125. .write("\n")
  126. .write(Array(program.tests[path_ind].length+1).join('='))
  127. .write("\n\n");
  128. }
  129. cursor
  130. .write(data)
  131. .write("\n\n");
  132. });
  133. });
  134. }
  135. if (program.generateHtml) {
  136. var open_browser;
  137. if (program.openInBrowser) {
  138. open_browser = require('open');
  139. }
  140. file_paths.forEach(function(path, path_ind) {
  141. cursor
  142. .bold()
  143. .write(program.tests[path_ind])
  144. .write(": ")
  145. .reset()
  146. .write(path)
  147. .write("\n");
  148. if (program.openInBrowser) {
  149. open_browser(path);
  150. }
  151. });
  152. console.log('');
  153. }
  154. if (program.generateHtml) {
  155. process.stdin.resume(); // pause until C-c
  156. process.on('SIGINT', function() {
  157. process.stdin.pause(); // exit
  158. });
  159. }
  160. if (!program.outputHtml && !program.generateHtml) {
  161. var failure_count = 0;
  162. var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js'));
  163. cursor
  164. .write("Running tests ")
  165. .bold()
  166. .write(program.tests.join(', '))
  167. .reset()
  168. .grey()
  169. .write(' using provider '+prov.name)
  170. .reset()
  171. .write("\n");
  172. //console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
  173. var provider = prov.provide_emitter(file_paths);
  174. provider.on('test_ready', function(test_json) {
  175. console.log('');
  176. filename = program.tests[test_json.file_ind];
  177. cursor.bold();
  178. console.log('Results for %s:', filename);
  179. console.log(Array('Results for :'.length+filename.length+1).join('='));
  180. cursor.reset();
  181. console.log('');
  182. cursor
  183. .write(''+test_json.num_tests+' tests run, ')
  184. .green()
  185. .write(''+test_json.num_passes+' passed');
  186. if (test_json.num_slow > 0) {
  187. cursor
  188. .reset()
  189. .write(' (');
  190. cursor
  191. .yellow()
  192. .write(''+test_json.num_slow+' slow')
  193. .reset()
  194. .write(')');
  195. }
  196. cursor
  197. .reset()
  198. .write(', ');
  199. cursor
  200. .red()
  201. .write(''+test_json.num_fails+' failed');
  202. if (test_json.num_skipped > 0) {
  203. cursor
  204. .reset()
  205. .write(', ')
  206. .grey()
  207. .write(''+test_json.num_skipped+' skipped');
  208. }
  209. cursor
  210. .reset()
  211. .write(' -- duration: '+test_json.duration+"s\n");
  212. console.log('');
  213. if (test_json.num_fails > 0 || program.printAll) {
  214. var traverse_tree = function(indentation, node) {
  215. if (node.type == 'suite') {
  216. if (!node.has_subfailures && !program.printAll) return;
  217. if (indentation === 0) {
  218. cursor.bold();
  219. console.log(node.name);
  220. console.log(Array(node.name.length+1).join('-'));
  221. cursor.reset();
  222. }
  223. else {
  224. cursor
  225. .write(Array(indentation+3).join('#'))
  226. .bold()
  227. .write(' '+node.name+' ')
  228. .reset()
  229. .write(Array(indentation+3).join('#'))
  230. .write("\n");
  231. }
  232. console.log('');
  233. for (var i = 0; i < node.children.length; i++) {
  234. traverse_tree(indentation+1, node.children[i]);
  235. }
  236. }
  237. else {
  238. if (!node.pass) {
  239. cursor.magenta();
  240. console.log('- failed: '+node.text+test_json.replay);
  241. cursor.red();
  242. console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
  243. cursor.reset();
  244. console.log('');
  245. }
  246. else if (program.printAll) {
  247. if (node.skipped) {
  248. cursor
  249. .grey()
  250. .write('- skipped: '+node.text);
  251. }
  252. else {
  253. if (node.slow) cursor.yellow();
  254. else cursor.green();
  255. cursor
  256. .write('- pass: '+node.text)
  257. .grey()
  258. .write(' ('+node.duration+') ');
  259. }
  260. /*if (node.slow) cursor.yellow();
  261. else cursor.green();*/
  262. cursor
  263. //.write(test_json.replay)
  264. .reset()
  265. .write("\n");
  266. console.log('');
  267. }
  268. }
  269. };
  270. for (var i = 0; i < test_json.suites.length; i++) {
  271. traverse_tree(0, test_json.suites[i]);
  272. }
  273. }
  274. if (test_json.num_fails === 0) {
  275. cursor.fg.green();
  276. console.log('all tests passed :-)');
  277. cursor.reset();
  278. }
  279. });
  280. if (program.debug) {
  281. provider.on('console', function(line) {
  282. // log to stderr
  283. console.error(line);
  284. });
  285. }
  286. provider.on('error', function(line) {
  287. // log to stderr
  288. console.error('ERROR: ' + line);
  289. });
  290. /*gprom.finally(function(ph) {
  291. ph.exit();
  292. // exit with a status code that actually gives information
  293. if (program.exitWithFailureCount) process.exit(failure_count);
  294. });*/
  295. }