Skip to content

Commit

Permalink
fix: use correct source maps in stacktrace (#2027)
Browse files Browse the repository at this point in the history
* [vitest] use correct source maps in stacktrace

use vitenode server to get a fetch result instead of a transform result.
And then get the sourcemap from it.

* [vitest] refactor

* add tests for stack traces and sourcemaps

* commit lockfile

* fix linter

* fix types linter

* remove dynamic content from snapshot

* push the correct snapshot

* fix tests :)

* increase timeout for runner tests

* decrease timeout .. not the culprit in CI

* move continue below map assignment

* guard against accessing vitenode server

* reduce the size of snapshots

* rename timeout to prevent running more than one
  • Loading branch information
haikyuu committed Oct 7, 2022
1 parent 53d8f3b commit d1919a0
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 4 deletions.
8 changes: 5 additions & 3 deletions packages/vitest/src/utils/source-map.ts
Expand Up @@ -25,10 +25,12 @@ export async function interpretSourcePos(stackFrames: ParsedStack[], ctx: Vitest
for (const frame of stackFrames) {
if ('sourcePos' in frame)
continue
const transformResult = ctx.server.moduleGraph.getModuleById(frame.file)?.ssrTransformResult
if (!transformResult)
const ssrTransformResult = ctx.server.moduleGraph.getModuleById(frame.file)?.ssrTransformResult
const fetchResult = ctx.vitenode?.fetchCache.get(frame.file)?.result
const map = fetchResult?.map || ssrTransformResult?.map
if (!map)
continue
const sourcePos = await getOriginalPos(transformResult.map as any as RawSourceMap | undefined, frame)
const sourcePos = await getOriginalPos(map as any as RawSourceMap, frame)
if (sourcePos)
frame.sourcePos = sourcePos
}
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
2 changes: 1 addition & 1 deletion test/fails/test/__snapshots__/runner.test.ts.snap
Expand Up @@ -14,4 +14,4 @@ exports[`should fails > nested-suite.test.ts > nested-suite.test.ts 1`] = `"Asse
exports[`should fails > stall.test.ts > stall.test.ts 1`] = `"TypeError: failure"`;
exports[`should fails > timeout.test.ts > timeout.test.ts 1`] = `"Error: Test timed out in 10ms."`;
exports[`should fails > test-timeout.test.ts > test-timeout.test.ts 1`] = `"Error: Test timed out in 10ms."`;
9 changes: 9 additions & 0 deletions test/stacktraces/fixtures/add-in-imba.test.imba
@@ -0,0 +1,9 @@
import {it, expect} from 'vitest'

export def add(...args)
return args.reduce((do(a, b) a + b), 0)

it "add", do
expect(add()).toBe 0
expect(add(1)).toBe 3
expect(add(1, 2, 3)).toBe 6
9 changes: 9 additions & 0 deletions test/stacktraces/fixtures/add-in-js.test.js
@@ -0,0 +1,9 @@
/* body */
import { expect, it } from 'vitest'
import { add } from './utils'

it('add', () => {
expect(add()).toBe(100)
expect(add(1)).toBe(1)
return expect(add(1, 2, 3)).toBe(6)
})
15 changes: 15 additions & 0 deletions test/stacktraces/fixtures/add.test.ts
@@ -0,0 +1,15 @@
/* body */
import { expect, it } from 'vitest'
import { add } from './utils'

interface Num {
count: number
}

const a: Num = { count: 10 }

it('add', () => {
expect(add(a.count)).toBe(100)
expect(add(1)).toBe(1)
return expect(add(1, 2, 3)).toBe(6)
})
3 changes: 3 additions & 0 deletions test/stacktraces/fixtures/utils.ts
@@ -0,0 +1,3 @@
export function add(...args: number[]) {
return args.reduce((a, b) => { return a + b }, 0)
}
32 changes: 32 additions & 0 deletions test/stacktraces/fixtures/vite.config.ts
@@ -0,0 +1,32 @@
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [{
name: 'vite-plugin-imba',
transform(code, id) {
if (id.endsWith('.imba')) {
return {
code:
'\n/*body*/\nimport {it,expect} from \'vitest\';\n\nexport function add(...args){\n\t\n\treturn args.reduce(function(a,b) { return a + b; },0);\n};\n\nit("add",function() {\n\t\n\texpect(add()).toBe(0);\n\texpect(add(1)).toBe(3);\n\treturn expect(add(1,2,3)).toBe(6);\n});\n\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMudGVzdC5pbWJhIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidXRpbHMudGVzdC5pbWJhIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7aXQsIGV4cGVjdH0gZnJvbSAndml0ZXN0J1xuXG5leHBvcnQgZGVmIGFkZCguLi5hcmdzKVxuXHRyZXR1cm4gYXJncy5yZWR1Y2UoKGRvKGEsIGIpIGEgKyBiKSwgMClcblxuaXQgXCJhZGRcIiwgZG9cblx0ZXhwZWN0KGFkZCgpKS50b0JlIDBcblx0ZXhwZWN0KGFkZCgxKSkudG9CZSAzXG5cdGV4cGVjdChhZGQoMSwgMiwgMykpLnRvQmUgNlxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsTUFBTSxFQUFFLEVBQUUsQ0FBRSxNQUFNLE9BQU8sUUFBUTs7QUFFakMsTUFBTSxDQUFDLFFBQUcsQ0FBQyxHQUFHLElBQUksSUFBSSxDQUFDOztDQUN0QixNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBRSxRQUFFLENBQUMsQ0FBQyxDQUFFLENBQUMsSUFBRSxPQUFBLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFHLENBQUMsQ0FBQztDQUFBOztBQUV4QyxFQUFFLENBQUMsS0FBSyxDQUFFLFFBQUUsR0FBQTs7Q0FDWCxNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFBO0NBQ3BCLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFBO0NBQ3JCLE9BQUEsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUUsQ0FBQyxDQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtDQUFBLENBSDFCO0FBSUY7In0=',
map: {
version: 3,
file: 'add-in-imba.test.imba',
names: [],
sourceRoot: '',
sources: ['add-in-imba.test.imba'],
sourcesContent: [
'import {it, expect} from \'vitest\'\n\nexport def add(...args)\n\treturn args.reduce((do(a, b) a + b), 0)\n\nit "add", do\n\texpect(add()).toBe 0\n\texpect(add(1)).toBe 3\n\texpect(add(1, 2, 3)).toBe 6\n',
],
mappings:
';;AAAA,MAAM,EAAE,EAAE,CAAE,MAAM,OAAO,QAAQ;;AAEjC,MAAM,CAAC,QAAG,CAAC,GAAG,IAAI,IAAI,CAAC;;CACtB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAE,QAAE,CAAC,CAAC,CAAE,CAAC,IAAE,OAAA,CAAC,CAAC,CAAC,CAAC,CAAC,IAAG,CAAC,CAAC;CAAA;;AAExC,EAAE,CAAC,KAAK,CAAE,QAAE,GAAA;;CACX,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;CACpB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;CACrB,OAAA,MAAM,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;CAAA,CAH1B;AAIF;',
},
}
}
},
}],
test: {
threads: false,
isolate: false,
include: ['**/*.{test,spec}.{imba,js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
})
13 changes: 13 additions & 0 deletions test/stacktraces/package.json
@@ -0,0 +1,13 @@
{
"name": "@vitest/test-fails",
"type": "module",
"private": true,
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
},
"devDependencies": {
"execa": "^6.1.0",
"vitest": "workspace:*"
}
}
34 changes: 34 additions & 0 deletions test/stacktraces/test/__snapshots__/runner.test.ts.snap
@@ -0,0 +1,34 @@
// Vitest Snapshot v1

exports[`stacktraces should respect sourcemaps > add.test.ts > add.test.ts 1`] = `
" ❯ add.test.ts:12:23
10|
11| it('add', () => {
12| expect(add(a.count)).toBe(100)
| ^
13| expect(add(1)).toBe(1)
14| return expect(add(1, 2, 3)).toBe(6)
"
`;
exports[`stacktraces should respect sourcemaps > add-in-imba.test.imba > add-in-imba.test.imba 1`] = `
" ❯ add-in-imba.test.imba:8:16
6| it \\"add\\", do
7| expect(add()).toBe 0
8| expect(add(1)).toBe 3
| ^
9| expect(add(1, 2, 3)).toBe 6
10|
"
`;
exports[`stacktraces should respect sourcemaps > add-in-js.test.js > add-in-js.test.js 1`] = `
" ❯ add-in-js.test.js:6:17
4|
5| it('add', () => {
6| expect(add()).toBe(100)
| ^
7| expect(add(1)).toBe(1)
8| return expect(add(1, 2, 3)).toBe(6)
"
`;
36 changes: 36 additions & 0 deletions test/stacktraces/test/runner.test.ts
@@ -0,0 +1,36 @@
import { resolve } from 'pathe'
import fg from 'fast-glob'
import { execa } from 'execa'
import { describe, expect, it } from 'vitest'

describe('stacktraces should respect sourcemaps', async () => {
const root = resolve(__dirname, '../fixtures')
const files = await fg('*.test.*', { cwd: root })

for (const file of files) {
it(file, async () => {
// in Windows child_process is very unstable, we skip testing it
if (process.platform === 'win32' && process.env.CI)
return

let error: any
await execa('npx', ['vitest', 'run', file], {
cwd: root,
env: {
...process.env,
CI: 'true',
NO_COLOR: 'true',
},
})
.catch((e) => {
error = e
})

expect(error).toBeTruthy()
const lines = String(error).split(/\n/g)
const index = lines.findIndex(val => val.includes(`${file}:`))
const msg = lines.slice(index, index + 8).join('\n')
expect(msg).toMatchSnapshot(file)
}, 10000)
}
})
7 changes: 7 additions & 0 deletions test/stacktraces/vite.config.ts
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'

export default defineConfig({
test: {
include: ['test/*.test.ts'],
},
})

0 comments on commit d1919a0

Please sign in to comment.