Skip to content
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

feat: filter stacktraces (fix #1999) #4338

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,34 @@ export default defineConfig({
})
```

### onStackTrace

- **Type**: `(error: Error, frame: ParsedStack) => boolean | void`
clarkf marked this conversation as resolved.
Show resolved Hide resolved
- **Version**: Since Vitest 1.0.0-beta.3

Apply a filtering function to each frame of each stacktrace when handling errors. The first argument, `error`, is an object with the same properties as a standard `Error`, but it is not an actual instance.

Can be useful for filtering out stacktrace frames from third-party libraries.

```ts
import type { ParsedStack } from 'vitest'
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
onStackTrace(error: Error, { file }: ParsedStack): boolean | void {
// If we've encountered a ReferenceError, show the whole stack.
sheremet-va marked this conversation as resolved.
Show resolved Hide resolved
if (error.name === 'ReferenceError')
return

// Reject all frames from third party libraries.
if (file.includes('node_modules'))
return false
},
},
})
```

### diff

- **Type:** `string`
Expand Down
6 changes: 5 additions & 1 deletion packages/utils/src/source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type { SourceMapInput } from '@jridgewell/trace-mapping'
export interface StackTraceParserOptions {
ignoreStackEntries?: (RegExp | string)[]
getSourceMap?: (file: string) => unknown
frameFilter?: (error: Error, frame: ParsedStack) => boolean | void
}

const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m
Expand Down Expand Up @@ -179,7 +180,10 @@ export function parseErrorStacktrace(e: ErrorWithDiff, options: StackTraceParser
return e.stacks

const stackStr = e.stack || e.stackStr || ''
const stackFrames = parseStacktrace(stackStr, options)
let stackFrames = parseStacktrace(stackStr, options)

if (options.frameFilter)
stackFrames = stackFrames.filter(f => options.frameFilter!(e, f) !== false)

e.stacks = stackFrames
return stackFrames
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export async function printError(error: unknown, project: WorkspaceProject | und
const parserOptions: StackTraceParserOptions = {
// only browser stack traces require remapping
getSourceMap: file => project.getBrowserSourceMapModuleById(file),
frameFilter: project.config.onStackTrace,
}

if (fullStack)
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/reporters/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export class JsonReporter implements Reporter {
const project = this.ctx.getProjectByTaskId(test.id)
const stack = parseErrorStacktrace(error, {
getSourceMap: file => project.getBrowserSourceMapModuleById(file),
frameFilter: this.ctx.config.onStackTrace,
})
const frame = stack[0]
if (!frame)
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/reporters/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class JUnitReporter implements Reporter {
const project = this.ctx.getProjectByTaskId(task.id)
const stack = parseErrorStacktrace(error, {
getSourceMap: file => project.getBrowserSourceMapModuleById(file),
frameFilter: this.ctx.config.onStackTrace,
})

// TODO: This is same as printStack but without colors. Find a way to reuse code.
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/reporters/tap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export class TapReporter implements Reporter {
task.result.errors.forEach((error) => {
const stacks = parseErrorStacktrace(error, {
getSourceMap: file => project.getBrowserSourceMapModuleById(file),
frameFilter: this.ctx.config.onStackTrace,
})
const stack = stacks[0]

Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ export class WorkspaceProject {
resolveSnapshotPath: undefined,
},
onConsoleLog: undefined!,
onStackTrace: undefined!,
sequence: {
...this.ctx.config.sequence,
sequencer: undefined!,
Expand Down
11 changes: 10 additions & 1 deletion packages/vitest/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { JSDOMOptions } from './jsdom-options'
import type { HappyDOMOptions } from './happy-dom-options'
import type { Reporter } from './reporter'
import type { SnapshotStateOptions } from './snapshot'
import type { Arrayable } from './general'
import type { Arrayable, ParsedStack } from './general'
import type { BenchmarkUserOptions } from './benchmark'
import type { BrowserConfigOptions, ResolvedBrowserOptions } from './browser'
import type { Pool, PoolOptions } from './pool-options'
Expand Down Expand Up @@ -537,6 +537,14 @@ export interface InlineConfig {
*/
onConsoleLog?: (log: string, type: 'stdout' | 'stderr') => false | void

/**
* Enable stack trace filtering. If absent, all stack trace frames
* will be shown.
*
* Return `false` to omit the frame.
*/
onStackTrace?: (error: Error, frame: ParsedStack) => boolean | void

/**
* Indicates if CSS files should be processed.
*
Expand Down Expand Up @@ -788,6 +796,7 @@ export type ProjectConfig = Omit<
| 'resolveSnapshotPath'
| 'passWithNoTests'
| 'onConsoleLog'
| 'onStackTrace'
| 'dangerouslyIgnoreUnhandledErrors'
| 'slowTestThreshold'
| 'inspect'
Expand Down
21 changes: 21 additions & 0 deletions test/stacktraces/fixtures/error-with-stack.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from 'vitest'

test('error in deps', () => {
a()
})

function a() {
b()
}

function b() {
c()
}

function c() {
d()
}

function d() {
throw new Error('Something truly horrible has happened!')
}
32 changes: 32 additions & 0 deletions test/stacktraces/test/__snapshots__/runner.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`stacktrace filtering > filters stacktraces > stacktrace-filtering 1`] = `
"⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL error-with-stack.test.js > error in deps
Error: Something truly horrible has happened!
❯ d error-with-stack.test.js:20:9
18|
19| function d() {
20| throw new Error('Something truly horrible has happened!')
| ^
21| }
22|
❯ c error-with-stack.test.js:16:3
❯ a error-with-stack.test.js:8:3
❯ error-with-stack.test.js:4:3

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

"
`;

exports[`stacktrace should print error frame source file correctly > error-in-deps > error-in-deps 1`] = `
"⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

Expand Down Expand Up @@ -71,6 +92,17 @@ exports[`stacktraces should respect sourcemaps > error-in-deps.test.js > error-i
"
`;

exports[`stacktraces should respect sourcemaps > error-with-stack.test.js > error-with-stack.test.js 1`] = `
" ❯ d error-with-stack.test.js:20:9
18|
19| function d() {
20| throw new Error('Something truly horrible has happened!')
| ^
21| }
22|
❯ c error-with-stack.test.js:16:3"
`;

exports[`stacktraces should respect sourcemaps > mocked-global.test.js > mocked-global.test.js 1`] = `
" ❯ mocked-global.test.js:6:13
4|
Expand Down
14 changes: 14 additions & 0 deletions test/stacktraces/test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,17 @@ describe('stacktrace should print error frame source file correctly', async () =
expect(stderr).toMatchSnapshot('error-in-deps')
}, 30000)
})

describe('stacktrace filtering', async () => {
const root = resolve(__dirname, '../fixtures')
const testFile = resolve(root, './error-with-stack.test.js')

it('filters stacktraces', async () => {
const { stderr } = await runVitest({
root,
onStackTrace: (_error, { method }) => method !== 'b',
}, [testFile])

expect(stderr).toMatchSnapshot('stacktrace-filtering')
}, 30000)
})
Loading