Skip to content

Commit 200609c

Browse files
authored
fix(reporter): use default error formatter for JUnit (#5629)
1 parent eeaebff commit 200609c

File tree

9 files changed

+188
-390
lines changed

9 files changed

+188
-390
lines changed

Diff for: packages/vitest/src/node/error.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable prefer-template */
22
import { existsSync, readFileSync } from 'node:fs'
3+
import { Writable } from 'node:stream'
34
import { normalize, relative } from 'pathe'
45
import c from 'picocolors'
56
import cliTruncate from 'cli-truncate'
@@ -13,7 +14,7 @@ import { TypeCheckError } from '../typecheck/typechecker'
1314
import { isPrimitive } from '../utils'
1415
import type { Vitest } from './core'
1516
import { divider } from './reporters/renderers/utils'
16-
import type { Logger } from './logger'
17+
import { Logger } from './logger'
1718
import type { WorkspaceProject } from './workspace'
1819

1920
interface PrintErrorOptions {
@@ -27,6 +28,26 @@ interface PrintErrorResult {
2728
nearest?: ParsedStack
2829
}
2930

31+
// use Logger with custom Console to capture entire error printing
32+
export async function captuerPrintError(
33+
error: unknown,
34+
ctx: Vitest,
35+
project: WorkspaceProject,
36+
) {
37+
let output = ''
38+
const writable = new Writable({
39+
write(chunk, _encoding, callback) {
40+
output += String(chunk)
41+
callback()
42+
},
43+
})
44+
const result = await printError(error, project, {
45+
showCodeFrame: false,
46+
logger: new Logger(ctx, writable, writable),
47+
})
48+
return { nearest: result?.nearest, output }
49+
}
50+
3051
export async function printError(error: unknown, project: WorkspaceProject | undefined, options: PrintErrorOptions): Promise<PrintErrorResult | undefined> {
3152
const { showCodeFrame = true, fullStack = false, type } = options
3253
const logger = options.logger

Diff for: packages/vitest/src/node/reporters/github-actions.ts

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { Writable } from 'node:stream'
21
import { getTasks } from '@vitest/runner/utils'
32
import stripAnsi from 'strip-ansi'
43
import type { File, Reporter, Vitest } from '../../types'
54
import { getFullName } from '../../utils'
6-
import { printError } from '../error'
7-
import { Logger } from '../logger'
5+
import { captuerPrintError } from '../error'
86
import type { WorkspaceProject } from '../workspace'
97

108
export class GithubActionsReporter implements Reporter {
@@ -44,7 +42,7 @@ export class GithubActionsReporter implements Reporter {
4442

4543
// format errors via `printError`
4644
for (const { project, title, error } of projectErrors) {
47-
const result = await printErrorWrapper(error, this.ctx, project)
45+
const result = await captuerPrintError(error, this.ctx, project)
4846
const stack = result?.nearest
4947
if (!stack)
5048
continue
@@ -63,23 +61,6 @@ export class GithubActionsReporter implements Reporter {
6361
}
6462
}
6563

66-
// use Logger with custom Console to extract messgage from `processError` util
67-
// TODO: maybe refactor `processError` to require single function `(message: string) => void` instead of full Logger?
68-
async function printErrorWrapper(error: unknown, ctx: Vitest, project: WorkspaceProject) {
69-
let output = ''
70-
const writable = new Writable({
71-
write(chunk, _encoding, callback) {
72-
output += String(chunk)
73-
callback()
74-
},
75-
})
76-
const result = await printError(error, project, {
77-
showCodeFrame: false,
78-
logger: new Logger(ctx, writable, writable),
79-
})
80-
return { nearest: result?.nearest, output }
81-
}
82-
8364
// workflow command formatting based on
8465
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message
8566
// https://github.com/actions/toolkit/blob/f1d9b4b985e6f0f728b4b766db73498403fd5ca3/packages/core/src/command.ts#L80-L85

Diff for: packages/vitest/src/node/reporters/junit.ts

+8-29
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import { hostname } from 'node:os'
33
import { dirname, relative, resolve } from 'pathe'
44

55
import type { Task } from '@vitest/runner'
6-
import type { ErrorWithDiff } from '@vitest/utils'
76
import { getSuites } from '@vitest/runner/utils'
7+
import stripAnsi from 'strip-ansi'
88
import type { Vitest } from '../../node'
99
import type { Reporter } from '../../types/reporter'
10-
import { parseErrorStacktrace } from '../../utils/source-map'
11-
import { F_POINTER } from '../../utils/figures'
1210
import { getOutputFile } from '../../utils/config-helpers'
11+
import { captuerPrintError } from '../error'
1312
import { IndentedLogger } from './renderers/indented-logger'
1413

1514
export interface JUnitOptions {
@@ -140,31 +139,6 @@ export class JUnitReporter implements Reporter {
140139
await this.logger.log(`</${name}>`)
141140
}
142141

143-
async writeErrorDetails(task: Task, error: ErrorWithDiff): Promise<void> {
144-
const errorName = error.name ?? error.nameStr ?? 'Unknown Error'
145-
const errorDetails = `${errorName}: ${error.message}`
146-
147-
// Be sure to escape any XML in the error Details
148-
await this.baseLog(escapeXML(errorDetails))
149-
150-
const project = this.ctx.getProjectByTaskId(task.id)
151-
const stack = parseErrorStacktrace(error, {
152-
getSourceMap: file => project.getBrowserSourceMapModuleById(file),
153-
frameFilter: this.ctx.config.onStackTrace,
154-
})
155-
156-
// TODO: This is same as printStack but without colors. Find a way to reuse code.
157-
for (const frame of stack) {
158-
const path = relative(this.ctx.config.root, frame.file)
159-
160-
await this.baseLog(escapeXML(` ${F_POINTER} ${[frame.method, `${path}:${frame.line}:${frame.column}`].filter(Boolean).join(' ')}`))
161-
162-
// reached at test file, skip the follow stack
163-
if (frame.file in this.ctx.state.filesMap)
164-
break
165-
}
166-
}
167-
168142
async writeLogs(task: Task, type: 'err' | 'out'): Promise<void> {
169143
if (task.logs == null || task.logs.length === 0)
170144
return
@@ -205,7 +179,12 @@ export class JUnitReporter implements Reporter {
205179
if (!error)
206180
return
207181

208-
await this.writeErrorDetails(task, error)
182+
const result = await captuerPrintError(
183+
error,
184+
this.ctx,
185+
this.ctx.getProjectByTaskId(task.id),
186+
)
187+
await this.baseLog(escapeXML(stripAnsi(result.output.trim())))
209188
})
210189
}
211190
}

Diff for: test/reporters/fixtures/error.test.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { afterAll, it, expect } from "vitest";
2+
3+
afterAll(() => {
4+
throwSuite()
5+
})
6+
7+
it('stack', () => {
8+
throwDeep()
9+
})
10+
11+
it('diff', () => {
12+
expect({ hello: 'x' }).toEqual({ hello: 'y' })
13+
})
14+
15+
it('unhandled', () => {
16+
(async () => throwSimple())()
17+
})
18+
19+
it('no name object', () => {
20+
throw { noName: 'hi' };
21+
});
22+
23+
it('string', () => {
24+
throw "hi";
25+
});
26+
27+
it('number', () => {
28+
throw 1234;
29+
});
30+
31+
it('number name object', () => {
32+
throw { name: 1234 };
33+
});
34+
35+
it('xml', () => {
36+
throw new Error('error message that has XML in it <div><input/></div>');
37+
})
38+
39+
function throwDeep() {
40+
throwSimple()
41+
}
42+
43+
function throwSimple() {
44+
throw new Error('throwSimple')
45+
}
46+
47+
function throwSuite() {
48+
throw new Error('throwSuite')
49+
}

Diff for: test/reporters/src/data-for-junit.ts

-63
This file was deleted.

Diff for: test/reporters/tests/__snapshots__/junit.test.ts.snap

+80
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,83 @@ Error: afterAll error
3434
</testsuites>
3535
"
3636
`;
37+
38+
exports[`format error 1`] = `
39+
"<?xml version="1.0" encoding="UTF-8" ?>
40+
<testsuites name="vitest tests" tests="9" failures="8" errors="0" time="...">
41+
<testsuite name="error.test.ts" timestamp="..." hostname="..." tests="9" failures="8" errors="0" skipped="0" time="...">
42+
<testcase classname="error.test.ts" name="stack" time="...">
43+
<failure message="throwSimple" type="Error">
44+
Error: throwSimple
45+
❯ throwSimple error.test.ts:44:9
46+
❯ throwDeep error.test.ts:40:3
47+
❯ error.test.ts:8:3
48+
</failure>
49+
</testcase>
50+
<testcase classname="error.test.ts" name="diff" time="...">
51+
<failure message="expected { hello: &apos;x&apos; } to deeply equal { hello: &apos;y&apos; }" type="AssertionError">
52+
AssertionError: expected { hello: &apos;x&apos; } to deeply equal { hello: &apos;y&apos; }
53+
54+
- Expected
55+
+ Received
56+
57+
Object {
58+
- &quot;hello&quot;: &quot;y&quot;,
59+
+ &quot;hello&quot;: &quot;x&quot;,
60+
}
61+
62+
❯ error.test.ts:12:26
63+
</failure>
64+
</testcase>
65+
<testcase classname="error.test.ts" name="unhandled" time="...">
66+
</testcase>
67+
<testcase classname="error.test.ts" name="no name object" time="...">
68+
<failure>
69+
{
70+
noName: &apos;hi&apos;,
71+
expected: &apos;undefined&apos;,
72+
actual: &apos;undefined&apos;,
73+
stacks: []
74+
}
75+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
76+
Serialized Error: { noName: &apos;hi&apos; }
77+
</failure>
78+
</testcase>
79+
<testcase classname="error.test.ts" name="string" time="...">
80+
<failure message="hi">
81+
Unknown Error: hi
82+
</failure>
83+
</testcase>
84+
<testcase classname="error.test.ts" name="number" time="...">
85+
<failure message="1234">
86+
Unknown Error: 1234
87+
</failure>
88+
</testcase>
89+
<testcase classname="error.test.ts" name="number name object" time="...">
90+
<failure type="1234">
91+
{
92+
name: 1234,
93+
nameStr: &apos;1234&apos;,
94+
expected: &apos;undefined&apos;,
95+
actual: &apos;undefined&apos;,
96+
stacks: []
97+
}
98+
</failure>
99+
</testcase>
100+
<testcase classname="error.test.ts" name="xml" time="...">
101+
<failure message="error message that has XML in it &lt;div&gt;&lt;input/&gt;&lt;/div&gt;" type="Error">
102+
Error: error message that has XML in it &lt;div&gt;&lt;input/&gt;&lt;/div&gt;
103+
❯ error.test.ts:36:9
104+
</failure>
105+
</testcase>
106+
<testcase classname="error.test.ts" name="error.test.ts" time="...">
107+
<failure message="throwSuite" type="Error">
108+
Error: throwSuite
109+
❯ throwSuite error.test.ts:48:9
110+
❯ error.test.ts:4:3
111+
</failure>
112+
</testcase>
113+
</testsuite>
114+
</testsuites>
115+
"
116+
`;

0 commit comments

Comments
 (0)