Skip to content

Commit 570a18c

Browse files
committed
test: less flaky timers acceptance test
1 parent 29473db commit 570a18c

File tree

3 files changed

+101
-47
lines changed

3 files changed

+101
-47
lines changed

lib/util/timers.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
* Consequently, timers may trigger later than their scheduled time.
1515
*/
1616

17-
const nativeSetTimeout = global.setTimeout
18-
const nativeClearTimeout = global.clearTimeout
19-
2017
/**
2118
* The fastNow variable contains the internal fast timer clock value.
2219
*
@@ -340,7 +337,7 @@ module.exports = {
340337
// If the delay is less than or equal to the RESOLUTION_MS value return a
341338
// native Node.js Timer instance.
342339
return delay <= RESOLUTION_MS
343-
? nativeSetTimeout(callback, delay, arg)
340+
? setTimeout(callback, delay, arg)
344341
: new FastTimer(callback, delay, arg)
345342
},
346343
/**
@@ -359,7 +356,7 @@ module.exports = {
359356
// Otherwise it is an instance of a native NodeJS.Timeout, so call the
360357
// Node.js native clearTimeout function.
361358
} else {
362-
nativeClearTimeout(timeout)
359+
clearTimeout(timeout)
363360
}
364361
},
365362
/**

test/timers-acceptance.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict'
2+
3+
const { tspl } = require('@matteo.collina/tspl')
4+
const { describe, test } = require('node:test')
5+
6+
const timers = require('../lib/util/timers')
7+
8+
// timers.setTimeout implements a low resolution timer with a 500 ms granularity
9+
// It is expected that in the worst case, a timer will fire about 500 ms after the
10+
// intended amount of time, an extra 200 ms is added to account event loop overhead
11+
// Timers should never fire excessively early, 1ms early is tolerated
12+
const ACCEPTABLE_DELTA = 700n
13+
14+
describe('timers - acceptance', () => {
15+
const getDelta = (start, target) => {
16+
const end = process.hrtime.bigint()
17+
const actual = (end - start) / 1_000_000n
18+
return actual - BigInt(target)
19+
}
20+
21+
test('meet acceptable resolution time', async (t) => {
22+
const testTimeouts = [0, 1, 499, 500, 501, 990, 999, 1000, 1001, 1100, 1400, 1499, 1500, 4000, 5000]
23+
24+
t = tspl(t, { plan: 1 + testTimeouts.length * 2 })
25+
26+
const start = process.hrtime.bigint()
27+
28+
for (const target of testTimeouts) {
29+
timers.setTimeout(() => {
30+
const delta = getDelta(start, target)
31+
32+
t.ok(delta >= -1n, `${target}ms fired early`)
33+
t.ok(delta < ACCEPTABLE_DELTA, `${target}ms fired late, got difference of ${delta}ms`)
34+
}, target)
35+
}
36+
37+
setTimeout(() => t.ok(true), 6000)
38+
await t.completed
39+
})
40+
})

test/timers.js

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ const { describe, test } = require('node:test')
66
const timers = require('../lib/util/timers')
77
const { eventLoopBlocker } = require('./utils/event-loop-blocker')
88

9+
// timers.setTimeout implements a low resolution timer with a 500 ms granularity
10+
// It is expected that in the worst case, a timer will fire about 500 ms after the
11+
// intended amount of time, an extra 200 ms is added to account event loop overhead
12+
// Timers should never fire excessively early, 1ms early is tolerated
13+
const ACCEPTABLE_DELTA = 700n
14+
915
describe('timers', () => {
1016
test('timers exports a clearTimeout', (t) => {
1117
t = tspl(t, { plan: 1 })
@@ -26,7 +32,7 @@ describe('timers', () => {
2632
t.strictEqual(timers.setTimeout(() => { }, 1e3)[timers.kFastTimer], undefined)
2733
})
2834

29-
test('setTimeout instantiates a FastTimer when delay is smaller than 1e3 ms', (t) => {
35+
test('setTimeout instantiates a FastTimer when delay is bigger than 1e3 ms', (t) => {
3036
t = tspl(t, { plan: 1 })
3137

3238
const timeout = timers.setTimeout(() => { }, 1001)
@@ -51,7 +57,8 @@ describe('timers', () => {
5157

5258
t.strictEqual(timer[timers.kFastTimer], true)
5359
t.strictEqual(timer._idleStart, -1)
54-
await new Promise((resolve) => setTimeout(resolve, 750))
60+
61+
timers.tick(750)
5562
t.notStrictEqual(timer._idleStart, -1)
5663

5764
timers.clearTimeout(timer)
@@ -66,7 +73,7 @@ describe('timers', () => {
6673

6774
t.strictEqual(timer[timers.kFastTimer], true)
6875
t.strictEqual(timer._idleStart, -1)
69-
await new Promise((resolve) => setTimeout(resolve, 750))
76+
timers.tick(750)
7077
t.notStrictEqual(timer._idleStart, -1)
7178
timers.clearTimeout(timer)
7279
t.strictEqual(timer._idleStart, -1)
@@ -83,21 +90,22 @@ describe('timers', () => {
8390
timers.clearTimeout(timer)
8491

8592
t.strictEqual(timer._idleStart, -1)
86-
await new Promise((resolve) => setTimeout(resolve, 750))
93+
timers.tick(750)
8794
t.strictEqual(timer._idleStart, -1)
8895
})
8996

9097
test('a cleared FastTimer can be refreshed', async (t) => {
9198
t = tspl(t, { plan: 2 })
9299

93-
const timer = timers.setTimeout(() => {
100+
const timer = timers.setFastTimeout(() => {
94101
t.ok('pass')
95102
}, 1001)
96103

97104
t.strictEqual(timer[timers.kFastTimer], true)
98105
timers.clearTimeout(timer)
99106
timer.refresh()
100-
await new Promise((resolve) => setTimeout(resolve, 2000))
107+
timers.tick(500)
108+
timers.tick(1000)
101109
timers.clearTimeout(timer)
102110
})
103111

@@ -107,50 +115,24 @@ describe('timers', () => {
107115
return actual - BigInt(target)
108116
}
109117

110-
// timers.setTimeout implements a low resolution timer with a 500 ms granularity
111-
// It is expected that in the worst case, a timer will fire about 500 ms after the
112-
// intended amount of time, an extra 200 ms is added to account event loop overhead
113-
// Timers should never fire excessively early, 1ms early is tolerated
114-
const ACCEPTABLE_DELTA = 700n
115-
116-
test('meet acceptable resolution time', async (t) => {
117-
const testTimeouts = [0, 1, 499, 500, 501, 990, 999, 1000, 1001, 1100, 1400, 1499, 1500, 4000, 5000]
118-
119-
t = tspl(t, { plan: 1 + testTimeouts.length * 2 })
120-
121-
const start = process.hrtime.bigint()
122-
123-
for (const target of testTimeouts) {
124-
timers.setTimeout(() => {
125-
const delta = getDelta(start, target)
126-
127-
t.ok(delta >= -1n, `${target}ms fired early`)
128-
t.ok(delta < ACCEPTABLE_DELTA, `${target}ms fired late, got difference of ${delta}ms`)
129-
}, target)
130-
}
131-
132-
setTimeout(() => t.ok(true), 6000)
133-
await t.completed
134-
})
135-
136118
test('refresh correctly with timeout < TICK_MS', async (t) => {
137119
t = tspl(t, { plan: 3 })
138120

139121
const start = process.hrtime.bigint()
140122

141123
const timeout = timers.setTimeout(() => {
142-
// 400 ms timer was refreshed after 600ms; total target is 1000
143-
const delta = getDelta(start, 1000)
124+
// 80 ms timer was refreshed after 120 ms; total target is 200 ms
125+
const delta = getDelta(start, 200)
144126

145127
t.ok(delta >= -1n, 'refreshed timer fired early')
146128
t.ok(delta < ACCEPTABLE_DELTA, 'refreshed timer fired late')
147-
}, 400)
129+
}, 80)
148130

149-
setTimeout(() => timeout.refresh(), 200)
150-
setTimeout(() => timeout.refresh(), 400)
151-
setTimeout(() => timeout.refresh(), 600)
131+
setTimeout(() => timeout.refresh(), 40)
132+
setTimeout(() => timeout.refresh(), 80)
133+
setTimeout(() => timeout.refresh(), 120)
152134

153-
setTimeout(() => t.ok(true), 1500)
135+
setTimeout(() => t.ok(true), 260)
154136
await t.completed
155137
})
156138

@@ -171,7 +153,42 @@ describe('timers', () => {
171153
setTimeout(() => timeout.refresh(), 750)
172154
setTimeout(() => timeout.refresh(), 1250)
173155

174-
setTimeout(() => t.ok(true), 3000)
156+
setTimeout(() => t.ok(true), 1800)
157+
await t.completed
158+
})
159+
160+
test('refresh correctly FastTimer with timeout > TICK_MS', async (t) => {
161+
t = tspl(t, { plan: 3 })
162+
163+
// The long running FastTimer will ensure that the internal clock is
164+
// incremented by the TICK_MS value in the onTick function
165+
const longRunningFastTimer = timers.setTimeout(() => {}, 1e10)
166+
167+
const start = timers.now()
168+
169+
const timeout = timers.setFastTimeout(() => {
170+
const delta = (timers.now() - start) - 2493
171+
172+
t.ok(delta >= -1n, `refreshed timer fired early (${delta} ms)`)
173+
t.ok(delta < ACCEPTABLE_DELTA, `refreshed timer fired late (${delta} ms)`)
174+
}, 1001)
175+
176+
timers.tick(250)
177+
timeout.refresh()
178+
179+
timers.tick(250)
180+
timeout.refresh()
181+
182+
timers.tick(250)
183+
timeout.refresh()
184+
185+
timers.tick(250)
186+
timeout.refresh()
187+
188+
timers.tick(1000)
189+
190+
timers.clearTimeout(longRunningFastTimer)
191+
setTimeout(() => t.ok(true), 500)
175192
await t.completed
176193
})
177194

@@ -190,8 +207,8 @@ describe('timers', () => {
190207
await new Promise((resolve) => setTimeout(resolve, 1))
191208

192209
t.strictEqual(timers.now() - startInternalClock, 499)
193-
await new Promise((resolve) => setTimeout(resolve, 1000))
194-
t.ok(timers.now() - startInternalClock <= 1497)
210+
timers.tick(1000)
211+
t.ok(timers.now() - startInternalClock <= 1588)
195212

196213
timers.clearTimeout(longRunningFastTimer)
197214
})

0 commit comments

Comments
 (0)