All files / lib/internal/assert calltracker.js

89.58% Statements 86/96
100% Branches 14/14
75% Functions 3/4
89.58% Lines 86/96

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x 104x           13x 13x 13x 1x 1x 13x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 9x 9x 7x 7x 7x 7x 9x 9x 9x 2x 2x 9x 13x 13x     9x 9x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 9x 9x     6x 6x 2x 2x 2x 2x 2x 6x   104x 104x  
'use strict';
 
const {
  ArrayPrototypePush,
  Error,
  FunctionPrototype,
  ReflectApply,
  SafeSet,
} = primordials;
 
const {
  codes: {
    ERR_UNAVAILABLE_DURING_EXIT,
  },
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const {
  validateUint32,
} = require('internal/validators');
 
const noop = FunctionPrototype;
 
class CallTracker {

  #callChecks = new SafeSet();

  calls(fn, exact = 1) {
    if (process._exiting)
      throw new ERR_UNAVAILABLE_DURING_EXIT();
    if (typeof fn === 'number') {
      exact = fn;
      fn = noop;
    } else if (fn === undefined) {
      fn = noop;
    }
 
    validateUint32(exact, 'exact', true);
 
    const context = {
      exact,
      actual: 0,
      // eslint-disable-next-line no-restricted-syntax
      stackTrace: new Error(),
      name: fn.name || 'calls'
    };
    const callChecks = this.#callChecks;
    callChecks.add(context);
 
    return function() {
      context.actual++;
      if (context.actual === context.exact) {
        // Once function has reached its call count remove it from
        // callChecks set to prevent memory leaks.
        callChecks.delete(context);
      }
      // If function has been called more than expected times, add back into
      // callchecks.
      if (context.actual === context.exact + 1) {
        callChecks.add(context);
      }
      return ReflectApply(fn, this, arguments);
    };
  }

  report() {
    const errors = [];
    for (const context of this.#callChecks) {
      // If functions have not been called exact times
      if (context.actual !== context.exact) {
        const message = `Expected the ${context.name} function to be ` +
                        `executed ${context.exact} time(s) but was ` +
                        `executed ${context.actual} time(s).`;
        ArrayPrototypePush(errors, {
          message,
          actual: context.actual,
          expected: context.exact,
          operator: context.name,
          stack: context.stackTrace
        });
      }
    }
    return errors;
  }

  verify() {
    const errors = this.report();
    if (errors.length > 0) {
      throw new AssertionError({
        message: 'Function(s) were not called the expected number of times',
        details: errors,
      });
    }
  }
}
 
module.exports = CallTracker;