123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- '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>'
- };
- // Add the exit listener only once to avoid listener leak warnings
- if (mustCallChecks.length === 0)
- process.on('exit', runCallChecks);
- mustCallChecks.push(context);
- function wrapped(...args) {
- ++context.actual;
- return fn.call(this, ...args);
- }
- // TODO: remove origFn?
- 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);
- // With the V8 Error API, the stack is not formatted until it is accessed
- // eslint-disable-next-line no-unused-expressions
- 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,
- };
|