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