-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
alternative stack trace approach #2119
Changes from all commits
b503aec
ab4415b
fa80d80
9f4ff1d
ce0fccb
d62aa68
01938e3
d69c47c
515f93a
c2fc0df
ce039a3
519b704
388f758
31785e2
17215a8
ef19e77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
@spawn | ||
@source-mapping | ||
Feature: Stack traces | ||
Background: | ||
Given a file named "features/a.feature" with: | ||
""" | ||
Feature: some feature | ||
Scenario: some scenario | ||
Given a passing step | ||
And a failing step | ||
""" | ||
|
||
Rule: Source maps are respected when dealing with transpiled support code | ||
|
||
Just-in-time transpilers like `@babel/register` and `ts-node` emit source maps with | ||
the transpiled code. Cucumber users expect stack traces to point to the line and column | ||
in the original source file when there is an error. | ||
|
||
Background: | ||
Given a file named "features/steps.ts" with: | ||
""" | ||
import { Given } from '@cucumber/cucumber' | ||
|
||
interface Something { | ||
field1: string | ||
field2: string | ||
} | ||
|
||
Given('a passing step', function() {}) | ||
|
||
Given('a failing step', function() { | ||
throw new Error('boom') | ||
}) | ||
""" | ||
|
||
Scenario: commonjs | ||
When I run cucumber-js with `--require-module ts-node/register --require features/steps.ts` | ||
Then the output contains the text: | ||
""" | ||
/features/steps.ts:11:9 | ||
""" | ||
And it fails | ||
|
||
Scenario: esm | ||
Given my env includes "{\"NODE_OPTIONS\":\"--loader ts-node/esm\"}" | ||
When I run cucumber-js with `--import features/steps.ts` | ||
Then the output contains the text: | ||
""" | ||
/features/steps.ts:11:9 | ||
""" | ||
And it fails |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import path from 'path' | ||
import { valueOrDefault } from './value_checker' | ||
import { StackFrame } from 'error-stack-parser' | ||
|
||
const projectRootPath = path.join(__dirname, '..') | ||
const projectChildDirs = ['src', 'lib', 'node_modules'] | ||
|
||
export function isFileNameInCucumber(fileName: string): boolean { | ||
return projectChildDirs.some((dir) => | ||
fileName.startsWith(path.join(projectRootPath, dir)) | ||
) | ||
} | ||
|
||
export function filterStackTrace(frames: StackFrame[]): StackFrame[] { | ||
if (isErrorInCucumber(frames)) { | ||
return frames | ||
} | ||
const index = frames.findIndex((x) => isFrameInCucumber(x)) | ||
if (index === -1) { | ||
return frames | ||
} | ||
return frames.slice(0, index) | ||
} | ||
|
||
function isErrorInCucumber(frames: StackFrame[]): boolean { | ||
const filteredFrames = frames.filter((x) => !isFrameInNode(x)) | ||
return filteredFrames.length > 0 && isFrameInCucumber(filteredFrames[0]) | ||
} | ||
|
||
function isFrameInCucumber(frame: StackFrame): boolean { | ||
const fileName = valueOrDefault(frame.getFileName(), '') | ||
return isFileNameInCucumber(fileName) | ||
} | ||
|
||
function isFrameInNode(frame: StackFrame): boolean { | ||
const fileName = valueOrDefault(frame.getFileName(), '') | ||
return !fileName.includes(path.sep) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { format } from 'assertion-error-formatter' | ||
import errorStackParser from 'error-stack-parser' | ||
import { filterStackTrace } from '../filter_stack_trace' | ||
|
||
export function formatError(error: Error, filterStackTraces: boolean): string { | ||
let filteredStack: string | ||
if (filterStackTraces) { | ||
try { | ||
filteredStack = filterStackTrace(errorStackParser.parse(error)) | ||
.map((f) => f.source) | ||
.join('\n') | ||
} catch { | ||
// if we weren't able to parse and filter, we'll settle for the original | ||
} | ||
} | ||
return format(error, { | ||
colorFns: { | ||
errorStack: (stack: string) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm kind of abusing the |
||
filteredStack ? `\n${filteredStack}` : stack, | ||
}, | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
nyc
instrumentation seems to mess these scenarios up when we check for coverage. We could try and fix up the environment for the child process but it didn't feel worth it.