From 4a7833521c457c50896cc0c0de3dad7db65cde87 Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Wed, 28 Dec 2016 17:15:20 +0100 Subject: [PATCH 1/8] Feat(progress-reporter) - Rename current progress-reporter to dots-reporter - Add new fancy progress-reporter --- .vscode/launch.json | 2 +- package.json | 9 ++- src/MutantTestMatcher.ts | 22 +++++- src/ReporterOrchestrator.ts | 2 + src/Stryker.ts | 2 +- src/reporters/BroadcastReporter.ts | 2 +- src/reporters/DotsReporter.ts | 31 ++++++++ src/reporters/ProgressReporter.ts | 80 ++++++++++++++------- test/unit/MutantTestMatcherSpec.ts | 51 +++++++++++-- test/unit/reporters/DotsReporterSpec.ts | 80 +++++++++++++++++++++ test/unit/reporters/ProgressReporterSpec.ts | 80 --------------------- 11 files changed, 245 insertions(+), 116 deletions(-) create mode 100644 src/reporters/DotsReporter.ts create mode 100644 test/unit/reporters/DotsReporterSpec.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index a98f27e383..abf6f5188c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -78,7 +78,7 @@ // "preLaunchTask": "build", "stopOnEntry": false, "args": [ - "--configFile", + "run", "stryker.conf.js" ], "cwd": "${workspaceRoot}", diff --git a/package.json b/package.json index 389d966bbb..2335f0a51d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "Nico Stapelbroek ", "Jeremy Nagel ", "Michael Williamson ", - "Philipp Weissenbacher " + "Philipp Weissenbacher ", + "Alex van Assem { @@ -28,6 +30,24 @@ export default class MutantTestMatcher { } }); }); + + this.reporter.onAllMutantsMatchedWithTests(Object.freeze(this.mutants.map(this.mapMutantOnMatchedMutant))); + } + + /** + * Map the Mutant object on the MatchMutant Object. + * @param mutant The mutant. + * @returns The MatchedMutant + */ + private mapMutantOnMatchedMutant(mutant: Mutant): MatchedMutant { + const matchedMutant = _.cloneDeep({ + mutatorName: mutant.mutatorName, + scopedTestIds: mutant.scopedTestIds, + timeSpentScopedTests: mutant.timeSpentScopedTests, + filename: mutant.filename, + replacement: mutant.replacement + }); + return Object.freeze(matchedMutant); } /** diff --git a/src/ReporterOrchestrator.ts b/src/ReporterOrchestrator.ts index e75caa283e..6966f47422 100644 --- a/src/ReporterOrchestrator.ts +++ b/src/ReporterOrchestrator.ts @@ -2,6 +2,7 @@ import {StrykerOptions} from 'stryker-api/core'; import {Reporter, ReporterFactory} from 'stryker-api/report'; import ClearTextReporter from './reporters/ClearTextReporter'; import ProgressReporter from './reporters/ProgressReporter'; +import DotsReporter from './reporters/DotsReporter'; import EventRecorderReporter from './reporters/EventRecorderReporter'; import BroadcastReporter, {NamedReporter} from './reporters/BroadcastReporter'; import * as log4js from 'log4js'; @@ -44,6 +45,7 @@ export default class ReporterOrchestrator { private registerDefaultReporters() { ReporterFactory.instance().register('progress', ProgressReporter); + ReporterFactory.instance().register('dots', DotsReporter); ReporterFactory.instance().register('clear-text', ClearTextReporter); ReporterFactory.instance().register('event-recorder', EventRecorderReporter); } diff --git a/src/Stryker.ts b/src/Stryker.ts index f3c80ecdb3..3be1df0575 100644 --- a/src/Stryker.ts +++ b/src/Stryker.ts @@ -138,7 +138,7 @@ export default class Stryker { .filter(inputFile => inputFile.mutated) .map(file => file.path)); log.info(`${mutants.length} Mutant(s) generated`); - let mutantRunResultMatcher = new MutantTestMatcher(mutants, runResult, this.coverageInstrumenter.retrieveStatementMapsPerFile(), this.config); + let mutantRunResultMatcher = new MutantTestMatcher(mutants, runResult, this.coverageInstrumenter.retrieveStatementMapsPerFile(), this.config, this.reporter); mutantRunResultMatcher.matchWithMutants(); return mutants; } diff --git a/src/reporters/BroadcastReporter.ts b/src/reporters/BroadcastReporter.ts index edee4f21e4..8ad563a680 100644 --- a/src/reporters/BroadcastReporter.ts +++ b/src/reporters/BroadcastReporter.ts @@ -11,7 +11,7 @@ export interface NamedReporter { reporter: Reporter; } -export const ALL_EVENT_METHOD_NAMES = ['onSourceFileRead', 'onAllSourceFilesRead', 'onMutantTested', 'onAllMutantsTested', 'onConfigRead']; +export const ALL_EVENT_METHOD_NAMES = ['onSourceFileRead', 'onAllSourceFilesRead', 'onAllMutantsMatchedWithTests', 'onMutantTested', 'onAllMutantsTested', 'onConfigRead']; export default class BroadcastReporter implements Reporter { diff --git a/src/reporters/DotsReporter.ts b/src/reporters/DotsReporter.ts new file mode 100644 index 0000000000..1c29c201b3 --- /dev/null +++ b/src/reporters/DotsReporter.ts @@ -0,0 +1,31 @@ +import {Reporter, MutantResult, MutantStatus} from 'stryker-api/report'; +import * as chalk from 'chalk'; +import * as os from 'os'; + +export default class DotsReporter implements Reporter { + onMutantTested(result: MutantResult) { + let toLog: string; + switch (result.status) { + case MutantStatus.Killed: + toLog = '.'; + break; + case MutantStatus.TimedOut: + toLog = chalk.yellow('T'); + break; + case MutantStatus.Survived: + toLog = chalk.bold.red('S'); + break; + case MutantStatus.Error: + toLog = chalk.yellow('E'); + break; + default: + toLog = ''; + break; + } + process.stdout.write(toLog); + } + + onAllMutantsTested(): void { + process.stdout.write(os.EOL); + } +} \ No newline at end of file diff --git a/src/reporters/ProgressReporter.ts b/src/reporters/ProgressReporter.ts index ca2b6b912a..ff258751a1 100644 --- a/src/reporters/ProgressReporter.ts +++ b/src/reporters/ProgressReporter.ts @@ -1,31 +1,61 @@ -import {Reporter, MutantResult, MutantStatus} from 'stryker-api/report'; +import { Reporter, MatchedMutant, MutantResult, MutantStatus } from 'stryker-api/report'; +import ProgressBar = require('progress'); import * as chalk from 'chalk'; -import * as os from 'os'; export default class ProgressReporter implements Reporter { - onMutantTested(result: MutantResult) { - let toLog: string; - switch (result.status) { - case MutantStatus.Killed: - toLog = '.'; - break; - case MutantStatus.TimedOut: - toLog = chalk.yellow('T'); - break; - case MutantStatus.Survived: - toLog = chalk.bold.red('S'); - break; - case MutantStatus.Error: - toLog = chalk.yellow('E'); - break; - default: - toLog = ''; - break; + private progressBar: ProgressBar; + private tickValues = { + error: 0, + survived: 0, + killed: 0, + timeout: 0, + noCoverage: 0 + }; + + onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { + let progressBarString: string; + + progressBarString = + `Mutation testing [:bar] :percent (ETA :etas)` + + `[:killed ${chalk.green.bold('killed')}]` + + `[:survived ${chalk.red.bold('survived')}]` + + `[:noCoverage ${chalk.red.bold('no coverage')}]` + + `[:timeout ${chalk.yellow.bold('timeout')}]` + + `[:error ${chalk.yellow.bold('error')}]`; + + this.progressBar = new ProgressBar(progressBarString, { + complete: '=', + incomplete: ' ', + width: 50, + total: matchedMutants.filter(m => m.scopedTestIds.length).length + }); } - process.stdout.write(toLog); - } - onAllMutantsTested(): void { - process.stdout.write(os.EOL); - } + onMutantTested(result: MutantResult): void { + switch (result.status) { + case MutantStatus.NoCoverage: + this.tickValues.noCoverage++; + break; + case MutantStatus.Killed: + this.tickValues.killed++; + this.tick(); + break; + case MutantStatus.Survived: + this.tickValues.survived++; + this.tick(); + break; + case MutantStatus.TimedOut: + this.tickValues.timeout++; + this.tick(); + break; + case MutantStatus.Error: + this.tickValues.error++; + this.tick(); + break; + } + } + + tick(): void { + this.progressBar.tick(this.tickValues); + } } \ No newline at end of file diff --git a/test/unit/MutantTestMatcherSpec.ts b/test/unit/MutantTestMatcherSpec.ts index fe3cd00338..c08825a322 100644 --- a/test/unit/MutantTestMatcherSpec.ts +++ b/test/unit/MutantTestMatcherSpec.ts @@ -5,6 +5,7 @@ import { StrykerOptions } from 'stryker-api/core'; import { StatementMapDictionary } from '../../src/coverage/CoverageInstrumenter'; import MutantTestMatcher from '../../src/MutantTestMatcher'; import Mutant from '../../src/Mutant'; +import { Reporter, MatchedMutant } from 'stryker-api/report'; describe('MutantTestMatcher', () => { @@ -13,25 +14,48 @@ describe('MutantTestMatcher', () => { let runResult: RunResult; let statementMapDictionary: StatementMapDictionary; let strykerOptions: StrykerOptions; + let reporter: Reporter; beforeEach(() => { mutants = []; statementMapDictionary = Object.create(null); runResult = { tests: [], status: RunStatus.Complete }; strykerOptions = {}; - sut = new MutantTestMatcher(mutants, runResult, statementMapDictionary, strykerOptions); + reporter = { onAllMutantsMatchedWithTests: sinon.stub() }; + sut = new MutantTestMatcher(mutants, runResult, statementMapDictionary, strykerOptions, reporter); }); describe('with coverageAnalysis: "perTest"', () => { - beforeEach(() => strykerOptions.coverageAnalysis = 'perTest'); + beforeEach(() => { + strykerOptions.coverageAnalysis = 'perTest'; + }); describe('matchWithMutants()', () => { describe('with 2 mutants and 2 testResults', () => { let mutantOne: any, mutantTwo: any, testResultOne: TestResult, testResultTwo: TestResult; beforeEach(() => { - mutantOne = { mutantOne: true, filename: 'fileWithMutantOne', location: { start: { line: 5, column: 6 }, end: { line: 5, column: 6 } }, addTestResult: sinon.stub() }; - mutantTwo = { mutantTwo: true, filename: 'fileWithMutantTwo', location: { start: { line: 10, column: 0 }, end: { line: 10, column: 0 } }, addTestResult: sinon.stub() }; + mutantOne = { + mutatorName: 'myMutator', + mutantOne: true, + filename: 'fileWithMutantOne', + location: { start: { line: 5, column: 6 }, end: { line: 5, column: 6 } }, + replacement: '>', + addTestResult: sinon.stub(), + scopedTestIds: [1, 2], + timeSpentScopedTests: 1, + }; + mutantTwo = { + mutatorName: 'myMutator', + mutantTwo: true, + filename: 'fileWithMutantTwo', + location: { start: { line: 10, column: 0 }, end: { line: 10, column: 0 } }, + replacement: '<', + addTestResult: sinon.stub(), + scopedTestIds: [9, 10], + timeSpentScopedTests: 59, + }; + testResultOne = { status: TestStatus.Success, name: 'test one', @@ -60,6 +84,25 @@ describe('MutantTestMatcher', () => { expect(mutantTwo.addTestResult).to.have.been.calledWith(0, testResultOne); expect(mutantTwo.addTestResult).to.have.been.calledWith(1, testResultTwo); }); + it('should have both mutants matched', () => { + let matchedMutants: MatchedMutant[] = [ + { + mutatorName: mutants[0].mutatorName, + scopedTestIds: mutants[0].scopedTestIds, + timeSpentScopedTests: mutants[0].timeSpentScopedTests, + filename: mutants[0].filename, + replacement: mutants[0].replacement + }, + { + mutatorName: mutants[1].mutatorName, + scopedTestIds: mutants[1].scopedTestIds, + timeSpentScopedTests: mutants[1].timeSpentScopedTests, + filename: mutants[1].filename, + replacement: mutants[1].replacement + } + ]; + expect(reporter.onAllMutantsMatchedWithTests).to.have.been.calledWith(Object.freeze(matchedMutants)); + }); }); describe('without the tests having covered the mutants', () => { diff --git a/test/unit/reporters/DotsReporterSpec.ts b/test/unit/reporters/DotsReporterSpec.ts new file mode 100644 index 0000000000..ca91a50138 --- /dev/null +++ b/test/unit/reporters/DotsReporterSpec.ts @@ -0,0 +1,80 @@ +import DotsReporter from '../../../src/reporters/DotsReporter'; +import * as sinon from 'sinon'; +import {MutantStatus, MutantResult} from 'stryker-api/report'; +import {expect} from 'chai'; +import * as chalk from 'chalk'; +import * as os from 'os'; + +describe('DotsReporter', () => { + + let sut: DotsReporter; + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sut = new DotsReporter(); + sandbox = sinon.sandbox.create(); + sandbox.stub(process.stdout, 'write'); + }); + + describe('onMutantTested()', () => { + + describe('when status is KILLED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Killed)); + }); + + it('should log "."', () => { + expect(process.stdout.write).to.have.been.calledWith('.'); + }); + }); + + describe('when status is TIMEDOUT', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); + }); + + it('should log "T"', () => { + expect(process.stdout.write).to.have.been.calledWith(chalk.yellow('T')); + }); + }); + + describe('when status is SURVIVED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Survived)); + }); + + it('should log "S"', () => { + expect(process.stdout.write).to.have.been.calledWith(chalk.bold.red('S')); + }); + }); + }); + + describe('onAllMutantsTested()', () => { + it('should write a new line', () => { + sut.onAllMutantsTested(); + expect(process.stdout.write).to.have.been.calledWith(os.EOL); + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + function mutantResult(status: MutantStatus): MutantResult { + return { + location: null, + mutatedLines: null, + mutatorName: null, + originalLines: null, + replacement: null, + sourceFilePath: null, + testsRan: null, + status, + range: null + }; + } + +}); diff --git a/test/unit/reporters/ProgressReporterSpec.ts b/test/unit/reporters/ProgressReporterSpec.ts index 9b8b1c9e3c..e69de29bb2 100644 --- a/test/unit/reporters/ProgressReporterSpec.ts +++ b/test/unit/reporters/ProgressReporterSpec.ts @@ -1,80 +0,0 @@ -import ProgressReporter from '../../../src/reporters/ProgressReporter'; -import * as sinon from 'sinon'; -import {MutantStatus, MutantResult} from 'stryker-api/report'; -import {expect} from 'chai'; -import * as chalk from 'chalk'; -import * as os from 'os'; - -describe('ProgressReporter', () => { - - let sut: ProgressReporter; - let sandbox: sinon.SinonSandbox; - - beforeEach(() => { - sut = new ProgressReporter(); - sandbox = sinon.sandbox.create(); - sandbox.stub(process.stdout, 'write'); - }); - - describe('onMutantTested()', () => { - - describe('when status is KILLED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Killed)); - }); - - it('should log "."', () => { - expect(process.stdout.write).to.have.been.calledWith('.'); - }); - }); - - describe('when status is TIMEDOUT', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); - }); - - it('should log "T"', () => { - expect(process.stdout.write).to.have.been.calledWith(chalk.yellow('T')); - }); - }); - - describe('when status is SURVIVED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Survived)); - }); - - it('should log "S"', () => { - expect(process.stdout.write).to.have.been.calledWith(chalk.bold.red('S')); - }); - }); - }); - - describe('onAllMutantsTested()', () => { - it('should write a new line', () => { - sut.onAllMutantsTested(); - expect(process.stdout.write).to.have.been.calledWith(os.EOL); - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - function mutantResult(status: MutantStatus): MutantResult { - return { - location: null, - mutatedLines: null, - mutatorName: null, - originalLines: null, - replacement: null, - sourceFilePath: null, - testsRan: null, - status, - range: null - }; - } - -}); From b474b61e229435a57473f75a4d365ab3b7eaf6ab Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Thu, 29 Dec 2016 16:30:18 +0100 Subject: [PATCH 2/8] Feat(#195) Added unit tests --- src/reporters/ProgressBar.ts | 3 + src/reporters/ProgressReporter.ts | 22 +-- test/unit/reporters/ProgressReporterSpec.ts | 169 ++++++++++++++++++++ 3 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 src/reporters/ProgressBar.ts diff --git a/src/reporters/ProgressBar.ts b/src/reporters/ProgressBar.ts new file mode 100644 index 0000000000..2677ed5c2c --- /dev/null +++ b/src/reporters/ProgressBar.ts @@ -0,0 +1,3 @@ +import ProgressBar = require('progress'); + +export default ProgressBar; \ No newline at end of file diff --git a/src/reporters/ProgressReporter.ts b/src/reporters/ProgressReporter.ts index ff258751a1..f96f17f125 100644 --- a/src/reporters/ProgressReporter.ts +++ b/src/reporters/ProgressReporter.ts @@ -1,5 +1,5 @@ import { Reporter, MatchedMutant, MutantResult, MutantStatus } from 'stryker-api/report'; -import ProgressBar = require('progress'); +import ProgressBar from './ProgressBar'; import * as chalk from 'chalk'; export default class ProgressReporter implements Reporter { @@ -13,21 +13,20 @@ export default class ProgressReporter implements Reporter { }; onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { - let progressBarString: string; + let progressBarContent: string; - progressBarString = - `Mutation testing [:bar] :percent (ETA :etas)` + - `[:killed ${chalk.green.bold('killed')}]` + - `[:survived ${chalk.red.bold('survived')}]` + - `[:noCoverage ${chalk.red.bold('no coverage')}]` + - `[:timeout ${chalk.yellow.bold('timeout')}]` + + progressBarContent = + `Mutation testing [:bar] :percent (ETC :etas) ` + + `[:killed ${chalk.green.bold('killed')}] ` + + `[:survived ${chalk.red.bold('survived')}] ` + + `[:noCoverage ${chalk.red.bold('no coverage')}] ` + + `[:timeout ${chalk.yellow.bold('timeout')}] ` + `[:error ${chalk.yellow.bold('error')}]`; - this.progressBar = new ProgressBar(progressBarString, { + this.progressBar = new ProgressBar(progressBarContent, { complete: '=', incomplete: ' ', - width: 50, - total: matchedMutants.filter(m => m.scopedTestIds.length).length + total: matchedMutants.filter(m => m.scopedTestIds.length > 0).length }); } @@ -35,6 +34,7 @@ export default class ProgressReporter implements Reporter { switch (result.status) { case MutantStatus.NoCoverage: this.tickValues.noCoverage++; + this.progressBar.render(this.tickValues); break; case MutantStatus.Killed: this.tickValues.killed++; diff --git a/test/unit/reporters/ProgressReporterSpec.ts b/test/unit/reporters/ProgressReporterSpec.ts index e69de29bb2..14d282f088 100644 --- a/test/unit/reporters/ProgressReporterSpec.ts +++ b/test/unit/reporters/ProgressReporterSpec.ts @@ -0,0 +1,169 @@ +import ProgressReporter from '../../../src/reporters/ProgressReporter'; +import * as progressBarModule from '../../../src/reporters/ProgressBar'; +import { MutantStatus, MutantResult, MatchedMutant } from 'stryker-api/report'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as chalk from 'chalk'; + +describe('ProgressReporter', () => { + + let sut: ProgressReporter; + let sandbox: sinon.SinonSandbox; + let matchedMutants: MatchedMutant[]; + let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; + const progressBarContent: string = + `Mutation testing [:bar] :percent (ETC :etas) ` + + `[:killed ${chalk.green.bold('killed')}] ` + + `[:survived ${chalk.red.bold('survived')}] ` + + `[:noCoverage ${chalk.red.bold('no coverage')}] ` + + `[:timeout ${chalk.yellow.bold('timeout')}] ` + + `[:error ${chalk.yellow.bold('error')}]`; + let progressBarOptions: any = { complete: '=', incomplete: ' ', total: null }; + + beforeEach(() => { + sut = new ProgressReporter(); + sandbox = sinon.sandbox.create(); + progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; + sandbox.stub(progressBarModule, 'default').returns(progressBar); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('onAllMutantsMatchedWithTests()', () => { + describe('when there are 3 MatchedMutants that all contain Tests', () => { + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + it('the total of MatchedMutants in the progressbar should be 3', () => { + progressBarOptions.total = 3; + expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); + }); + }); + describe('when there are 2 MatchedMutants that all contain Tests and 1 MatchMutant that doesnt have tests', () => { + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(0), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + it('the total of MatchedMutants in the progressbar should be 2', () => { + progressBarOptions.total = 2; + expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); + }); + }); + }); + + describe('onMutantTested()', () => { + let progressBarTickTokens: any; + + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + describe('when status is KILLED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Killed)); + }); + + it('should report 1 killed mutant', () => { + progressBarTickTokens = { error: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is TIMEDOUT', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); + }); + + it('should report 1 timed out mutant', () => { + progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is SURVIVED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Survived)); + }); + + it('should report 1 survived mutant', () => { + progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is ERRORED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Error)); + }); + + it('should report 1 errored mutant', () => { + progressBarTickTokens = { error: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is NO COVERAGE', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.NoCoverage)); + }); + + it('should not report immediately', () => { + expect(progressBar.tick).to.not.have.been.called; + }); + + describe('and then the status is KILLED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Killed)); + }); + + it('should report 1 not covered mutant and 1 killed mutant', () => { + progressBarTickTokens = { error: 0, killed: 1, noCoverage: 1, survived: 0, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + }); + }); +}); + +function mutantResult(status: MutantStatus): MutantResult { + return { + location: null, + mutatedLines: null, + mutatorName: null, + originalLines: null, + replacement: null, + sourceFilePath: null, + testsRan: null, + status, + range: null + }; +} + +function matchedMutant(numberOfTests: number): MatchedMutant { + let scopedTestIds: number[] = []; + for (let i = 0; i < numberOfTests; i++) { + scopedTestIds.push(1); + } + return { + mutatorName: null, + scopedTestIds: scopedTestIds, + timeSpentScopedTests: null, + filename: null, + replacement: null + }; +} From b546f1ac0e117f5e08f98f56a6b034c95781b26d Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Thu, 29 Dec 2016 16:47:16 +0100 Subject: [PATCH 3/8] Replaced colors of progress bar --- src/reporters/ProgressReporter.ts | 106 +++--- test/unit/reporters/ProgressReporterSpec.ts | 338 ++++++++++---------- 2 files changed, 226 insertions(+), 218 deletions(-) diff --git a/src/reporters/ProgressReporter.ts b/src/reporters/ProgressReporter.ts index f96f17f125..421f09f956 100644 --- a/src/reporters/ProgressReporter.ts +++ b/src/reporters/ProgressReporter.ts @@ -1,61 +1,69 @@ import { Reporter, MatchedMutant, MutantResult, MutantStatus } from 'stryker-api/report'; import ProgressBar from './ProgressBar'; import * as chalk from 'chalk'; +import * as _ from 'lodash'; export default class ProgressReporter implements Reporter { - private progressBar: ProgressBar; - private tickValues = { - error: 0, - survived: 0, - killed: 0, - timeout: 0, - noCoverage: 0 - }; + private progressBar: ProgressBar; + private tickValues = { + error: 0, + survived: 0, + killed: 0, + timeout: 0, + noCoverage: 0 + }; - onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { - let progressBarContent: string; + onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { + let progressBarContent: string; - progressBarContent = - `Mutation testing [:bar] :percent (ETC :etas) ` + - `[:killed ${chalk.green.bold('killed')}] ` + - `[:survived ${chalk.red.bold('survived')}] ` + - `[:noCoverage ${chalk.red.bold('no coverage')}] ` + - `[:timeout ${chalk.yellow.bold('timeout')}] ` + - `[:error ${chalk.yellow.bold('error')}]`; + progressBarContent = + `Mutation testing [:bar] :percent (ETC :etas)` + + `[:killed] ` + + `[:survived] ` + + `[:noCoverage] ` + + `[:timeout] ` + + `[:error]`; - this.progressBar = new ProgressBar(progressBarContent, { - complete: '=', - incomplete: ' ', - total: matchedMutants.filter(m => m.scopedTestIds.length > 0).length - }); - } + this.progressBar = new ProgressBar(progressBarContent, { + width: 50, + complete: '=', + incomplete: ' ', + total: matchedMutants.filter(m => m.scopedTestIds.length > 0).length + }); + } - onMutantTested(result: MutantResult): void { - switch (result.status) { - case MutantStatus.NoCoverage: - this.tickValues.noCoverage++; - this.progressBar.render(this.tickValues); - break; - case MutantStatus.Killed: - this.tickValues.killed++; - this.tick(); - break; - case MutantStatus.Survived: - this.tickValues.survived++; - this.tick(); - break; - case MutantStatus.TimedOut: - this.tickValues.timeout++; - this.tick(); - break; - case MutantStatus.Error: - this.tickValues.error++; - this.tick(); - break; - } + onMutantTested(result: MutantResult): void { + switch (result.status) { + case MutantStatus.NoCoverage: + this.tickValues.noCoverage++; + this.progressBar.render(this.tickValues); + break; + case MutantStatus.Killed: + this.tickValues.killed++; + this.tick(); + break; + case MutantStatus.Survived: + this.tickValues.survived++; + this.tick(); + break; + case MutantStatus.TimedOut: + this.tickValues.timeout++; + this.tick(); + break; + case MutantStatus.Error: + this.tickValues.error++; + this.tick(); + break; } + } - tick(): void { - this.progressBar.tick(this.tickValues); - } + tick(): void { + const vals = _.clone(this.tickValues); + (vals as any).killed = `${vals.killed} ${chalk.green.bold('killed')}`; + (vals as any).survived = `${vals.survived} ${chalk.red.bold('survived')}`; + (vals as any).noCoverage = `${vals.noCoverage} ${chalk.red.bold('no coverage')}`; + (vals as any).timeout = `${vals.timeout} ${chalk.yellow.bold('timeout')}`; + (vals as any).error = `${vals.error} ${chalk.yellow.bold('error')}`; + this.progressBar.tick(vals); + } } \ No newline at end of file diff --git a/test/unit/reporters/ProgressReporterSpec.ts b/test/unit/reporters/ProgressReporterSpec.ts index 14d282f088..2719f4a95a 100644 --- a/test/unit/reporters/ProgressReporterSpec.ts +++ b/test/unit/reporters/ProgressReporterSpec.ts @@ -1,169 +1,169 @@ -import ProgressReporter from '../../../src/reporters/ProgressReporter'; -import * as progressBarModule from '../../../src/reporters/ProgressBar'; -import { MutantStatus, MutantResult, MatchedMutant } from 'stryker-api/report'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import * as chalk from 'chalk'; - -describe('ProgressReporter', () => { - - let sut: ProgressReporter; - let sandbox: sinon.SinonSandbox; - let matchedMutants: MatchedMutant[]; - let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; - const progressBarContent: string = - `Mutation testing [:bar] :percent (ETC :etas) ` + - `[:killed ${chalk.green.bold('killed')}] ` + - `[:survived ${chalk.red.bold('survived')}] ` + - `[:noCoverage ${chalk.red.bold('no coverage')}] ` + - `[:timeout ${chalk.yellow.bold('timeout')}] ` + - `[:error ${chalk.yellow.bold('error')}]`; - let progressBarOptions: any = { complete: '=', incomplete: ' ', total: null }; - - beforeEach(() => { - sut = new ProgressReporter(); - sandbox = sinon.sandbox.create(); - progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; - sandbox.stub(progressBarModule, 'default').returns(progressBar); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('onAllMutantsMatchedWithTests()', () => { - describe('when there are 3 MatchedMutants that all contain Tests', () => { - beforeEach(() => { - matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; - - sut.onAllMutantsMatchedWithTests(matchedMutants); - }); - - it('the total of MatchedMutants in the progressbar should be 3', () => { - progressBarOptions.total = 3; - expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); - }); - }); - describe('when there are 2 MatchedMutants that all contain Tests and 1 MatchMutant that doesnt have tests', () => { - beforeEach(() => { - matchedMutants = [matchedMutant(1), matchedMutant(0), matchedMutant(2)]; - - sut.onAllMutantsMatchedWithTests(matchedMutants); - }); - - it('the total of MatchedMutants in the progressbar should be 2', () => { - progressBarOptions.total = 2; - expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); - }); - }); - }); - - describe('onMutantTested()', () => { - let progressBarTickTokens: any; - - beforeEach(() => { - matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; - - sut.onAllMutantsMatchedWithTests(matchedMutants); - }); - - describe('when status is KILLED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Killed)); - }); - - it('should report 1 killed mutant', () => { - progressBarTickTokens = { error: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is TIMEDOUT', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); - }); - - it('should report 1 timed out mutant', () => { - progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is SURVIVED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Survived)); - }); - - it('should report 1 survived mutant', () => { - progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is ERRORED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Error)); - }); - - it('should report 1 errored mutant', () => { - progressBarTickTokens = { error: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - - describe('when status is NO COVERAGE', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.NoCoverage)); - }); - - it('should not report immediately', () => { - expect(progressBar.tick).to.not.have.been.called; - }); - - describe('and then the status is KILLED', () => { - - beforeEach(() => { - sut.onMutantTested(mutantResult(MutantStatus.Killed)); - }); - - it('should report 1 not covered mutant and 1 killed mutant', () => { - progressBarTickTokens = { error: 0, killed: 1, noCoverage: 1, survived: 0, timeout: 0 }; - expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); - }); - }); - }); - }); -}); - -function mutantResult(status: MutantStatus): MutantResult { - return { - location: null, - mutatedLines: null, - mutatorName: null, - originalLines: null, - replacement: null, - sourceFilePath: null, - testsRan: null, - status, - range: null - }; -} - -function matchedMutant(numberOfTests: number): MatchedMutant { - let scopedTestIds: number[] = []; - for (let i = 0; i < numberOfTests; i++) { - scopedTestIds.push(1); - } - return { - mutatorName: null, - scopedTestIds: scopedTestIds, - timeSpentScopedTests: null, - filename: null, - replacement: null - }; -} +// import ProgressReporter from '../../../src/reporters/ProgressReporter'; +// import * as progressBarModule from '../../../src/reporters/ProgressBar'; +// import { MutantStatus, MutantResult, MatchedMutant } from 'stryker-api/report'; +// import { expect } from 'chai'; +// import * as sinon from 'sinon'; +// import * as chalk from 'chalk'; + +// describe('ProgressReporter', () => { + +// let sut: ProgressReporter; +// let sandbox: sinon.SinonSandbox; +// let matchedMutants: MatchedMutant[]; +// let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; +// const progressBarContent: string = +// `Mutation testing [:bar] :percent (ETC :etas) ` + +// `[:killed ${chalk.green.bold('killed')}] ` + +// `[:survived ${chalk.red.bold('survived')}] ` + +// `[:noCoverage ${chalk.red.bold('no coverage')}] ` + +// `[:timeout ${chalk.yellow.bold('timeout')}] ` + +// `[:error ${chalk.yellow.bold('error')}]`; +// let progressBarOptions: any = { complete: '=', incomplete: ' ', total: null }; + +// beforeEach(() => { +// sut = new ProgressReporter(); +// sandbox = sinon.sandbox.create(); +// progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; +// sandbox.stub(progressBarModule, 'default').returns(progressBar); +// }); + +// afterEach(() => { +// sandbox.restore(); +// }); + +// describe('onAllMutantsMatchedWithTests()', () => { +// describe('when there are 3 MatchedMutants that all contain Tests', () => { +// beforeEach(() => { +// matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + +// sut.onAllMutantsMatchedWithTests(matchedMutants); +// }); + +// it('the total of MatchedMutants in the progressbar should be 3', () => { +// progressBarOptions.total = 3; +// expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); +// }); +// }); +// describe('when there are 2 MatchedMutants that all contain Tests and 1 MatchMutant that doesnt have tests', () => { +// beforeEach(() => { +// matchedMutants = [matchedMutant(1), matchedMutant(0), matchedMutant(2)]; + +// sut.onAllMutantsMatchedWithTests(matchedMutants); +// }); + +// it('the total of MatchedMutants in the progressbar should be 2', () => { +// progressBarOptions.total = 2; +// expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); +// }); +// }); +// }); + +// describe('onMutantTested()', () => { +// let progressBarTickTokens: any; + +// beforeEach(() => { +// matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + +// sut.onAllMutantsMatchedWithTests(matchedMutants); +// }); + +// describe('when status is KILLED', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.Killed)); +// }); + +// it('should report 1 killed mutant', () => { +// progressBarTickTokens = { error: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; +// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); +// }); +// }); + +// describe('when status is TIMEDOUT', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); +// }); + +// it('should report 1 timed out mutant', () => { +// progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; +// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); +// }); +// }); + +// describe('when status is SURVIVED', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.Survived)); +// }); + +// it('should report 1 survived mutant', () => { +// progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; +// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); +// }); +// }); + +// describe('when status is ERRORED', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.Error)); +// }); + +// it('should report 1 errored mutant', () => { +// progressBarTickTokens = { error: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; +// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); +// }); +// }); + +// describe('when status is NO COVERAGE', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.NoCoverage)); +// }); + +// it('should not report immediately', () => { +// expect(progressBar.tick).to.not.have.been.called; +// }); + +// describe('and then the status is KILLED', () => { + +// beforeEach(() => { +// sut.onMutantTested(mutantResult(MutantStatus.Killed)); +// }); + +// it('should report 1 not covered mutant and 1 killed mutant', () => { +// progressBarTickTokens = { error: 0, killed: 1, noCoverage: 1, survived: 0, timeout: 0 }; +// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); +// }); +// }); +// }); +// }); +// }); + +// function mutantResult(status: MutantStatus): MutantResult { +// return { +// location: null, +// mutatedLines: null, +// mutatorName: null, +// originalLines: null, +// replacement: null, +// sourceFilePath: null, +// testsRan: null, +// status, +// range: null +// }; +// } + +// function matchedMutant(numberOfTests: number): MatchedMutant { +// let scopedTestIds: number[] = []; +// for (let i = 0; i < numberOfTests; i++) { +// scopedTestIds.push(1); +// } +// return { +// mutatorName: null, +// scopedTestIds: scopedTestIds, +// timeSpentScopedTests: null, +// filename: null, +// replacement: null +// }; +// } From b6b3e71050b44230ed907bbfa79bf85880e6b765 Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Thu, 29 Dec 2016 17:12:12 +0100 Subject: [PATCH 4/8] Fixed flickering of progress bar --- src/reporters/ProgressReporter.ts | 27 +- test/unit/reporters/ProgressReporterSpec.ts | 331 ++++++++++---------- 2 files changed, 176 insertions(+), 182 deletions(-) diff --git a/src/reporters/ProgressReporter.ts b/src/reporters/ProgressReporter.ts index 421f09f956..a735324925 100644 --- a/src/reporters/ProgressReporter.ts +++ b/src/reporters/ProgressReporter.ts @@ -5,12 +5,19 @@ import * as _ from 'lodash'; export default class ProgressReporter implements Reporter { private progressBar: ProgressBar; + + // tickValues contains Labels, because on initation of the ProgressBar the width is determined based on the amount of characters of the progressBarContent inclusive ASCII-codes for colors private tickValues = { error: 0, survived: 0, killed: 0, timeout: 0, - noCoverage: 0 + noCoverage: 0, + killedLabel: `${chalk.green.bold('killed')}`, + survivedLabel: `${chalk.red.bold('survived')}`, + noCoverageLabel: `${chalk.red.bold('no coverage')}`, + timeoutLabel: `${chalk.yellow.bold('timeout')}`, + errorLabel: `${chalk.yellow.bold('error')}` }; onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void { @@ -18,11 +25,11 @@ export default class ProgressReporter implements Reporter { progressBarContent = `Mutation testing [:bar] :percent (ETC :etas)` + - `[:killed] ` + - `[:survived] ` + - `[:noCoverage] ` + - `[:timeout] ` + - `[:error]`; + `[:killed :killedLabel] ` + + `[:survived :survivedLabel] ` + + `[:noCoverage :noCoverageLabel] ` + + `[:timeout :timeoutLabel] ` + + `[:error :errorLabel]`; this.progressBar = new ProgressBar(progressBarContent, { width: 50, @@ -58,12 +65,6 @@ export default class ProgressReporter implements Reporter { } tick(): void { - const vals = _.clone(this.tickValues); - (vals as any).killed = `${vals.killed} ${chalk.green.bold('killed')}`; - (vals as any).survived = `${vals.survived} ${chalk.red.bold('survived')}`; - (vals as any).noCoverage = `${vals.noCoverage} ${chalk.red.bold('no coverage')}`; - (vals as any).timeout = `${vals.timeout} ${chalk.yellow.bold('timeout')}`; - (vals as any).error = `${vals.error} ${chalk.yellow.bold('error')}`; - this.progressBar.tick(vals); + this.progressBar.tick(this.tickValues); } } \ No newline at end of file diff --git a/test/unit/reporters/ProgressReporterSpec.ts b/test/unit/reporters/ProgressReporterSpec.ts index 2719f4a95a..4ccdcc32be 100644 --- a/test/unit/reporters/ProgressReporterSpec.ts +++ b/test/unit/reporters/ProgressReporterSpec.ts @@ -1,169 +1,162 @@ -// import ProgressReporter from '../../../src/reporters/ProgressReporter'; -// import * as progressBarModule from '../../../src/reporters/ProgressBar'; -// import { MutantStatus, MutantResult, MatchedMutant } from 'stryker-api/report'; -// import { expect } from 'chai'; -// import * as sinon from 'sinon'; -// import * as chalk from 'chalk'; - -// describe('ProgressReporter', () => { - -// let sut: ProgressReporter; -// let sandbox: sinon.SinonSandbox; -// let matchedMutants: MatchedMutant[]; -// let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; -// const progressBarContent: string = -// `Mutation testing [:bar] :percent (ETC :etas) ` + -// `[:killed ${chalk.green.bold('killed')}] ` + -// `[:survived ${chalk.red.bold('survived')}] ` + -// `[:noCoverage ${chalk.red.bold('no coverage')}] ` + -// `[:timeout ${chalk.yellow.bold('timeout')}] ` + -// `[:error ${chalk.yellow.bold('error')}]`; -// let progressBarOptions: any = { complete: '=', incomplete: ' ', total: null }; - -// beforeEach(() => { -// sut = new ProgressReporter(); -// sandbox = sinon.sandbox.create(); -// progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; -// sandbox.stub(progressBarModule, 'default').returns(progressBar); -// }); - -// afterEach(() => { -// sandbox.restore(); -// }); - -// describe('onAllMutantsMatchedWithTests()', () => { -// describe('when there are 3 MatchedMutants that all contain Tests', () => { -// beforeEach(() => { -// matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; - -// sut.onAllMutantsMatchedWithTests(matchedMutants); -// }); - -// it('the total of MatchedMutants in the progressbar should be 3', () => { -// progressBarOptions.total = 3; -// expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); -// }); -// }); -// describe('when there are 2 MatchedMutants that all contain Tests and 1 MatchMutant that doesnt have tests', () => { -// beforeEach(() => { -// matchedMutants = [matchedMutant(1), matchedMutant(0), matchedMutant(2)]; - -// sut.onAllMutantsMatchedWithTests(matchedMutants); -// }); - -// it('the total of MatchedMutants in the progressbar should be 2', () => { -// progressBarOptions.total = 2; -// expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); -// }); -// }); -// }); - -// describe('onMutantTested()', () => { -// let progressBarTickTokens: any; - -// beforeEach(() => { -// matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; - -// sut.onAllMutantsMatchedWithTests(matchedMutants); -// }); - -// describe('when status is KILLED', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.Killed)); -// }); - -// it('should report 1 killed mutant', () => { -// progressBarTickTokens = { error: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; -// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); -// }); -// }); - -// describe('when status is TIMEDOUT', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); -// }); - -// it('should report 1 timed out mutant', () => { -// progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; -// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); -// }); -// }); - -// describe('when status is SURVIVED', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.Survived)); -// }); - -// it('should report 1 survived mutant', () => { -// progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; -// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); -// }); -// }); - -// describe('when status is ERRORED', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.Error)); -// }); - -// it('should report 1 errored mutant', () => { -// progressBarTickTokens = { error: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; -// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); -// }); -// }); - -// describe('when status is NO COVERAGE', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.NoCoverage)); -// }); - -// it('should not report immediately', () => { -// expect(progressBar.tick).to.not.have.been.called; -// }); - -// describe('and then the status is KILLED', () => { - -// beforeEach(() => { -// sut.onMutantTested(mutantResult(MutantStatus.Killed)); -// }); - -// it('should report 1 not covered mutant and 1 killed mutant', () => { -// progressBarTickTokens = { error: 0, killed: 1, noCoverage: 1, survived: 0, timeout: 0 }; -// expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); -// }); -// }); -// }); -// }); -// }); - -// function mutantResult(status: MutantStatus): MutantResult { -// return { -// location: null, -// mutatedLines: null, -// mutatorName: null, -// originalLines: null, -// replacement: null, -// sourceFilePath: null, -// testsRan: null, -// status, -// range: null -// }; -// } - -// function matchedMutant(numberOfTests: number): MatchedMutant { -// let scopedTestIds: number[] = []; -// for (let i = 0; i < numberOfTests; i++) { -// scopedTestIds.push(1); -// } -// return { -// mutatorName: null, -// scopedTestIds: scopedTestIds, -// timeSpentScopedTests: null, -// filename: null, -// replacement: null -// }; -// } +import ProgressReporter from '../../../src/reporters/ProgressReporter'; +import * as progressBarModule from '../../../src/reporters/ProgressBar'; +import { MutantStatus, MutantResult, MatchedMutant } from 'stryker-api/report'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as chalk from 'chalk'; + +describe('ProgressReporter', () => { + + let sut: ProgressReporter; + let sandbox: sinon.SinonSandbox; + let matchedMutants: MatchedMutant[]; + let progressBar: { tick: sinon.SinonStub, render: sinon.SinonStub }; + const progressBarContent: string = + `Mutation testing [:bar] :percent (ETC :etas)` + + `[:killed :killedLabel] ` + + `[:survived :survivedLabel] ` + + `[:noCoverage :noCoverageLabel] ` + + `[:timeout :timeoutLabel] ` + + `[:error :errorLabel]`; + let progressBarOptions: any = { complete: '=', incomplete: ' ', total: null }; + + beforeEach(() => { + sut = new ProgressReporter(); + sandbox = sinon.sandbox.create(); + progressBar = { tick: sandbox.stub(), render: sandbox.stub() }; + sandbox.stub(progressBarModule, 'default').returns(progressBar); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('onAllMutantsMatchedWithTests()', () => { + describe('when there are 3 MatchedMutants that all contain Tests', () => { + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + it('the total of MatchedMutants in the progressbar should be 3', () => { + progressBarOptions.total = 3; + expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); + }); + }); + describe('when there are 2 MatchedMutants that all contain Tests and 1 MatchMutant that doesnt have tests', () => { + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(0), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + it('the total of MatchedMutants in the progressbar should be 2', () => { + progressBarOptions.total = 2; + expect(progressBarModule.default).to.have.been.calledWithMatch(progressBarContent, progressBarOptions); + }); + }); + }); + + describe('onMutantTested()', () => { + let progressBarTickTokens: any; + + beforeEach(() => { + matchedMutants = [matchedMutant(1), matchedMutant(4), matchedMutant(2)]; + + sut.onAllMutantsMatchedWithTests(matchedMutants); + }); + + describe('when status is KILLED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Killed)); + }); + + it('should tick the ProgressBar with 1 killed mutant', () => { + progressBarTickTokens = { error: 0, killed: 1, noCoverage: 0, survived: 0, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is TIMEDOUT', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.TimedOut)); + }); + + it('should tick the ProgressBar with 1 timed out mutant', () => { + progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 0, timeout: 1 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is SURVIVED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Survived)); + }); + + it('should tick the ProgressBar with 1 survived mutant', () => { + progressBarTickTokens = { error: 0, killed: 0, noCoverage: 0, survived: 1, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is ERRORED', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.Error)); + }); + + it('should tick the ProgressBar with 1 errored mutant', () => { + progressBarTickTokens = { error: 1, killed: 0, noCoverage: 0, survived: 0, timeout: 0 }; + expect(progressBar.tick).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + + describe('when status is NO COVERAGE', () => { + + beforeEach(() => { + sut.onMutantTested(mutantResult(MutantStatus.NoCoverage)); + }); + + it('should not tick the ProgressBar', () => { + expect(progressBar.tick).to.not.have.been.called; + }); + + it('should render the ProgressBar', () => { + progressBarTickTokens = { error: 0, killed: 0, noCoverage: 1, survived: 0, timeout: 0 }; + expect(progressBar.render).to.have.been.calledWithMatch(progressBarTickTokens); + }); + }); + }); +}); + +function mutantResult(status: MutantStatus): MutantResult { + return { + location: null, + mutatedLines: null, + mutatorName: null, + originalLines: null, + replacement: null, + sourceFilePath: null, + testsRan: null, + status, + range: null + }; +} + +function matchedMutant(numberOfTests: number): MatchedMutant { + let scopedTestIds: number[] = []; + for (let i = 0; i < numberOfTests; i++) { + scopedTestIds.push(1); + } + return { + mutatorName: null, + scopedTestIds: scopedTestIds, + timeSpentScopedTests: null, + filename: null, + replacement: null + }; +} From 61a29c468ea7110bb6d7025df1ec141ce11dd588 Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Fri, 30 Dec 2016 16:52:57 +0100 Subject: [PATCH 5/8] Fix(InputFileResolver) - Check currentFiles is not an empty array - Check if filePath is not a directory (/core.js/dist/vender/core.js) --- src/InputFileResolver.ts | 4 ++++ src/utils/fileUtils.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/InputFileResolver.ts b/src/InputFileResolver.ts index 42d3e9f901..0734d52b35 100644 --- a/src/InputFileResolver.ts +++ b/src/InputFileResolver.ts @@ -96,6 +96,10 @@ class PatternResolver { const currentFiles = results[1]; // If this expression started with a '!', exclude current files if (this.ignore) { + // Don't filter files when there are no current files + if (currentFiles.length === 0) { + return previousFiles; + } return previousFiles.filter(previousFile => currentFiles.some(currentFile => previousFile.path !== currentFile.path)); } else { // Only add files which were not already added diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts index c85d88d07d..ddca54637f 100644 --- a/src/utils/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -54,7 +54,7 @@ export function normalize(files: string[]): void { export function glob(expression: string): Promise { return new Promise((resolve, reject) => { - nodeGlob(expression, (error, matches) => { + nodeGlob(expression, { nodir: true }, (error, matches) => { if (error) { reject(error); } else { From 0c362bf87a6f298feb05f7977bf87c9a95b14231 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Fri, 30 Dec 2016 18:30:34 +0100 Subject: [PATCH 6/8] Merge with master --- package.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package.json b/package.json index 5a09b5bbd6..a1fd5337b2 100644 --- a/package.json +++ b/package.json @@ -97,19 +97,11 @@ "sinon": "^1.17.2", "sinon-as-promised": "^4.0.2", "sinon-chai": "^2.8.0", -<<<<<<< HEAD - "stryker-api": "^0.4.2-rc1", -======= "stryker-api": "^0.4.2", ->>>>>>> master "tslint": "^3.15.1", "typescript": "^2.1.4" }, "peerDependencies": { -<<<<<<< HEAD - "stryker-api": "^0.4.2-rc1" -======= "stryker-api": "^0.4.2" ->>>>>>> master } } From f01399cf72b5f298bf3378f65b779589a042b934 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Fri, 30 Dec 2016 21:54:39 +0100 Subject: [PATCH 7/8] test(InputFileResolver): Add tests * Add test for exclusion of file when the expression resolves in empty * Test that globbing only results in files, not directories. --- src/InputFileResolver.ts | 9 +++----- test/integration/utils/fileUtilsSpec.ts | 26 +++++++++++++++-------- test/unit/InputFileResolverSpec.ts | 17 +++++++++------ testResources/vendor/zone.js/zone.file.js | 0 4 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 testResources/vendor/zone.js/zone.file.js diff --git a/src/InputFileResolver.ts b/src/InputFileResolver.ts index 51315c6d97..c4bb3959b9 100644 --- a/src/InputFileResolver.ts +++ b/src/InputFileResolver.ts @@ -105,18 +105,15 @@ class PatternResolver { // Start the globbing task for the current descriptor const globbingTask = this.resolveGlobbingExpression(this.descriptor.pattern) .then(filePaths => filePaths.map(filePath => this.createInputFile(filePath))); + + // If there is a previous globbing expression, resolve that one as well if (this.previous) { - // If there is a previous globbing expression, resolve that one as well return Promise.all([this.previous.resolve(), globbingTask]).then(results => { const previousFiles = results[0]; const currentFiles = results[1]; // If this expression started with a '!', exclude current files if (this.ignore) { - // Don't filter files when there are no current files - if (currentFiles.length === 0) { - return previousFiles; - } - return previousFiles.filter(previousFile => currentFiles.some(currentFile => previousFile.path !== currentFile.path)); + return previousFiles.filter(previousFile => currentFiles.every(currentFile => previousFile.path !== currentFile.path)); } else { // Only add files which were not already added return previousFiles.concat(currentFiles.filter(currentFile => !previousFiles.some(file => file.path === currentFile.path))); diff --git a/test/integration/utils/fileUtilsSpec.ts b/test/integration/utils/fileUtilsSpec.ts index 7dd12d3bf6..a867b7fcd1 100644 --- a/test/integration/utils/fileUtilsSpec.ts +++ b/test/integration/utils/fileUtilsSpec.ts @@ -13,9 +13,8 @@ describe('fileUtils', () => { afterEach(() => sandbox.restore()); - describe('should be able to read a file', () => { - - it('synchronously', () => { + describe('readFileSync()', () => { + it('should synchronously', () => { const msg = 'hello 1 2'; sandbox.stub(fs, 'readFileSync', (filename: string, encoding: string) => msg); const data = fileUtils.readFile('hello.js'); @@ -23,16 +22,25 @@ describe('fileUtils', () => { }); }); - it('should indicate that an existing file exists', () => { - const exists = fileUtils.fileOrFolderExistsSync('src/Stryker.ts'); + describe('fileOrFolderExistsSync()', () => { + it('should indicate that an existing file exists', () => { + const exists = fileUtils.fileOrFolderExistsSync('src/Stryker.ts'); + + expect(exists).to.equal(true); + }); + it('should indicate that an non-existing file does not exists', () => { + const exists = fileUtils.fileOrFolderExistsSync('src/Strykerfaefeafe.js'); - expect(exists).to.equal(true); + expect(exists).to.equal(false); + }); }); - it('should indicate that an non-existing file does not exists', () => { - const exists = fileUtils.fileOrFolderExistsSync('src/Strykerfaefeafe.js'); + describe('glob', () => { + it('should resolve files', () => + expect(fileUtils.glob('testResources/sampleProject/**/*.js')).to.eventually.have.length(9)); - expect(exists).to.equal(false); + it('should not resolve to directories', () => + expect(fileUtils.glob('testResources/vendor/**/*.js')).to.eventually.have.length(1)); }); }); diff --git a/test/unit/InputFileResolverSpec.ts b/test/unit/InputFileResolverSpec.ts index d0a1a955b6..52e33a4dcf 100644 --- a/test/unit/InputFileResolverSpec.ts +++ b/test/unit/InputFileResolverSpec.ts @@ -161,24 +161,27 @@ describe('InputFileResolver', () => { it('should not exclude files added using an input file descriptor', () => expect(new InputFileResolver([], ['file2', { pattern: '!file2' }]).resolve()).to.eventually.deep.equal(fileDescriptors(['/file2.js']))); + + it('should not exlude files when the globbing expression results in an empty array', () => + expect(new InputFileResolver([], ['file2', '!does/not/exist']).resolve()).to.eventually.deep.equal(fileDescriptors(['/file2.js']))); }); describe('when provided duplicate files', () => { - it('should deduplicate files that occur more than once', () => + it('should deduplicate files that occur more than once', () => expect(new InputFileResolver([], ['file2', 'file2']).resolve()).to.eventually.deep.equal(fileDescriptors(['/file2.js']))); - - it('should deduplicate files that previously occured in a wildcard expression', () => + + it('should deduplicate files that previously occured in a wildcard expression', () => expect(new InputFileResolver([], ['file*', 'file2']).resolve()).to.eventually.deep.equal(fileDescriptors(['/file1.js', '/file2.js', '/file3.js']))); - - it('should order files by expression order', () => + + it('should order files by expression order', () => expect(new InputFileResolver([], ['file2', 'file*']).resolve()).to.eventually.deep.equal(fileDescriptors(['/file2.js', '/file1.js', '/file3.js']))); }); describe('with url as file pattern', () => { it('should pass through the web urls without globbing', () => { - return new InputFileResolver([], ['http://www', {pattern: 'https://ok'}]) + return new InputFileResolver([], ['http://www', { pattern: 'https://ok' }]) .resolve() .then(() => expect(fileUtils.glob).to.not.have.been.called); }); @@ -188,7 +191,7 @@ describe('InputFileResolver', () => { }); it('should fail when web url is to be mutated', () => { - expect(() => new InputFileResolver([], [ { pattern: 'http://www', mutated: true } ])).throws('Cannot mutate web url "http://www".'); + expect(() => new InputFileResolver([], [{ pattern: 'http://www', mutated: true }])).throws('Cannot mutate web url "http://www".'); }); }); diff --git a/testResources/vendor/zone.js/zone.file.js b/testResources/vendor/zone.js/zone.file.js new file mode 100644 index 0000000000..e69de29bb2 From 436d28298a610358b469b495e9999a6aed382e99 Mon Sep 17 00:00:00 2001 From: Alex van Assem Date: Fri, 30 Dec 2016 22:21:54 +0100 Subject: [PATCH 8/8] (test) Fixed mutation test coverage - ProgressReporter --- src/reporters/ProgressReporter.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/reporters/ProgressReporter.ts b/src/reporters/ProgressReporter.ts index 5255eaa926..5122891c23 100644 --- a/src/reporters/ProgressReporter.ts +++ b/src/reporters/ProgressReporter.ts @@ -12,11 +12,11 @@ export default class ProgressReporter implements Reporter { killed: 0, timeout: 0, noCoverage: 0, - killedLabel: `${chalk.green.bold('killed')}`, - survivedLabel: `${chalk.red.bold('survived')}`, - noCoverageLabel: `${chalk.red.bold('no coverage')}`, - timeoutLabel: `${chalk.yellow.bold('timeout')}`, - errorLabel: `${chalk.yellow.bold('error')}` + killedLabel: chalk.green.bold('killed'), + survivedLabel: chalk.red.bold('survived'), + noCoverageLabel: chalk.red.bold('no coverage'), + timeoutLabel: chalk.yellow.bold('timeout'), + errorLabel: chalk.yellow.bold('error') }; onAllMutantsMatchedWithTests(matchedMutants: ReadonlyArray): void {