Skip to content

Commit

Permalink
fix(progress reporter): Simpify reported progress (#401)
Browse files Browse the repository at this point in the history
Simplify the progress reporter to now only show relevant information about the progress: `3/23 tested (1 survived)`

fixes #400
  • Loading branch information
nicojs authored and simondel committed Oct 1, 2017
1 parent 66f61fb commit 6258ef1
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 132 deletions.
15 changes: 5 additions & 10 deletions packages/stryker/src/reporters/ProgressAppendOnlyReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
36 changes: 5 additions & 31 deletions packages/stryker/src/reporters/ProgressKeeper.ts
Original file line number Diff line number Diff line change
@@ -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<MatchedMutant>): 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;
}
}
}
Expand Down
14 changes: 4 additions & 10 deletions packages/stryker/src/reporters/ProgressReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,21 @@ export default class ProgressBarReporter extends ProgressKeeper {
onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray<MatchedMutant>): 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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}`);
});
});
});
76 changes: 10 additions & 66 deletions packages/stryker/test/unit/reporters/ProgressReporterSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProgressBar>;
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);
});

Expand Down Expand Up @@ -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);
});
});
Expand Down

0 comments on commit 6258ef1

Please sign in to comment.