All files / lib/internal/assert calltracker.js

95.83% Statements 92/96
80% Branches 12/15
75% Functions 3/4
95.83% Lines 92/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 97102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 102x 9x 9x 9x     9x     8x 8x 8x 8x 8x 8x 8x 8x 9x 9x 9x 9x 9x 9x 5x 5x 3x 3x 3x 3x 5x 5x 5x 2x 2x 5x 9x 9x 102x 102x 6x 6x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 6x 6x 102x 102x 3x 3x 2x 2x 2x 2x 2x 3x 102x 102x 102x  
'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;