common.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. 'use strict';
  2. const assert = require('assert');
  3. const { inspect } = require('util');
  4. const mustCallChecks = [];
  5. function noop() {}
  6. function runCallChecks(exitCode) {
  7. if (exitCode !== 0) return;
  8. const failed = mustCallChecks.filter((context) => {
  9. if ('minimum' in context) {
  10. context.messageSegment = `at least ${context.minimum}`;
  11. return context.actual < context.minimum;
  12. }
  13. context.messageSegment = `exactly ${context.exact}`;
  14. return context.actual !== context.exact;
  15. });
  16. failed.forEach((context) => {
  17. console.error('Mismatched %s function calls. Expected %s, actual %d.',
  18. context.name,
  19. context.messageSegment,
  20. context.actual);
  21. console.error(context.stack.split('\n').slice(2).join('\n'));
  22. });
  23. if (failed.length)
  24. process.exit(1);
  25. }
  26. function mustCall(fn, exact) {
  27. return _mustCallInner(fn, exact, 'exact');
  28. }
  29. function mustCallAtLeast(fn, minimum) {
  30. return _mustCallInner(fn, minimum, 'minimum');
  31. }
  32. function _mustCallInner(fn, criteria = 1, field) {
  33. if (process._exiting)
  34. throw new Error('Cannot use common.mustCall*() in process exit handler');
  35. if (typeof fn === 'number') {
  36. criteria = fn;
  37. fn = noop;
  38. } else if (fn === undefined) {
  39. fn = noop;
  40. }
  41. if (typeof criteria !== 'number')
  42. throw new TypeError(`Invalid ${field} value: ${criteria}`);
  43. const context = {
  44. [field]: criteria,
  45. actual: 0,
  46. stack: inspect(new Error()),
  47. name: fn.name || '<anonymous>'
  48. };
  49. // Add the exit listener only once to avoid listener leak warnings
  50. if (mustCallChecks.length === 0)
  51. process.on('exit', runCallChecks);
  52. mustCallChecks.push(context);
  53. function wrapped(...args) {
  54. ++context.actual;
  55. return fn.call(this, ...args);
  56. }
  57. // TODO: remove origFn?
  58. wrapped.origFn = fn;
  59. return wrapped;
  60. }
  61. function getCallSite(top) {
  62. const originalStackFormatter = Error.prepareStackTrace;
  63. Error.prepareStackTrace = (err, stack) =>
  64. `${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
  65. const err = new Error();
  66. Error.captureStackTrace(err, top);
  67. // With the V8 Error API, the stack is not formatted until it is accessed
  68. // eslint-disable-next-line no-unused-expressions
  69. err.stack;
  70. Error.prepareStackTrace = originalStackFormatter;
  71. return err.stack;
  72. }
  73. function mustNotCall(msg) {
  74. const callSite = getCallSite(mustNotCall);
  75. return function mustNotCall(...args) {
  76. args = args.map(inspect).join(', ');
  77. const argsInfo = (args.length > 0
  78. ? `\ncalled with arguments: ${args}`
  79. : '');
  80. assert.fail(
  81. `${msg || 'function should not have been called'} at ${callSite}`
  82. + argsInfo);
  83. };
  84. }
  85. module.exports = {
  86. mustCall,
  87. mustCallAtLeast,
  88. mustNotCall,
  89. };