-
Notifications
You must be signed in to change notification settings - Fork 191
/
manualTimer.js
102 lines (90 loc) · 3.32 KB
/
manualTimer.js
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
97
98
99
100
101
102
import { Far } from '@endo/marshal';
import { bindAllMethods } from '@agoric/internal';
import { buildManualTimer as build } from '@agoric/swingset-vat/tools/manual-timer.js';
import { TimeMath } from '@agoric/swingset-vat/src/vats/timer/timeMath.js';
import './types.js';
import './internal-types.js';
// we wrap SwingSet's buildManualTimer to accomodate the needs of
// zoe's tests
/**
* @typedef {{
* timeStep?: RelativeTime,
* eventLoopIteration?: () => Promise<unknown>,
* }} ZoeManualTimerOptions
*/
const nolog = (..._args) => {};
/**
*
* A fake TimerService, for unit tests that do not use a real
* kernel. You can make time pass by calling `advanceTo(when)`, or one
* `timeStep` at a time by calling `tick()`.
*
* `advanceTo()` merely schedules a wakeup: the actual
* handlers (in the code under test) are invoked several turns
* later. Some zoe/etc tests want to poll for the consequences of
* those invocations. The best approach is to get an appropriate
* Promise from your code-under-test, wait for it to fire, and then
* poll. But some libraries do not offer this convenience, especially
* when they use internal "fire and forget" actions.
*
* To support those tests, the manual timer accepts a
* `eventLoopIteration` option. If provided, each call to `tick()`
* will wait for all triggered activity to complete before
* returning. That doesn't mean the `wake()` handler's result promise
* has fired; it just means there are no settled Promises still trying
* to execute their callbacks.
*
* The following will wait for all such Promise activity to finish
* before returning from `tick()`:
*
* eventLoopIteration = () => new Promise(setImmediate);
* mt = buildManualTimer(log, startTime, { eventLoopIteration })
*
* `tickN(count)` calls `tick()` multiple times, awaiting each one
*
* The first argument is called to log 'tick' events, which might help
* with "golden transcript" -style tests to distinguish tick
* boundaries
*
* @param {(...args: any[]) => void} [log]
* @param {Timestamp} [startValue=0n]
* @param {ZoeManualTimerOptions} [options]
* @returns {ManualTimer}
*/
const buildManualTimer = (log = nolog, startValue = 0n, options = {}) => {
const {
timeStep = 1n,
eventLoopIteration = () => 0,
...buildOptions
} = options;
assert.typeof(timeStep, 'bigint');
const timerService = build({ startTime: startValue, ...buildOptions });
const tick = msg => {
const oldTime = timerService.getCurrentTimestamp();
const newTime = TimeMath.addAbsRel(oldTime, timeStep);
log(`@@ tick:${newTime}${msg ? `: ${msg}` : ''} @@`);
timerService.advanceTo(newTime);
// that schedules wakeups, but they don't fire until a later turn
return eventLoopIteration();
};
const tickN = async (nTimes, msg) => {
assert(nTimes >= 1, 'invariant nTimes >= 1');
for (let i = 0; i < nTimes; i += 1) {
// eslint-disable-next-line no-await-in-loop
await tick(msg);
}
};
const setWakeup = (when, handler, cancelToken) => {
const now = timerService.getCurrentTimestamp();
log(`@@ schedule task for:${when}, currently: ${now} @@`);
return timerService.setWakeup(when, handler, cancelToken);
};
return Far('ManualTimer', {
...bindAllMethods(timerService),
tick,
tickN,
setWakeup,
});
};
harden(buildManualTimer);
export default buildManualTimer;