mocha.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #!/usr/bin/env node
  2. 'use strict';
  3. /**
  4. * This wrapper executable checks for known node flags and appends them when found,
  5. * before invoking the "real" executable (`lib/cli/cli.js`)
  6. *
  7. * @module bin/mocha
  8. * @private
  9. */
  10. const os = require('node:os');
  11. const {loadOptions} = require('../lib/cli/options');
  12. const {
  13. unparseNodeFlags,
  14. isNodeFlag,
  15. impliesNoTimeouts
  16. } = require('../lib/cli/node-flags');
  17. const unparse = require('yargs-unparser');
  18. const debug = require('debug')('mocha:cli:mocha');
  19. const {aliases} = require('../lib/cli/run-option-metadata');
  20. const mochaArgs = {};
  21. const nodeArgs = {};
  22. const SIGNAL_OFFSET = 128;
  23. let hasInspect = false;
  24. const opts = loadOptions(process.argv.slice(2));
  25. debug('loaded opts', opts);
  26. /**
  27. * Given option/command `value`, disable timeouts if applicable
  28. * @param {string} [value] - Value to check
  29. * @ignore
  30. */
  31. const disableTimeouts = value => {
  32. if (impliesNoTimeouts(value)) {
  33. debug('option %s disabled timeouts', value);
  34. mochaArgs.timeout = 0;
  35. }
  36. };
  37. /**
  38. * If `value` begins with `v8-` and is not explicitly `v8-options`, remove prefix
  39. * @param {string} [value] - Value to check
  40. * @returns {string} `value` with prefix (maybe) removed
  41. * @ignore
  42. */
  43. const trimV8Option = value =>
  44. value !== 'v8-options' && /^v8-/.test(value) ? value.slice(3) : value;
  45. // sort options into "node" and "mocha" buckets
  46. Object.keys(opts).forEach(opt => {
  47. if (isNodeFlag(opt)) {
  48. nodeArgs[trimV8Option(opt)] = opts[opt];
  49. } else {
  50. mochaArgs[opt] = opts[opt];
  51. }
  52. });
  53. // disable 'timeout' for debugFlags
  54. Object.keys(nodeArgs).forEach(opt => disableTimeouts(opt));
  55. mochaArgs['node-option'] &&
  56. mochaArgs['node-option'].forEach(opt => disableTimeouts(opt));
  57. // Native debugger handling
  58. // see https://nodejs.org/api/debugger.html#debugger_debugger
  59. // look for 'inspect' that would launch this debugger,
  60. // remove it from Mocha's opts and prepend it to Node's opts.
  61. // A deprecation warning will be printed by node, if applicable.
  62. // (mochaArgs._ are "positional" arguments, not prefixed with - or --)
  63. if (mochaArgs._) {
  64. const i = mochaArgs._.findIndex(val => val === 'inspect');
  65. if (i > -1) {
  66. mochaArgs._.splice(i, 1);
  67. disableTimeouts('inspect');
  68. hasInspect = true;
  69. }
  70. }
  71. if (mochaArgs['node-option'] || Object.keys(nodeArgs).length || hasInspect) {
  72. const {spawn} = require('node:child_process');
  73. const mochaPath = require.resolve('../lib/cli/cli.js');
  74. const nodeArgv =
  75. (mochaArgs['node-option'] && mochaArgs['node-option'].map(v => '--' + v)) ||
  76. unparseNodeFlags(nodeArgs);
  77. if (hasInspect) nodeArgv.unshift('inspect');
  78. delete mochaArgs['node-option'];
  79. debug('final node argv', nodeArgv);
  80. const args = [].concat(
  81. nodeArgv,
  82. mochaPath,
  83. unparse(mochaArgs, {alias: aliases})
  84. );
  85. debug(
  86. 'forking child process via command: %s %s',
  87. process.execPath,
  88. args.join(' ')
  89. );
  90. const proc = spawn(process.execPath, args, {
  91. stdio: 'inherit'
  92. });
  93. proc.on('exit', (code, signal) => {
  94. process.on('exit', () => {
  95. if (signal) {
  96. signal = typeof signal === 'string' ? os.constants.signals[signal] : signal;
  97. if (mochaArgs['posix-exit-codes'] === true) {
  98. process.exitCode = SIGNAL_OFFSET + signal;
  99. }
  100. process.kill(process.pid, signal);
  101. } else {
  102. process.exit(Math.min(code, mochaArgs['posix-exit-codes'] ? 1 : 255));
  103. }
  104. });
  105. });
  106. // terminate children.
  107. process.on('SIGINT', () => {
  108. // XXX: a previous comment said this would abort the runner, but I can't see that it does
  109. // anything with the default runner.
  110. debug('main process caught SIGINT');
  111. proc.kill('SIGINT');
  112. // if running in parallel mode, we will have a proper SIGINT handler, so the below won't
  113. // be needed.
  114. if (!args.parallel || args.jobs < 2) {
  115. // win32 does not support SIGTERM, so use next best thing.
  116. if (os.platform() === 'win32') {
  117. proc.kill('SIGKILL');
  118. } else {
  119. // using SIGKILL won't cleanly close the output streams, which can result
  120. // in cut-off text or a befouled terminal.
  121. debug('sending SIGTERM to child process');
  122. proc.kill('SIGTERM');
  123. }
  124. }
  125. });
  126. } else {
  127. debug('running Mocha in-process');
  128. require('../lib/cli/cli').main([], mochaArgs);
  129. }