Skip to content

Commit ee40437

Browse files
authored
fix TypeScript error stack traces (#5403)
1 parent ea77b5d commit ee40437

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

lib/command/workers/runTests.js

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { parentPort, workerData } from 'worker_threads'
1111

1212
// Delay imports to avoid ES Module loader race conditions in Node 22.x worker threads
1313
// These will be imported dynamically when needed
14-
let event, container, Codecept, getConfig, tryOrDefault, deepMerge
14+
let event, container, Codecept, getConfig, tryOrDefault, deepMerge, fixErrorStack
1515

1616
let stdout = ''
1717

@@ -21,10 +21,17 @@ const { options, tests, testRoot, workerIndex, poolMode } = workerData
2121

2222
// Global error handlers to catch critical errors but not test failures
2323
process.on('uncaughtException', (err) => {
24+
if (global.container?.tsFileMapping && fixErrorStack) {
25+
const fileMapping = global.container.tsFileMapping()
26+
if (fileMapping) {
27+
fixErrorStack(err, fileMapping)
28+
}
29+
}
30+
2431
// Log to stderr to bypass stdout suppression
2532
process.stderr.write(`[Worker ${workerIndex}] UNCAUGHT EXCEPTION: ${err.message}\n`)
2633
process.stderr.write(`${err.stack}\n`)
27-
34+
2835
// Don't exit on test assertion errors - those are handled by mocha
2936
if (err.name === 'AssertionError' || err.message?.includes('expected')) {
3037
return
@@ -33,13 +40,20 @@ process.on('uncaughtException', (err) => {
3340
})
3441

3542
process.on('unhandledRejection', (reason, promise) => {
43+
if (reason && typeof reason === 'object' && reason.stack && global.container?.tsFileMapping && fixErrorStack) {
44+
const fileMapping = global.container.tsFileMapping()
45+
if (fileMapping) {
46+
fixErrorStack(reason, fileMapping)
47+
}
48+
}
49+
3650
// Log to stderr to bypass stdout suppression
3751
const msg = reason?.message || String(reason)
3852
process.stderr.write(`[Worker ${workerIndex}] UNHANDLED REJECTION: ${msg}\n`)
3953
if (reason?.stack) {
4054
process.stderr.write(`${reason.stack}\n`)
4155
}
42-
56+
4357
// Don't exit on test-related rejections
4458
if (msg.includes('expected') || msg.includes('AssertionError')) {
4559
return
@@ -132,13 +146,15 @@ initPromise = (async function () {
132146
const utilsModule = await import('../utils.js')
133147
const coreUtilsModule = await import('../../utils.js')
134148
const CodeceptModule = await import('../../codecept.js')
135-
149+
const typescriptModule = await import('../../utils/typescript.js')
150+
136151
event = eventModule.default
137152
container = containerModule.default
138153
getConfig = utilsModule.getConfig
139154
tryOrDefault = coreUtilsModule.tryOrDefault
140155
deepMerge = coreUtilsModule.deepMerge
141156
Codecept = CodeceptModule.default
157+
fixErrorStack = typescriptModule.fixErrorStack
142158

143159
const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
144160

@@ -147,6 +163,12 @@ initPromise = (async function () {
147163
// IMPORTANT: await is required here since getConfig is async
148164
baseConfig = await getConfig(options.config || testRoot)
149165
} catch (configErr) {
166+
if (global.container?.tsFileMapping && fixErrorStack) {
167+
const fileMapping = global.container.tsFileMapping()
168+
if (fileMapping) {
169+
fixErrorStack(configErr, fileMapping)
170+
}
171+
}
150172
process.stderr.write(`[Worker ${workerIndex}] FAILED loading config: ${configErr.message}\n`)
151173
process.stderr.write(`${configErr.stack}\n`)
152174
await new Promise(resolve => setTimeout(resolve, 100))
@@ -163,6 +185,12 @@ initPromise = (async function () {
163185
try {
164186
await codecept.init(testRoot)
165187
} catch (initErr) {
188+
if (global.container?.tsFileMapping && fixErrorStack) {
189+
const fileMapping = global.container.tsFileMapping()
190+
if (fileMapping) {
191+
fixErrorStack(initErr, fileMapping)
192+
}
193+
}
166194
process.stderr.write(`[Worker ${workerIndex}] FAILED during codecept.init(): ${initErr.message}\n`)
167195
process.stderr.write(`${initErr.stack}\n`)
168196
process.exit(1)
@@ -190,6 +218,12 @@ initPromise = (async function () {
190218
parentPort?.close()
191219
}
192220
} catch (err) {
221+
if (global.container?.tsFileMapping && fixErrorStack) {
222+
const fileMapping = global.container.tsFileMapping()
223+
if (fileMapping) {
224+
fixErrorStack(err, fileMapping)
225+
}
226+
}
193227
process.stderr.write(`[Worker ${workerIndex}] FATAL ERROR: ${err.message}\n`)
194228
process.stderr.write(`${err.stack}\n`)
195229
process.exit(1)

lib/mocha/cli.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import event from '../event.js'
99
import AssertionFailedError from '../assert/error.js'
1010
import output from '../output.js'
1111
import test, { cloneTest } from './test.js'
12+
import { fixErrorStack } from '../utils/typescript.js'
1213

1314
// Get version from package.json to avoid circular dependency
1415
const __filename = fileURLToPath(import.meta.url)
@@ -264,6 +265,11 @@ class Cli extends Base {
264265
}
265266

266267
try {
268+
const fileMapping = global.container?.tsFileMapping?.()
269+
if (fileMapping) {
270+
fixErrorStack(err, fileMapping)
271+
}
272+
267273
let stack = err.stack
268274
stack = (stack || '').replace(originalMessage, '')
269275
stack = stack ? stack.split('\n') : []

lib/mocha/factory.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import gherkinParser, { loadTranslations } from './gherkin.js'
77
import output from '../output.js'
88
import scenarioUiFunction from './ui.js'
99
import { initMochaGlobals } from '../globals.js'
10+
import { fixErrorStack } from '../utils/typescript.js'
1011

1112
const __filename = fileURLToPath(import.meta.url)
1213
const __dirname = fsPath.dirname(__filename)
@@ -34,6 +35,10 @@ class MochaFactory {
3435
// Handle ECONNREFUSED without dynamic import for now
3536
err = new Error('Connection refused: ' + err.toString())
3637
}
38+
const fileMapping = global.container?.tsFileMapping?.()
39+
if (fileMapping) {
40+
fixErrorStack(err, fileMapping)
41+
}
3742
output.error(err)
3843
output.print(err.stack)
3944
process.exit(1)

0 commit comments

Comments
 (0)