Skip to content

Commit

Permalink
feat: filter stacktraces (fix #1999)
Browse files Browse the repository at this point in the history
This change introduces a new optional configuration parameter,
`onStackTrace`. If included, each frame of each error stacktrace
encountered during the test run will be tested by the provided
function. If the test fails, the frame will be omitted from the
displayed trace.
  • Loading branch information
clarkf committed Oct 19, 2023
1 parent 4293e1b commit 9454e96
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 2 deletions.
22 changes: 22 additions & 0 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,28 @@ export default defineConfig({
})
```

### onStackTrace

- **Type**: `(frame: ParsedStack) => boolean`

Apply a filtering function to each frame of each stacktrace when handling errors.

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({ file }: ParsedStack): boolean {
// Reject all frames from third party libraries.
return !file.includes('node_modules')
},
},
})
```

### 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?: (frame: ParsedStack) => boolean
}

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(options.frameFilter)

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?: (frame: ParsedStack) => boolean

/**
* 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:11
18|
19| function d() {
20| throw new Error("Something truly horrible has happened!")
| ^
21| }
22|
❯ c error-with-stack.test.js:16:5
❯ a error-with-stack.test.js:8:5
❯ error-with-stack.test.js:4:5
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[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:11
18|
19| function d() {
20| throw new Error("Something truly horrible has happened!")
| ^
21| }
22|
❯ c error-with-stack.test.js:16:5"
`;
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: ({ method }) => method !== 'b',
}, [testFile])

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

0 comments on commit 9454e96

Please sign in to comment.