| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import mocha from 'mocha'; |
| import * as fs from 'fs'; |
| |
| |
| type SuiteChild = Suite | Test; |
| |
| interface Suite { |
| name: string; |
| children: SuiteChild[], |
| tests: number; |
| failures: number; |
| skipped: number; |
| } |
| |
| interface Test { |
| name: string; |
| speed?: string; |
| duration?: number; |
| status: 'pass' | 'fail' | 'pending'; |
| err?: any; // mocha's .d.ts doesn't define the weird structure of mocha's errors |
| } |
| |
| export default class TestReporter extends mocha.reporters.Base { |
| out: fs.WriteStream; |
| |
| constructor(runner: mocha.Runner, options: mocha.MochaOptions) { |
| super(runner, options); |
| |
| const outputName = options?.reporterOptions?.output; |
| this.out = fs.createWriteStream(outputName); |
| |
| const suiteName = options?.reporterOptions?.suiteName ?? 'Mocha Tests'; |
| |
| const suiteStack: Suite[] = [ |
| ]; |
| let currentSuite: Suite = { // root |
| name: suiteName, |
| children: [], |
| tests: 0, |
| failures: 0, |
| skipped: 0, |
| }; |
| |
| runner.on('suite', (suite) => { |
| if (suite.root) { return; } |
| suiteStack.push(currentSuite); |
| currentSuite = { |
| name: suite.title, |
| children: [], |
| tests: 0, |
| failures: 0, |
| skipped: 0, |
| }; |
| }); |
| |
| runner.on('suite end', (suite: mocha.Suite) => { |
| if (suiteStack.length > 0) { |
| const parentSuite = suiteStack.pop() as Suite; |
| parentSuite.children.push(currentSuite); |
| parentSuite.tests += currentSuite.tests; |
| parentSuite.failures += currentSuite.failures; |
| parentSuite.skipped += currentSuite.skipped; |
| currentSuite = parentSuite; |
| } |
| }); |
| |
| runner.on('pending', (test: mocha.Test) => { |
| currentSuite.children.push({ |
| name: test.title, |
| status: 'pending', |
| }); |
| currentSuite.tests++; |
| currentSuite.skipped++; |
| }); |
| |
| runner.on('pass', (test: mocha.Test) => { |
| currentSuite.children.push({ |
| name: test.title, |
| speed: test.speed, |
| duration: test.duration, |
| status: 'pass', |
| }); |
| currentSuite.tests++; |
| }); |
| |
| runner.on('fail', (test: mocha.Test) => { |
| currentSuite.children.push({ |
| name: test.title, |
| speed: test.speed, |
| duration: test.duration, |
| status: 'fail', |
| err: test.err, |
| }); |
| currentSuite.tests++; |
| currentSuite.failures++; |
| }); |
| |
| runner.on('end', () => { |
| this.out.write('<testsuites>\n'); |
| this.writeSuite(currentSuite); |
| this.out.write('</testsuites>\n'); |
| this.out.end(); |
| }); |
| } |
| |
| writeSuite(suite: Suite) { |
| this.out.write(`<testsuite name="${suite.name.replaceAll('"', '"')}" failures="${suite.failures}" tests="${suite.tests}">\n`); |
| for (const child of suite.children) { |
| if ('status' in child) { |
| this.writeTest(child as Test); |
| } else { |
| this.writeSuite(child as Suite); |
| } |
| } |
| this.out.write('</testsuite>\n'); |
| } |
| |
| writeTest(test: Test) { |
| const status = test.status === 'pending' ? 'notrun' : 'run'; |
| let result = 'completed'; |
| if (test.status === 'pending') { |
| result = 'skipped'; |
| } |
| this.out.write(`<testcase name="${test.name.replaceAll('"', '"')}" time="${test.duration /* ms */}" status="${status}" result="${result}">\n`); |
| if (test.status === 'fail') { |
| let msg = test.err!.message; |
| if (test.err!.generatedMessage) { |
| // we've got an automatically generated message with a diff, make a |
| // shorter one -- we'll get the diff in expected vs actual |
| msg = `Expected ${test.err!.operator}`; |
| } |
| this.out.write(`<failure message="${msg}"><![CDATA[`); |
| this.out.write(`${test.err!.stack}\n`); |
| this.out.write(']]>'); |
| if (test.err!.expected) { |
| this.out.write(`<expected><value>${test.err!.expected}</value></expected>\n`); |
| } |
| if (test.err!.actual) { |
| this.out.write(`<actual><value>${test.err!.expected}</value></actual>\n`); |
| } |
| this.out.write('</failure>\n'); |
| } |
| this.out.write('</testcase>\n'); |
| } |
| } |
| |