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