-
Notifications
You must be signed in to change notification settings - Fork 240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature (reporter): issue #1814 Add json reporter #2601
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -133,6 +133,18 @@ | |
} | ||
} | ||
}, | ||
"jsonReporterOptions": { | ||
"title": "JsonReporterOptions", | ||
"additionalProperties": false, | ||
"type": "object", | ||
"properties": { | ||
"baseDir": { | ||
"description": "The output folder for the json report.", | ||
"type": "string", | ||
"default": "reports/mutation/json" | ||
} | ||
} | ||
}, | ||
"mutationScoreThresholds": { | ||
"title": "MutationScoreThresholds", | ||
"additionalProperties": false, | ||
|
@@ -349,6 +361,10 @@ | |
"description": "The options for the html reporter", | ||
"$ref": "#/definitions/htmlReporterOptions" | ||
}, | ||
"jsonReporter": { | ||
"description": "The options for the json reporter", | ||
"$ref": "#/definitions/jsonReporterOptions" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please fill the |
||
}, | ||
"disableTypeChecks": { | ||
"description": "Configure a pattern that matches the files of which type checking has to be disabled. This is needed because Stryker will create (typescript) type errors when inserting the mutants in your code. Stryker disables type checking by inserting `// @ts-nocheck` atop those files and removing other `// @ts-xxx` directives (so they won't interfere with `@ts-nocheck`). The default setting allows these directives to be stripped from all JavaScript and friend files in `lib`, `src` and `test` directories. You can specify a different glob expression or set it to `false` to completely disable this behavior.", | ||
"anyOf": [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import * as path from 'path'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually a duplication of the code in |
||
import { promisify } from 'util'; | ||
import { promises as fs } from 'fs'; | ||
|
||
import mkdirp = require('mkdirp'); | ||
import * as rimraf from 'rimraf'; | ||
import { throwError } from 'rxjs'; | ||
|
||
export const deleteDir = promisify(rimraf); | ||
export const mkdir = mkdirp; | ||
|
||
export async function writeFile(fileName: string, content: string) { | ||
try { | ||
await mkdirp(path.dirname(fileName)); | ||
await fs.writeFile(fileName, content, 'utf8'); | ||
} catch (error) { | ||
throwError(error); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import * as path from 'path'; | ||
|
||
import fileUrl = require('file-url'); | ||
|
||
import { Logger } from '@stryker-mutator/api/logging'; | ||
import { mutationTestReportSchema, Reporter } from '@stryker-mutator/api/report'; | ||
import { StrykerOptions } from '@stryker-mutator/api/core'; | ||
import { commonTokens, tokens } from '@stryker-mutator/api/plugin'; | ||
|
||
import { mkdir, deleteDir, writeFile } from './json-reporter-util'; | ||
|
||
const DEFAULT_BASE_FOLDER = path.normalize('reports/mutation/json'); | ||
|
||
export default class JsonReporter implements Reporter { | ||
private _baseDir!: string; | ||
private mainPromise: Promise<void> | undefined; | ||
|
||
constructor(private readonly options: StrykerOptions, private readonly log: Logger) {} | ||
|
||
public static readonly inject = tokens(commonTokens.options, commonTokens.logger); | ||
|
||
public onMutationTestReportReady(report: mutationTestReportSchema.MutationTestResult) { | ||
this.mainPromise = this.generateReport(report); | ||
} | ||
|
||
public wrapUp() { | ||
return this.mainPromise; | ||
} | ||
|
||
public async generateReport(report: mutationTestReportSchema.MutationTestResult) { | ||
const indexFileName = path.resolve(this.baseDir, 'report.json'); | ||
|
||
await this.cleanBaseFolder(); | ||
await writeFile(indexFileName, JSON.stringify(report)); | ||
|
||
this.log.info(`Your report can be found at: ${fileUrl(indexFileName)}`); | ||
} | ||
|
||
private get baseDir(): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As stated earlier, this null check is no longer needed if you use |
||
if (!this._baseDir) { | ||
if (this.options.jsonReporter && this.options.jsonReporter.baseDir) { | ||
this._baseDir = this.options.jsonReporter.baseDir; | ||
this.log.debug(`Using configured output folder ${this._baseDir}`); | ||
} else { | ||
this.log.debug( | ||
`No base folder configuration found (using configuration: jsonReporter: { baseDir: 'output/folder' }), using default ${DEFAULT_BASE_FOLDER}` | ||
); | ||
this._baseDir = DEFAULT_BASE_FOLDER; | ||
} | ||
} | ||
return this._baseDir; | ||
} | ||
|
||
private async cleanBaseFolder(): Promise<void> { | ||
await deleteDir(this.baseDir); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need to delete the |
||
await mkdir(this.baseDir); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as path from 'path'; | ||
|
||
import { mutationTestReportSchema } from '@stryker-mutator/api/report'; | ||
import { testInjector } from '@stryker-mutator/test-helpers'; | ||
import { expect } from 'chai'; | ||
import * as sinon from 'sinon'; | ||
|
||
import JsonReporter from '../../../../src/reporters/json/json-reporter'; | ||
import * as JsonReporterUtil from '../../../../src/reporters/json/json-reporter-util'; | ||
|
||
describe(JsonReporter.name, () => { | ||
let writeFileStub: sinon.SinonStub; | ||
let mkdirStub: sinon.SinonStub; | ||
let deleteDirStub: sinon.SinonStub; | ||
let sut: JsonReporter; | ||
|
||
beforeEach(() => { | ||
writeFileStub = sinon.stub(JsonReporterUtil, 'writeFile'); | ||
deleteDirStub = sinon.stub(JsonReporterUtil, 'deleteDir'); | ||
mkdirStub = sinon.stub(JsonReporterUtil, 'mkdir'); | ||
sut = testInjector.injector.injectClass(JsonReporter); | ||
}); | ||
|
||
describe('onMutationTestReportReady', () => { | ||
it('should use configured base directory', async () => { | ||
testInjector.options.jsonReporter = { baseDir: 'foo/bar' }; | ||
actReportReady(); | ||
await sut.wrapUp(); | ||
expect(testInjector.logger.debug).calledWith('Using configured output folder foo/bar'); | ||
expect(deleteDirStub).calledWith('foo/bar'); | ||
}); | ||
|
||
it('should use default base directory when no override is configured', async () => { | ||
const expectedBaseDir = path.normalize('reports/mutation/json'); | ||
actReportReady(); | ||
await sut.wrapUp(); | ||
expect(testInjector.logger.debug).calledWith( | ||
`No base folder configuration found (using configuration: jsonReporter: { baseDir: 'output/folder' }), using default ${expectedBaseDir}` | ||
); | ||
expect(deleteDirStub).calledWith(expectedBaseDir); | ||
}); | ||
|
||
it('should clean the base directory', async () => { | ||
actReportReady(); | ||
await sut.wrapUp(); | ||
expect(deleteDirStub).calledWith(path.normalize('reports/mutation/json')); | ||
expect(mkdirStub).calledWith(path.normalize('reports/mutation/json')); | ||
expect(deleteDirStub).calledBefore(mkdirStub); | ||
}); | ||
|
||
it('should write the mutation report to disk', async () => { | ||
const report: mutationTestReportSchema.MutationTestResult = { | ||
files: {}, | ||
schemaVersion: '1.0', | ||
thresholds: { | ||
high: 80, | ||
low: 60, | ||
}, | ||
}; | ||
sut.onMutationTestReportReady(report); | ||
await sut.wrapUp(); | ||
expect(writeFileStub).calledWith(path.resolve('reports', 'mutation', 'json', 'report.json'), JSON.stringify(report)); | ||
}); | ||
}); | ||
|
||
describe('wrapUp', () => { | ||
it('should resolve when everything is OK', () => { | ||
actReportReady(); | ||
return expect(sut.wrapUp()).eventually.undefined; | ||
}); | ||
|
||
it('should reject when "deleteDir" rejects', () => { | ||
const expectedError = new Error('delete dir'); | ||
deleteDirStub.rejects(expectedError); | ||
actReportReady(); | ||
return expect(sut.wrapUp()).rejectedWith(expectedError); | ||
}); | ||
|
||
it('should reject when "mkdir" rejects', () => { | ||
const expectedError = new Error('mkdir'); | ||
mkdirStub.rejects(expectedError); | ||
actReportReady(); | ||
return expect(sut.wrapUp()).rejectedWith(expectedError); | ||
}); | ||
|
||
it('should reject when "writeFile" rejects', () => { | ||
const expectedError = new Error('writeFile'); | ||
writeFileStub.rejects(expectedError); | ||
actReportReady(); | ||
return expect(sut.wrapUp()).rejectedWith(expectedError); | ||
}); | ||
}); | ||
|
||
function actReportReady() { | ||
sut.onMutationTestReportReady({ files: {}, schemaVersion: '', thresholds: { high: 0, low: 0 } }); | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,8 @@ | |
"reporters": [ | ||
"progress", | ||
"html", | ||
"dashboard" | ||
"dashboard", | ||
"json" | ||
], | ||
"plugins": [ | ||
"../mocha-runner", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,7 +102,8 @@ | |
"preprocessors", | ||
"serializable", | ||
"surrialize" | ||
] | ||
], | ||
"compile-hero.disable-compile-files-on-did-save-code": false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That went in by mistake. It's a markdown plugin I have. |
||
}, | ||
"extensions": { | ||
"recommendations": [ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the JSON report will be a single file, I think it makes sense to default to
reports/mutation
here. So