All files / lib/internal/assert calltracker.js

100% Statements 96/96
100% Branches 16/16
75% Functions 3/4
100% Lines 96/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 97105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 105x 13x 13x 13x 1x 1x 13x 1x 1x 12x 12x 12x 12x 12x 12x 12x 12x 13x 13x 13x 13x 13x 13x 9x 9x 7x 7x 7x 7x 9x 9x 9x 2x 2x 9x 13x 13x 105x 105x 9x 9x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 9x 9x 105x 105x 6x 6x 2x 2x 2x 2x 2x 6x 105x 105x 105x  
'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;