UNPKG

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