// UUT.test.js import { timerUtils } from './timerUtils.js'; import { UUT } from './UUT.js'; import sinon from 'sinon'; import { expect } from 'chai'; const promiseResolvers = []; const useGlobalClock = false; let clock; const callCount = 3; const HIL_SIM_TIME = 1000; async function realTimeOut(ms) { if (useGlobalClock) { clock.restore() } else { timerUtils.pauseFakeTimer(); } await new Promise(resolve => setTimeout(resolve, ms)); if (useGlobalClock) { clock = sinon.useFakeTimers(); } else { timerUtils.resumeFakeTimer(); } } describe('Main Function Test', function () { beforeEach(function () { if (useGlobalClock) { clock = sinon.useFakeTimers(); } else { timerUtils.useFakeTimer(); } sinon.stub(UUT, 'subFunction').callsFake(async (index) => { console.log(`Stub: subFunction ${index} called, ${new Date().toISOString()}`); return new Promise((resolve) => { promiseResolvers.push(() => { // HIL Simulation calls are stubbed here timerUtils.pauseFakeTimer(); // Pause the fake timer console.log(`Stub: [HIL-SIM] call #${index} starting on ${new Date().toISOString()}`) setTimeout(() => { resolve(); console.log(`Stub: [HIL-SIM] call #${index} ended on ${new Date().toISOString()}`) timerUtils.resumeFakeTimer(); // Resume the fake timer }, HIL_SIM_TIME); // Real-time timeout }); }); }); }); afterEach(function () { if (useGlobalClock) { clock.restore(); } else { timerUtils.restoreRealTimer(); } promiseResolvers.length = 0; UUT.subFunction.restore(); }); it('should complete mainFunction with controlled promise resolution', async function () { this.timeout((callCount + 2) * HIL_SIM_TIME); const mainFunctionPromise = UUT.mainFunction(); // Ensure we advance time and resolve promises only after they are pushed for (let i = 1; i <= callCount; i++) { // Wait for real time based stub, at least HIL_SIM_TIME console.log(`Test: Start waiting in real time (for the stub) [subfunction ${i}]`); await realTimeOut(1000); console.log(`Test: Finish waiting in real time (for the stub) [subfunction ${i}]`); console.log(`Test: Start waiting in fake time (for the UUT) [subfunction ${i}]`); if (useGlobalClock) { await clock.tickAsync(1000) } else { await timerUtils.currentClock.tickAsync(1000); } console.log(`Test: Finish waiting in fake time (for the UUT) [subfunction ${i}]`); let rCount = promiseResolvers.length; expect(rCount, `Expected ${i} resolvers but received ${rCount}`).to.equal(i); console.log(`Test: Resolving subfunction ${i}`); if (typeof promiseResolvers[i - 1] === 'function') { promiseResolvers[i - 1](); // Resolve the i-th subfunction's promise console.log(`Test: Resolved subfunction ${i}`) } else { // This should not be reached as the previous expectation should fire throw new Error(`Test: Resolver for subfunction ${i} is not a function`); } } console.log(`Test: All ${promiseResolvers.length} subfunction promises are resolved`); console.log('Test: Advancing time for the final wait'); if (useGlobalClock) { await clock.tickAsync(4000) } else { await timerUtils.currentClock.tickAsync(4000); } console.log('Test: awaiting mainFunction promise'); await mainFunctionPromise; console.log('Test: mainFunction should be completed now'); expect(UUT.subFunction.callCount).to.equal(callCount); }); });