From 6258ef1009368af8052550f62f4784cdab2f1770 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Sun, 1 Oct 2017 20:35:07 +0200 Subject: [PATCH] fix(progress reporter): Simpify reported progress (#401) Simplify the progress reporter to now only show relevant information about the progress: `3/23 tested (1 survived)` fixes #400 --- .../reporters/ProgressAppendOnlyReporter.ts | 15 ++-- .../stryker/src/reporters/ProgressKeeper.ts | 36 ++------- .../stryker/src/reporters/ProgressReporter.ts | 14 +--- .../ProgressAppendOnlyReporterSpec.ts | 18 +---- .../unit/reporters/ProgressReporterSpec.ts | 76 +++---------------- 5 files changed, 27 insertions(+), 132 deletions(-) diff --git a/packages/stryker/src/reporters/ProgressAppendOnlyReporter.ts b/packages/stryker/src/reporters/ProgressAppendOnlyReporter.ts index e86e34832d..24f0897771 100644 --- a/packages/stryker/src/reporters/ProgressAppendOnlyReporter.ts +++ b/packages/stryker/src/reporters/ProgressAppendOnlyReporter.ts @@ -18,22 +18,17 @@ export default class ProgressAppendOnlyReporter extends ProgressKeeper { } private render() { - process.stdout.write(`Mutation testing ${this.procent()} (ETC ${this.etc()}) ` + - `[${this.progress.killed} ${this.progress.killedLabel}] ` + - `[${this.progress.survived} ${this.progress.survivedLabel}] ` + - `[${this.progress.noCoverage} ${this.progress.noCoverageLabel}] ` + - `[${this.progress.timeout} ${this.progress.timeoutLabel}] ` + - `[${this.progress.runtimeError} ${this.progress.runtimeErrorLabel}] ` + - `[${this.progress.transpileError} ${this.progress.transpileErrorLabel}]` + + process.stdout.write(`Mutation testing ${this.percent()} (ETC ${this.etc()}) ` + + `${this.progress.tested}/${this.progress.total} tested (${this.progress.survived} survived)` + os.EOL); } - private procent() { - return Math.floor(this.progress.testedCount / this.progress.totalCount * 100) + '%'; + private percent() { + return Math.floor(this.progress.tested / this.progress.total * 100) + '%'; } private etc() { - const etcSeconds = Math.floor(this.timer.elapsedSeconds() / this.progress.testedCount * (this.progress.totalCount - this.progress.testedCount)); + const etcSeconds = Math.floor(this.timer.elapsedSeconds() / this.progress.tested * (this.progress.total - this.progress.tested)); if (isFinite(etcSeconds)) { return etcSeconds + 's'; } else { diff --git a/packages/stryker/src/reporters/ProgressKeeper.ts b/packages/stryker/src/reporters/ProgressKeeper.ts index c33f054498..1ad2e8b7be 100644 --- a/packages/stryker/src/reporters/ProgressKeeper.ts +++ b/packages/stryker/src/reporters/ProgressKeeper.ts @@ -1,52 +1,26 @@ -import * as chalk from 'chalk'; import { MatchedMutant, Reporter, MutantResult, MutantStatus } from 'stryker-api/report'; abstract class ProgressKeeper implements Reporter { - // progress contains Labels, because on initiation of the ProgressBar the width is determined based on the amount of characters of the progressBarContent inclusive ASCII-codes for colors protected progress = { - runtimeError: 0, - transpileError: 0, survived: 0, - killed: 0, - timeout: 0, - noCoverage: 0, - testedCount: 0, - totalCount: 0, - killedLabel: chalk.green.bold('killed'), - survivedLabel: chalk.red.bold('survived'), - noCoverageLabel: chalk.red.bold('no coverage'), - timeoutLabel: chalk.yellow.bold('timeout'), - runtimeErrorLabel: chalk.yellow.bold('runtime error'), - transpileErrorLabel: chalk.yellow.bold('transpile error') + tested: 0, + total: 0 }; onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { - this.progress.totalCount = matchedMutants.filter(m => m.scopedTestIds.length > 0).length; + this.progress.total = matchedMutants.filter(m => m.scopedTestIds.length > 0).length; } onMutantTested(result: MutantResult): void { - this.progress.testedCount++; + this.progress.tested++; switch (result.status) { case MutantStatus.NoCoverage: - this.progress.testedCount--; // correct for not tested, because no coverage - this.progress.noCoverage++; - break; - case MutantStatus.Killed: - this.progress.killed++; + this.progress.tested--; // correct for not tested, because no coverage break; case MutantStatus.Survived: this.progress.survived++; break; - case MutantStatus.TimedOut: - this.progress.timeout++; - break; - case MutantStatus.RuntimeError: - this.progress.runtimeError++; - break; - case MutantStatus.TranspileError: - this.progress.transpileError++; - break; } } } diff --git a/packages/stryker/src/reporters/ProgressReporter.ts b/packages/stryker/src/reporters/ProgressReporter.ts index 9217002cca..e88c3d9bb6 100644 --- a/packages/stryker/src/reporters/ProgressReporter.ts +++ b/packages/stryker/src/reporters/ProgressReporter.ts @@ -8,27 +8,21 @@ export default class ProgressBarReporter extends ProgressKeeper { onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { super.onAllMutantsMatchedWithTests(matchedMutants); const progressBarContent = - `Mutation testing [:bar] :percent (ETC :etas)` + - `[:killed :killedLabel] ` + - `[:survived :survivedLabel] ` + - `[:noCoverage :noCoverageLabel] ` + - `[:timeout :timeoutLabel] ` + - `[:runtimeError :runtimeErrorLabel] ` + - `[:transpileError :transpileErrorLabel]`; + `Mutation testing [:bar] :percent (ETC :etas) :tested/:total tested (:survived survived)`; this.progressBar = new ProgressBar(progressBarContent, { width: 50, complete: '=', incomplete: ' ', stream: process.stdout, - total: this.progress.totalCount + total: this.progress.total }); } onMutantTested(result: MutantResult): void { - const ticksBefore = this.progress.testedCount; + const ticksBefore = this.progress.tested; super.onMutantTested(result); - if (ticksBefore < this.progress.testedCount) { + if (ticksBefore < this.progress.tested) { this.tick(); } else { this.render(); diff --git a/packages/stryker/test/unit/reporters/ProgressAppendOnlyReporterSpec.ts b/packages/stryker/test/unit/reporters/ProgressAppendOnlyReporterSpec.ts index 97c3af29ed..86bf347f4d 100644 --- a/packages/stryker/test/unit/reporters/ProgressAppendOnlyReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/ProgressAppendOnlyReporterSpec.ts @@ -1,6 +1,5 @@ import * as os from 'os'; import * as sinon from 'sinon'; -import * as chalk from 'chalk'; import { expect } from 'chai'; import { MutantStatus } from 'stryker-api/report'; import ProgressAppendOnlyReporter from '../../../src/reporters/ProgressAppendOnlyReporter'; @@ -29,28 +28,17 @@ describe('ProgressAppendOnlyReporter', () => { expect(process.stdout.write).to.not.have.been.called; }); - it('should log zero first progress after 10s without completed tests', () => { + it('should log zero progress after 10s without completed tests', () => { sandbox.clock.tick(10000); expect(process.stdout.write).to.have.been.calledWith(`Mutation testing 0% (ETC n/a) ` + - `[0 ${chalk.green.bold('killed')}] ` + - `[0 ${chalk.red.bold('survived')}] ` + - `[0 ${chalk.red.bold('no coverage')}] ` + - `[0 ${chalk.yellow.bold('timeout')}] ` + - `[0 ${chalk.yellow.bold('runtime error')}] ` + - `[0 ${chalk.yellow.bold('transpile error')}]${os.EOL}`); + `0/2 tested (0 survived)${os.EOL}`); }); it('should should log 50% with 10s ETC after 10s with 1 completed test', () => { sut.onMutantTested(mutantResult({ status: MutantStatus.Killed })); expect(process.stdout.write).to.not.have.been.called; sandbox.clock.tick(10000); - expect(process.stdout.write).to.have.been.calledWith(`Mutation testing 50% (ETC 10s) ` + - `[1 ${chalk.green.bold('killed')}] ` + - `[0 ${chalk.red.bold('survived')}] ` + - `[0 ${chalk.red.bold('no coverage')}] ` + - `[0 ${chalk.yellow.bold('timeout')}] ` + - `[0 ${chalk.yellow.bold('runtime error')}] ` + - `[0 ${chalk.yellow.bold('transpile error')}]${os.EOL}`); + expect(process.stdout.write).to.have.been.calledWith(`Mutation testing 50% (ETC 10s) 1/2 tested (0 survived)${os.EOL}`); }); }); }); diff --git a/packages/stryker/test/unit/reporters/ProgressReporterSpec.ts b/packages/stryker/test/unit/reporters/ProgressReporterSpec.ts index f88049d0bc..6fa8446259 100644 --- a/packages/stryker/test/unit/reporters/ProgressReporterSpec.ts +++ b/packages/stryker/test/unit/reporters/ProgressReporterSpec.ts @@ -3,27 +3,22 @@ import { expect } from 'chai'; import { MutantStatus, MatchedMutant } from 'stryker-api/report'; import ProgressReporter from '../../../src/reporters/ProgressReporter'; import * as progressBarModule from '../../../src/reporters/ProgressBar'; -import { matchedMutant, mutantResult } from '../../helpers/producers'; +import { matchedMutant, mutantResult, Mock, mock } from '../../helpers/producers'; +import ProgressBar = require('progress'); describe('ProgressReporter', () => { let sut: ProgressReporter; let sandbox: sinon.SinonSandbox; let matchedMutants: MatchedMutant[]; - let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; + let progressBar: Mock; const progressBarContent: string = - `Mutation testing [:bar] :percent (ETC :etas)` + - `[:killed :killedLabel] ` + - `[:survived :survivedLabel] ` + - `[:noCoverage :noCoverageLabel] ` + - `[:timeout :timeoutLabel] ` + - `[:runtimeError :runtimeErrorLabel] ` + - `[:transpileError :transpileErrorLabel]`; + `Mutation testing [:bar] :percent (ETC :etas) :tested/:total tested (:survived survived)`; beforeEach(() => { sut = new ProgressReporter(); sandbox = sinon.sandbox.create(); - progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; + progressBar = mock(ProgressBar); sandbox.stub(progressBarModule, 'default').returns(progressBar); }); @@ -65,77 +60,26 @@ describe('ProgressReporter', () => { sut.onAllMutantsMatchedWithTests(matchedMutants); }); - describe('when status is KILLED', () => { + describe('when status is not "Survived"', () => { beforeEach(() => { sut.onMutantTested(mutantResult({ status: MutantStatus.Killed })); }); - it('should tick the ProgressBar with 1 killed mutant', () => { - progressBarTickTokens = { runtimeError: 0, transpileError: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; + it('should tick the ProgressBar with 1 tested mutant, 0 survived', () => { + progressBarTickTokens = { total: 3, tested: 1, survived: 0 }; expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); }); }); - describe('when status is TimedOut', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult({ status: MutantStatus.TimedOut })); - }); - - it('should tick the ProgressBar with 1 timed out mutant', () => { - progressBarTickTokens = { runtimeError: 0, transpileError: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is Survived', () => { + describe('when status is "Survived"', () => { beforeEach(() => { sut.onMutantTested(mutantResult({ status: MutantStatus.Survived })); }); it('should tick the ProgressBar with 1 survived mutant', () => { - progressBarTickTokens = { runtimeError: 0, transpileError: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is RuntimeError', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult({ status: MutantStatus.RuntimeError })); - }); - - it('should tick the ProgressBar with 1 RuntimeError mutant', () => { - progressBarTickTokens = { runtimeError: 1, transpileError: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is NoCoverage', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult({ status: MutantStatus.NoCoverage })); - }); - - it('should not tick the ProgressBar', () => { - expect(progressBar.tick).to.not.have.been.called; - }); - - it('should render the ProgressBar', () => { - progressBarTickTokens = { runtimeError: 0, transpileError: 0, killed: 0, noCoverage: 1, survived: 0, timeout: 0 }; - expect(progressBar.render).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - describe('when status is TranspileError', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult({ status: MutantStatus.TranspileError })); - }); - - it('should tick the ProgressBar with 1 TranspileError mutant', () => { - progressBarTickTokens = { runtimeError: 0, transpileError: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; + progressBarTickTokens = { total: 3, tested: 1, survived: 1 }; expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); }); });