Skip to content

Commit e7f267e

Browse files
committed
feat(runner): provide meaningful stack on timeouts
1 parent 931c5a2 commit e7f267e

File tree

3 files changed

+45
-18
lines changed

3 files changed

+45
-18
lines changed

src/runner/TestContext.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,51 @@ export function setTestContext(on: object, context: TestSuite|TestGroup) {
2929
)
3030
}
3131
}
32+
3233
const test = (
3334
title: string,
3435
callback: TestCallback,
3536
timeout?: number,
3637
) => {
37-
new TestFunction(context, title, callback, timeout)
38+
const error = new Error()
39+
const getErrorCallStack = async () => {
40+
return error.stack?.split('\n').slice(2).join('\n')
41+
}
42+
43+
new TestFunction(
44+
context,
45+
title,
46+
callback,
47+
timeout,
48+
getErrorCallStack,
49+
)
3850
}
39-
test.each = <Args>(cases: Iterable<Args>) => (
40-
title: string,
41-
cb: TestCallback<Args extends (unknown[] | readonly unknown[]) ? [...Args] : [Args]>,
42-
timeout?: number,
43-
) => {
44-
for (const args of cases) {
45-
const argsArray = (Array.isArray(args) ? args : [args]) as (Args extends (unknown[] | readonly unknown[]) ? [...Args] : [Args])
46-
test(
47-
vsprintf(title, argsArray),
48-
function(this: TestFunction) {
49-
return cb.apply(this, argsArray)
50-
},
51-
timeout,
52-
)
51+
test.each = <Args>(cases: Iterable<Args>) => {
52+
const error = new Error()
53+
const getErrorCallStack = async () => {
54+
return error.stack?.split('\n').slice(2).join('\n')
55+
}
56+
57+
return (
58+
title: string,
59+
cb: TestCallback<Args extends (unknown[] | readonly unknown[]) ? [...Args] : [Args]>,
60+
timeout?: number,
61+
) => {
62+
for (const args of cases) {
63+
const argsArray = (Array.isArray(args) ? args : [args]) as (Args extends (unknown[] | readonly unknown[]) ? [...Args] : [Args])
64+
new TestFunction(
65+
context,
66+
vsprintf(title, argsArray),
67+
function(this: TestFunction) {
68+
return cb.apply(this, argsArray)
69+
},
70+
timeout,
71+
getErrorCallStack,
72+
)
73+
}
5374
}
5475
}
76+
5577
const beforeAll = (cb: BeforeCallback) => {
5678
context.beforeAll.push(cb)
5779
}

src/runner/TestNode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export class TestFunction extends TestChildNode {
6161
readonly title: string,
6262
readonly callback: TestCallback,
6363
readonly timeout?: number,
64+
readonly getCallStack?: () => Promise<string|undefined>,
6465
) {
6566
super(parent, title)
6667
}

src/runner/TestRunner.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,19 +148,23 @@ export class TestRunner {
148148
const t0 = this.clock()
149149
let timer: number|undefined
150150
let reject: () => void = () => void 0
151+
const timeoutSymbol = Symbol('timeout')
151152
try {
152153
await Promise.race([
153154
new Promise((res, rej) => {
154-
timer = this.setTimeout(() => rej(
155-
new TimeoutError(`Test "${test.title}" timed out after ${timeout}ms.`),
156-
), timeout)
155+
timer = this.setTimeout(() => rej(timeoutSymbol), timeout)
157156
}),
158157
test.callback.call(test),
159158
])
160159
const duration = this.clock() - t0
161160
return new TestResult(test, undefined, duration)
162161
} catch (e) {
163162
const duration = this.clock() - t0
163+
if (e === timeoutSymbol) {
164+
const error = new TimeoutError(`Test "${test.title}" timed out after ${timeout}ms.`)
165+
error.stack = `${error.name}: ${error.message}\n${await test.getCallStack?.() ?? ''}`
166+
return new TestResult(test, error, duration)
167+
}
164168
return new TestResult(test, this.normalizeError(e), duration)
165169
} finally {
166170
reject()

0 commit comments

Comments
 (0)