| 'use strict'; |
|
|
| const assert = require('assert'); |
| const { inspect } = require('util'); |
|
|
| const mustCallChecks = []; |
|
|
| function noop() {} |
|
|
| function runCallChecks(exitCode) { |
| if (exitCode !== 0) return; |
|
|
| const failed = mustCallChecks.filter((context) => { |
| if ('minimum' in context) { |
| context.messageSegment = `at least ${context.minimum}`; |
| return context.actual < context.minimum; |
| } |
| context.messageSegment = `exactly ${context.exact}`; |
| return context.actual !== context.exact; |
| }); |
|
|
| failed.forEach((context) => { |
| console.error('Mismatched %s function calls. Expected %s, actual %d.', |
| context.name, |
| context.messageSegment, |
| context.actual); |
| console.error(context.stack.split('\n').slice(2).join('\n')); |
| }); |
|
|
| if (failed.length) |
| process.exit(1); |
| } |
|
|
| function mustCall(fn, exact) { |
| return _mustCallInner(fn, exact, 'exact'); |
| } |
|
|
| function mustCallAtLeast(fn, minimum) { |
| return _mustCallInner(fn, minimum, 'minimum'); |
| } |
|
|
| function _mustCallInner(fn, criteria = 1, field) { |
| if (process._exiting) |
| throw new Error('Cannot use common.mustCall*() in process exit handler'); |
|
|
| if (typeof fn === 'number') { |
| criteria = fn; |
| fn = noop; |
| } else if (fn === undefined) { |
| fn = noop; |
| } |
|
|
| if (typeof criteria !== 'number') |
| throw new TypeError(`Invalid ${field} value: ${criteria}`); |
|
|
| const context = { |
| [field]: criteria, |
| actual: 0, |
| stack: inspect(new Error()), |
| name: fn.name || '<anonymous>' |
| }; |
|
|
| |
| if (mustCallChecks.length === 0) |
| process.on('exit', runCallChecks); |
|
|
| mustCallChecks.push(context); |
|
|
| function wrapped(...args) { |
| ++context.actual; |
| return fn.call(this, ...args); |
| } |
| |
| wrapped.origFn = fn; |
|
|
| return wrapped; |
| } |
|
|
| function getCallSite(top) { |
| const originalStackFormatter = Error.prepareStackTrace; |
| Error.prepareStackTrace = (err, stack) => |
| `${stack[0].getFileName()}:${stack[0].getLineNumber()}`; |
| const err = new Error(); |
| Error.captureStackTrace(err, top); |
| |
| |
| err.stack; |
| Error.prepareStackTrace = originalStackFormatter; |
| return err.stack; |
| } |
|
|
| function mustNotCall(msg) { |
| const callSite = getCallSite(mustNotCall); |
| return function mustNotCall(...args) { |
| args = args.map(inspect).join(', '); |
| const argsInfo = (args.length > 0 |
| ? `\ncalled with arguments: ${args}` |
| : ''); |
| assert.fail( |
| `${msg || 'function should not have been called'} at ${callSite}` |
| + argsInfo); |
| }; |
| } |
|
|
| module.exports = { |
| mustCall, |
| mustCallAtLeast, |
| mustNotCall, |
| }; |
|
|