/** * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const RED_COLOR = '\x1b[31m'; const GREEN_COLOR = '\x1b[32m'; const YELLOW_COLOR = '\x1b[33m'; const RESET_COLOR = '\x1b[0m'; class Reporter { constructor(runner, testFolder = null) { this._runner = runner; this._testFolder = testFolder; runner.on('started', this._onStarted.bind(this)); runner.on('terminated', this._onTerminated.bind(this)); runner.on('finished', this._onFinished.bind(this)); runner.on('teststarted', this._onTestStarted.bind(this)); runner.on('testfinished', this._onTestFinished.bind(this)); this._workersState = new Map(); } _onStarted() { this._timestamp = Date.now(); console.log(`Running ${YELLOW_COLOR}${this._runner.parallel()}${RESET_COLOR} worker(s):\n`); } _onTerminated(message, error) { this._printTestResults(); console.log(`${RED_COLOR}## TERMINATED ##${RESET_COLOR}`); console.log('Message:'); console.log(` ${RED_COLOR}${message}${RESET_COLOR}`); if (error && error.stack) { console.log('Stack:'); console.log(error.stack.split('\n').map(line => ' ' + line).join('\n')); } console.log('WORKERS STATE'); const workerIds = Array.from(this._workersState.keys()); workerIds.sort((a, b) => a - b); for (const workerId of workerIds) { const {isRunning, test} = this._workersState.get(workerId); let description = ''; if (isRunning) description = `${YELLOW_COLOR}RUNNING${RESET_COLOR}`; else if (test.result === 'ok') description = `${GREEN_COLOR}OK${RESET_COLOR}`; else if (test.result === 'skipped') description = `${YELLOW_COLOR}SKIPPED${RESET_COLOR}`; else if (test.result === 'failed') description = `${RED_COLOR}FAILED${RESET_COLOR}`; else if (test.result === 'timedout') description = `${RED_COLOR}TIMEDOUT${RESET_COLOR}`; else description = `${RED_COLOR}${RESET_COLOR}`; console.log(` ${workerId}: [${description}] ${test.fullName} (${formatTestLocation(test)})`); } process.exit(2); } _onFinished() { this._printTestResults(); const failedTests = this._runner.failedTests(); process.exit(failedTests.length > 0 ? 1 : 0); } _printTestResults() { // 2 newlines after completing all tests. console.log('\n'); const failedTests = this._runner.failedTests(); if (failedTests.length > 0) { console.log('\nFailures:'); for (let i = 0; i < failedTests.length; ++i) { const test = failedTests[i]; console.log(`${i + 1}) ${test.fullName} (${formatTestLocation(test)})`); if (test.result === 'timedout') { console.log(' Message:'); console.log(` ${YELLOW_COLOR}Timeout Exceeded ${this._runner.timeout()}ms${RESET_COLOR}`); } else { console.log(' Message:'); console.log(` ${RED_COLOR}${test.error.message || test.error}${RESET_COLOR}`); console.log(' Stack:'); if (test.error.stack) console.log(this._beautifyStack(test.error.stack)); } if (test.output) { console.log(' Output:'); console.log(test.output.split('\n').map(line => ' ' + line).join('\n')); } console.log(''); } } const tests = this._runner.tests(); const skippedTests = tests.filter(test => test.result === 'skipped'); if (skippedTests.length > 0) { console.log('\nSkipped:'); for (let i = 0; i < skippedTests.length; ++i) { const test = skippedTests[i]; console.log(`${i + 1}) ${test.fullName}`); console.log(` ${YELLOW_COLOR}Temporary disabled with xit${RESET_COLOR} ${formatTestLocation(test)}\n`); } } const executedTests = tests.filter(test => test.result); console.log(`\nRan ${executedTests.length} of ${tests.length} test(s)`); const milliseconds = Date.now() - this._timestamp; const seconds = milliseconds / 1000; console.log(`Finished in ${YELLOW_COLOR}${seconds}${RESET_COLOR} seconds`); } _beautifyStack(stack) { if (!this._testFolder) return stack; const lines = stack.split('\n').map(line => ' ' + line); // Find last stack line that include testrunner code. let index = 0; while (index < lines.length && !lines[index].includes(__dirname)) ++index; while (index < lines.length && lines[index].includes(__dirname)) ++index; if (index >= lines.length) return stack; const line = lines[index]; const fromIndex = line.lastIndexOf(this._testFolder) + this._testFolder.length; const toIndex = line.lastIndexOf(')'); lines[index] = line.substring(0, fromIndex) + YELLOW_COLOR + line.substring(fromIndex, toIndex) + RESET_COLOR + line.substring(toIndex); return lines.join('\n'); } _onTestStarted(test, workerId) { this._workersState.set(workerId, {test, isRunning: true}); } _onTestFinished(test, workerId) { this._workersState.set(workerId, {test, isRunning: false}); if (test.result === 'ok') process.stdout.write(`${GREEN_COLOR}.${RESET_COLOR}`); else if (test.result === 'skipped') process.stdout.write(`${YELLOW_COLOR}*${RESET_COLOR}`); else if (test.result === 'failed') process.stdout.write(`${RED_COLOR}F${RESET_COLOR}`); else if (test.result === 'timedout') process.stdout.write(`${RED_COLOR}T${RESET_COLOR}`); } } function formatTestLocation(test) { const location = test.location; if (!location) return ''; return `${location.fileName}:${location.lineNumber}:${location.columnNumber}`; } module.exports = Reporter;