diff --git a/packages/vitest/src/node/reporters/junit.ts b/packages/vitest/src/node/reporters/junit.ts index 47090179d245..116e2313b1a4 100644 --- a/packages/vitest/src/node/reporters/junit.ts +++ b/packages/vitest/src/node/reporters/junit.ts @@ -112,7 +112,10 @@ export class JUnitReporter implements Reporter { async writeErrorDetails(error: ErrorWithDiff): Promise { const errorName = error.name ?? error.nameStr ?? 'Unknown Error' - await this.baseLog(`${errorName}: ${error.message}`) + const errorDetails = `${errorName}: ${error.message}` + + // Be sure to escape any XML in the error Details + await this.baseLog(escapeXML(errorDetails)) const stack = parseStacktrace(error) diff --git a/test/reporters/src/data-for-junit.ts b/test/reporters/src/data-for-junit.ts new file mode 100644 index 000000000000..bafcd3394287 --- /dev/null +++ b/test/reporters/src/data-for-junit.ts @@ -0,0 +1,58 @@ +import { AssertionError } from 'assert' +import type { File, Suite, Task } from 'vitest' + +function createSuiteHavingFailedTestWithXmlInError(): File[] { + const file: File = { + id: '1223128da3', + name: 'test/core/test/basic.test.ts', + type: 'suite', + mode: 'run', + filepath: '/vitest/test/core/test/basic.test.ts', + result: { state: 'fail', duration: 145.99284195899963 }, + tasks: [], + } + + const suite: Suite = { + id: '', + type: 'suite', + name: 'suite', + mode: 'run', + file, + result: { state: 'pass', duration: 1.90183687210083 }, + tasks: [], + } + + const errorWithXml = new AssertionError({ + message: 'error message that has XML in it ', + }) + + errorWithXml.stack = 'Error: error message that has XML in it \n' + + ' at /vitest/test/core/test/basic.test.ts:8:32\n' + + ' at etc....' + + const tasks: Task[] = [ + { + id: '123_0', + type: 'test', + name: 'test with xml in error', + mode: 'run', + suite, + fails: undefined, + file, + result: { + state: 'fail', + error: errorWithXml, + duration: 2.123123123, + }, + context: null as any, + }, + ] + + file.tasks = [suite] + suite.tasks = tasks + + return [file] +} + +export { createSuiteHavingFailedTestWithXmlInError } + diff --git a/test/reporters/src/data.ts b/test/reporters/src/data.ts index 0f6c1188b2b6..b22b6a92be2f 100644 --- a/test/reporters/src/data.ts +++ b/test/reporters/src/data.ts @@ -174,4 +174,7 @@ const tasks: Task[] = [ file.tasks = [suite] suite.tasks = tasks -export const files = [file] +const files = [file] + +export { files } + diff --git a/test/reporters/tests/__snapshots__/reporters.spec.ts.snap b/test/reporters/tests/__snapshots__/reporters.spec.ts.snap index bd60ace995a4..ae1db42bf395 100644 --- a/test/reporters/tests/__snapshots__/reporters.spec.ts.snap +++ b/test/reporters/tests/__snapshots__/reporters.spec.ts.snap @@ -236,6 +236,26 @@ AssertionError: expected 2.23606797749979 to equal 2 " `; +exports[`JUnit reporter with outputFile with XML in error message 1`] = ` +"JUNIT report written to /report_escape_msg_xml.xml +" +`; + +exports[`JUnit reporter with outputFile with XML in error message 2`] = ` +" + + + + +AssertionError: error message that has XML in it <tag> + ❯ vitest/test/core/test/basic.test.ts:8:32 + + + + +" +`; + exports[`json reporter (no outputFile entry) 1`] = ` { "numFailedTestSuites": 0, diff --git a/test/reporters/tests/reporters.spec.ts b/test/reporters/tests/reporters.spec.ts index 1ac95909b23e..d0061a9439a5 100644 --- a/test/reporters/tests/reporters.spec.ts +++ b/test/reporters/tests/reporters.spec.ts @@ -7,6 +7,7 @@ import { TapReporter } from '../../../packages/vitest/src/node/reporters/tap' import { TapFlatReporter } from '../../../packages/vitest/src/node/reporters/tap-flat' import { getContext } from '../src/context' import { files } from '../src/data' +import { createSuiteHavingFailedTestWithXmlInError } from '../src/data-for-junit' afterEach(() => { vi.useRealTimers() @@ -103,6 +104,35 @@ test('JUnit reporter with outputFile', async () => { rmSync(outputFile) }) +test('JUnit reporter with outputFile with XML in error message', async () => { + // Arrange + const reporter = new JUnitReporter() + const outputFile = resolve('report_escape_msg_xml.xml') + const context = getContext() + context.vitest.config.outputFile = outputFile + + vi.mock('os', () => ({ + hostname: () => 'hostname', + })) + + vi.setSystemTime(1642587001759) + + // setup suite with failed test with xml + const filesWithTestHavingXmlInError = createSuiteHavingFailedTestWithXmlInError() + + // Act + await reporter.onInit(context.vitest) + await reporter.onFinished(filesWithTestHavingXmlInError) + + // Assert + expect(normalizeCwd(context.output)).toMatchSnapshot() + expect(existsSync(outputFile)).toBe(true) + expect(readFileSync(outputFile, 'utf8')).toMatchSnapshot() + + // Cleanup + rmSync(outputFile) +}) + test('JUnit reporter with outputFile object', async () => { // Arrange const reporter = new JUnitReporter()