• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypePush,
5  Error,
6  FunctionPrototype,
7  ReflectApply,
8  SafeSet,
9} = primordials;
10
11const {
12  codes: {
13    ERR_UNAVAILABLE_DURING_EXIT,
14  },
15} = require('internal/errors');
16const AssertionError = require('internal/assert/assertion_error');
17const {
18  validateUint32,
19} = require('internal/validators');
20
21const noop = FunctionPrototype;
22
23class CallTracker {
24
25  #callChecks = new SafeSet()
26
27  calls(fn, exact = 1) {
28    if (process._exiting)
29      throw new ERR_UNAVAILABLE_DURING_EXIT();
30    if (typeof fn === 'number') {
31      exact = fn;
32      fn = noop;
33    } else if (fn === undefined) {
34      fn = noop;
35    }
36
37    validateUint32(exact, 'exact', true);
38
39    const context = {
40      exact,
41      actual: 0,
42      // eslint-disable-next-line no-restricted-syntax
43      stackTrace: new Error(),
44      name: fn.name || 'calls'
45    };
46    const callChecks = this.#callChecks;
47    callChecks.add(context);
48
49    return function() {
50      context.actual++;
51      if (context.actual === context.exact) {
52        // Once function has reached its call count remove it from
53        // callChecks set to prevent memory leaks.
54        callChecks.delete(context);
55      }
56      // If function has been called more than expected times, add back into
57      // callchecks.
58      if (context.actual === context.exact + 1) {
59        callChecks.add(context);
60      }
61      return ReflectApply(fn, this, arguments);
62    };
63  }
64
65  report() {
66    const errors = [];
67    for (const context of this.#callChecks) {
68      // If functions have not been called exact times
69      if (context.actual !== context.exact) {
70        const message = `Expected the ${context.name} function to be ` +
71                        `executed ${context.exact} time(s) but was ` +
72                        `executed ${context.actual} time(s).`;
73        ArrayPrototypePush(errors, {
74          message,
75          actual: context.actual,
76          expected: context.exact,
77          operator: context.name,
78          stack: context.stackTrace
79        });
80      }
81    }
82    return errors;
83  }
84
85  verify() {
86    const errors = this.report();
87    if (errors.length > 0) {
88      throw new AssertionError({
89        message: 'Function(s) were not called the expected number of times',
90        details: errors,
91      });
92    }
93  }
94}
95
96module.exports = CallTracker;
97