manualTimer.js
1 import { Far } from '@endo/marshal'; 2 import { bindAllMethods } from '@agoric/internal'; 3 import { buildManualTimer as build } from '@agoric/swingset-vat/tools/manual-timer.js'; 4 import { TimeMath } from '@agoric/time'; 5 import { eventLoopIteration } from './eventLoopIteration.js'; 6 7 const { Fail } = assert; 8 9 // we wrap SwingSet's buildManualTimer to accomodate the needs of 10 // zoe's tests 11 12 /** 13 * @typedef {{ 14 * timeStep?: import('@agoric/time/src/types').RelativeTime | bigint, 15 * eventLoopIteration?: () => Promise<void>, 16 * }} ZoeManualTimerOptions 17 */ 18 19 const nolog = (..._args) => {}; 20 21 const defaultOptions = { timeStep: 1n, eventLoopIteration }; 22 23 /** 24 * A fake TimerService, for unit tests that do not use a real 25 * kernel. You can make time pass by calling `advanceTo(when)`, or one 26 * `timeStep` at a time by calling `tick()`. 27 * 28 * `advanceTo()` merely schedules a wakeup: the actual 29 * handlers (in the code under test) are invoked several turns 30 * later. Some zoe/etc tests want to poll for the consequences of 31 * those invocations. The best approach is to get an appropriate 32 * Promise from your code-under-test, wait for it to fire, and then 33 * poll. But some libraries do not offer this convenience, especially 34 * when they use internal "fire and forget" actions. 35 * 36 * To support those tests, the manual timer accepts a 37 * `eventLoopIteration` option. If provided, each call to `tick()` 38 * will wait for all triggered activity to complete before 39 * returning. That doesn't mean the `wake()` handler's result promise 40 * has fired; it just means there are no settled Promises still trying 41 * to execute their callbacks. 42 * 43 * The following will wait for all such Promise activity to finish 44 * before returning from `tick()`: 45 * 46 * eventLoopIteration = () => new Promise(setImmediate); 47 * mt = buildManualTimer(log, startTime, { eventLoopIteration }) 48 * 49 * `tickN(count)` calls `tick()` multiple times, awaiting each one 50 * 51 * The first argument is called to log 'tick' events, which might help 52 * with "golden transcript" -style tests to distinguish tick 53 * boundaries 54 * 55 * @param {(...args: any[]) => void} [log] 56 * @param {import('@agoric/time/src/types').Timestamp | bigint} [startValue=0n] 57 * @param {ZoeManualTimerOptions} [options] 58 * @returns {ManualTimer} 59 */ 60 61 const buildManualTimer = ( 62 log = nolog, 63 startValue = 0n, 64 options = defaultOptions 65 ) => { 66 const { timeStep, eventLoopIteration, ...buildOptions } = options; 67 const optSuffix = msg => (msg ? `: ${msg}` : ''); 68 const callbacks = { 69 advanceTo: (newTime, msg) => log(`@@ tick:${newTime}${optSuffix(msg)} @@`), 70 setWakeup: (now, when) => 71 log(`@@ schedule task for:${when}, currently: ${now} @@`) 72 // wake: now => log(`@@ run task at:${now} @@`), 73 }; 74 75 // neither of these could possibly be a record, because the caller 76 // doesn't have our brand yet, but this makes the types maximally 77 // tolerant 78 startValue = TimeMath.absValue(startValue); 79 const timeStepValue = TimeMath.relValue(timeStep); 80 assert.typeof(startValue, 'bigint'); 81 assert.typeof(timeStepValue, 'bigint'); 82 83 const timerService = build({ 84 startTime: startValue, 85 ...buildOptions, 86 callbacks 87 }); 88 const toRT = rt => 89 TimeMath.coerceRelativeTimeRecord(rt, timerService.getTimerBrand()); 90 91 const tick = msg => { 92 const oldTime = timerService.getCurrentTimestamp(); 93 const newTime = TimeMath.addAbsRel(oldTime, toRT(timeStepValue)); 94 timerService.advanceTo(TimeMath.absValue(newTime), msg); 95 console.log(eventLoopIteration); 96 // that schedules wakeups, but they don't fire until a later turn 97 return eventLoopIteration && eventLoopIteration(); 98 }; 99 100 const tickN = async (nTimes, msg) => { 101 nTimes >= 1 || Fail`invariant nTimes >= 1`; 102 for (let i = 0; i < nTimes; i += 1) { 103 // eslint-disable-next-line no-await-in-loop 104 await tick(msg); 105 } 106 }; 107 108 const setWakeup = (when, handler, cancelToken) => { 109 return timerService.setWakeup(when, handler, cancelToken); 110 }; 111 112 return Far('ManualTimer', { 113 ...bindAllMethods(timerService), 114 tick, 115 tickN, 116 setWakeup 117 }); 118 }; 119 harden(buildManualTimer); 120 121 export default buildManualTimer;