UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

165 lines 7.05 kB
import os from 'os'; import chalk from 'chalk'; import { commonTokens } from '@stryker-mutator/api/plugin'; import { TestStatus } from 'mutation-testing-metrics'; import { tokens } from 'typed-inject'; import { getEmojiForStatus, plural } from '../utils/string-utils.js'; import { ClearTextScoreTable } from './clear-text-score-table.js'; export class ClearTextReporter { log; options; static inject = tokens(commonTokens.logger, commonTokens.options); constructor(log, options) { this.log = log; this.options = options; this.configConsoleColor(); } out = process.stdout; writeLine = (output) => { this.out.write(`${output ?? ''}${os.EOL}`); }; writeDebugLine = (input) => { this.log.debug(input); }; configConsoleColor() { if (!this.options.allowConsoleColors) { chalk.level = 0; // All colors disabled } } onMutationTestReportReady(_report, metrics) { this.writeLine(); if (this.options.clearTextReporter.reportTests) { this.reportTests(metrics); } if (this.options.clearTextReporter.reportMutants) { this.reportMutants(metrics); } if (this.options.clearTextReporter.reportScoreTable) { this.writeLine(new ClearTextScoreTable(metrics.systemUnderTestMetrics, this.options).draw()); } } reportTests(metrics) { function indent(depth) { return new Array(depth).fill(' ').join(''); } const formatTestLine = (test, state) => { return `${this.color('grey', `${test.name}${test.location ? ` [line ${test.location.start.line}]` : ''}`)} (${state})`; }; if (metrics.testMetrics) { const reportTests = (currentResult, depth = 0) => { const nameParts = [currentResult.name]; while (!currentResult.file && currentResult.childResults.length === 1) { [currentResult] = currentResult.childResults; nameParts.push(currentResult.name); } this.writeLine(`${indent(depth)}${nameParts.join('/')}`); currentResult.file?.tests.forEach((test) => { switch (test.status) { case TestStatus.Killing: this.writeLine(`${indent(depth + 1)}${this.color('greenBright', '✓')} ${formatTestLine(test, `killed ${test.killedMutants?.length}`)}`); break; case TestStatus.Covering: this.writeLine(`${indent(depth + 1)}${this.color('blueBright', '~')} ${formatTestLine(test, `covered ${test.coveredMutants?.length}`)}`); break; case TestStatus.NotCovering: this.writeLine(`${indent(depth + 1)}${this.color('redBright', '✘')} ${formatTestLine(test, 'covered 0')}`); break; } }); currentResult.childResults.forEach((childResult) => reportTests(childResult, depth + 1)); }; reportTests(metrics.testMetrics); } } reportMutants({ systemUnderTestMetrics }) { this.writeLine(); let totalTests = 0; const reportMutants = (metrics) => { metrics.forEach((child) => { child.file?.mutants.forEach((result) => { totalTests += result.testsCompleted ?? 0; switch (result.status) { case 'Killed': case 'Timeout': case 'RuntimeError': case 'CompileError': this.reportMutantResult(result, this.writeDebugLine); break; case 'Survived': case 'NoCoverage': this.reportMutantResult(result, this.writeLine); break; default: } }); reportMutants(child.childResults); }); }; reportMutants(systemUnderTestMetrics.childResults); this.writeLine(`Ran ${(totalTests / systemUnderTestMetrics.metrics.totalMutants).toFixed(2)} tests per mutant on average.`); } statusLabel(mutant) { const { status } = mutant; return this.options.clearTextReporter.allowEmojis ? `${getEmojiForStatus(status)} ${status}` : status.toString(); } reportMutantResult(result, logImplementation) { logImplementation(`[${this.statusLabel(result)}] ${result.mutatorName}`); logImplementation(this.colorSourceFileAndLocation(result.fileName, result.location.start)); result .getOriginalLines() .split('\n') .filter(Boolean) .forEach((line) => { logImplementation(chalk.red('- ' + line)); }); result .getMutatedLines() .split('\n') .filter(Boolean) .forEach((line) => { logImplementation(chalk.green('+ ' + line)); }); if (result.status === 'Survived') { if (result.static) { logImplementation('Ran all tests for this mutant.'); } else if (result.coveredByTests) { this.logExecutedTests(result.coveredByTests, logImplementation); } } else if (result.status === 'Killed' && result.killedByTests?.length) { logImplementation(`Killed by: ${result.killedByTests[0].name}`); } else if (result.status === 'RuntimeError' || result.status === 'CompileError') { logImplementation(`Error message: ${result.statusReason}`); } logImplementation(''); } colorSourceFileAndLocation(fileName, position) { return [this.color('cyan', fileName), this.color('yellow', position.line), this.color('yellow', position.column)].join(':'); } color(color, ...text) { if (this.options.clearTextReporter.allowColor) { return chalk[color](...text); } return text.join(''); } logExecutedTests(tests, logImplementation) { if (!this.options.clearTextReporter.logTests) { return; } const testCount = Math.min(this.options.clearTextReporter.maxTestsToLog, tests.length); if (testCount > 0) { logImplementation('Tests ran:'); tests.slice(0, testCount).forEach((test) => { logImplementation(` ${test.name}`); }); const diff = tests.length - this.options.clearTextReporter.maxTestsToLog; if (diff > 0) { logImplementation(` and ${diff} more test${plural(diff)}!`); } logImplementation(''); } } } //# sourceMappingURL=clear-text-reporter.js.map