Skip to content

Commit 8199f9c

Browse files
authored
test_runner: make it compatible with fake timers
Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #59272 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent abff716 commit 8199f9c

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

lib/internal/test_runner/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ function stopTest(timeout, signal) {
149149

150150
disposeFunction = () => {
151151
abortListener[SymbolDispose]();
152-
timer[SymbolDispose]();
152+
clearTimeout(timer);
153153
};
154154
}
155155

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
3+
// Simulate @sinonjs/fake-timers: patch the timers module BEFORE
4+
// the test runner is loaded, so the test runner captures the patched
5+
// versions at import time.
6+
const nodeTimers = require('node:timers');
7+
const originalSetTimeout = nodeTimers.setTimeout;
8+
const originalClearTimeout = nodeTimers.clearTimeout;
9+
10+
const fakeTimers = new Map();
11+
let nextId = 1;
12+
13+
nodeTimers.setTimeout = (fn, delay, ...args) => {
14+
const id = nextId++;
15+
const timer = originalSetTimeout(fn, delay, ...args);
16+
fakeTimers.set(id, timer);
17+
// Sinon fake timers return an object with unref/ref but without
18+
// Symbol.dispose, which would cause the test runner to throw.
19+
return { id, unref() {}, ref() {} };
20+
};
21+
22+
nodeTimers.clearTimeout = (id) => {
23+
if (id != null && typeof id === 'object') id = id.id;
24+
const timer = fakeTimers.get(id);
25+
if (timer) {
26+
originalClearTimeout(timer);
27+
fakeTimers.delete(id);
28+
}
29+
};
30+
31+
// Now load the test runner - it will capture our patched setTimeout/clearTimeout
32+
const { test } = require('node:test');
33+
34+
test('test with fake timers and timeout', { timeout: 10_000 }, () => {
35+
// This test verifies that the test runner works when setTimeout returns
36+
// an object without Symbol.dispose (like sinon fake timers).
37+
// Previously, the test runner called timer[Symbol.dispose]() which would
38+
// throw TypeError on objects returned by fake timer implementations.
39+
});
40+
41+
// Restore
42+
nodeTimers.setTimeout = originalSetTimeout;
43+
nodeTimers.clearTimeout = originalClearTimeout;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
require('../common');
3+
const fixtures = require('../common/fixtures');
4+
const assert = require('node:assert');
5+
const { spawnSync } = require('node:child_process');
6+
const { test } = require('node:test');
7+
8+
test('mock timers do not break test timeout cleanup', async () => {
9+
const fixture = fixtures.path('test-runner', 'mock-timers-with-timeout.js');
10+
const cp = spawnSync(process.execPath, ['--test', fixture], {
11+
timeout: 30_000,
12+
});
13+
assert.strictEqual(cp.status, 0, `Test failed:\nstdout: ${cp.stdout}\nstderr: ${cp.stderr}`);
14+
});

0 commit comments

Comments
 (0)