1 | 'use strict';
|
2 |
|
3 | const assert = require('assert');
|
4 | const { inspect } = require('util');
|
5 |
|
6 | const mustCallChecks = [];
|
7 |
|
8 | function noop() {}
|
9 |
|
10 | function 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 |
|
34 | function mustCall(fn, exact) {
|
35 | return _mustCallInner(fn, exact, 'exact');
|
36 | }
|
37 |
|
38 | function mustCallAtLeast(fn, minimum) {
|
39 | return _mustCallInner(fn, minimum, 'minimum');
|
40 | }
|
41 |
|
42 | function _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 |
|
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 |
|
74 | wrapped.origFn = fn;
|
75 |
|
76 | return wrapped;
|
77 | }
|
78 |
|
79 | function 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 |
|
86 |
|
87 | err.stack;
|
88 | Error.prepareStackTrace = originalStackFormatter;
|
89 | return err.stack;
|
90 | }
|
91 |
|
92 | function 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 |
|
105 | module.exports = {
|
106 | mustCall,
|
107 | mustCallAtLeast,
|
108 | mustNotCall,
|
109 | };
|