UNPKG

apex-mutation-testing

Version:

Apex mutation testing plugin

118 lines 5.14 kB
import { ApexClassRepository } from '../adapter/apexClassRepository.js'; import { ApexTestRunner } from '../adapter/apexTestRunner.js'; import { MutantGenerator } from './mutantGenerator.js'; export class MutationTestingService { progress; spinner; connection; apexClassName; apexTestClassName; constructor(progress, spinner, connection, { apexClassName, apexTestClassName }) { this.progress = progress; this.spinner = spinner; this.connection = connection; this.apexClassName = apexClassName; this.apexTestClassName = apexTestClassName; } async process() { const apexClassRepository = new ApexClassRepository(this.connection); const apexTestRunner = new ApexTestRunner(this.connection); this.spinner.start(`Fetching "${this.apexClassName}" ApexClass content`, undefined, { stdout: true, }); const apexClass = (await apexClassRepository.read(this.apexClassName)); this.spinner.stop('Done'); this.spinner.start(`Computing coverage from "${this.apexTestClassName}" Test class`, undefined, { stdout: true, }); const coveredLines = await apexTestRunner.getCoveredLines(this.apexTestClassName); this.spinner.stop('Done'); this.spinner.start(`Generating mutants for "${this.apexClassName}" ApexClass`, undefined, { stdout: true, }); const mutantGenerator = new MutantGenerator(); const mutations = mutantGenerator.compute(apexClass.Body, coveredLines); const mutationResults = { sourceFile: this.apexClassName, sourceFileContent: apexClass.Body, testFile: this.apexTestClassName, mutants: [], }; this.spinner.stop(`${mutations.length} mutations generated`); this.progress.start(mutations.length, { info: 'Starting mutation testing' }, { title: 'MUTATION TESTING PROGRESS', format: '%s | {bar} | {value}/{total} {info}', }); let mutationCount = 0; for (const mutation of mutations) { const mutatedVersion = mutantGenerator.mutate(mutation); this.progress.update(mutationCount, { info: `Deploying "${mutation.replacement}" mutation at line ${mutation.token.symbol.line}`, }); let progressMessage; try { await apexClassRepository.update({ Id: apexClass.Id, Body: mutatedVersion, }); this.progress.update(mutationCount, { info: `Running tests for "${mutation.replacement}" mutation at line ${mutation.token.symbol.line}`, }); const testResult = await apexTestRunner.run(this.apexTestClassName); const mutantResult = this.buildMutantResult(mutation, testResult); mutationResults.mutants.push(mutantResult); progressMessage = `Mutation result: ${testResult.summary.outcome === 'Pass' ? 'zombie' : 'mutant killed'}`; } catch { progressMessage = `Issue while computing "${mutation.replacement}" mutation at line ${mutation.token.symbol.line}`; } ++mutationCount; this.progress.update(mutationCount, { info: progressMessage, }); } this.progress.finish({ info: `All mutations evaluated`, }); try { this.spinner.start(`Rolling back "${this.apexClassName}" ApexClass to its original state`, undefined, { stdout: true, }); await apexClassRepository.update(apexClass); this.spinner.stop('Done'); } catch { this.spinner.stop('Class not rolled back, please do it manually'); } return mutationResults; } calculateScore(mutationResult) { return ((mutationResult.mutants.filter(mutant => mutant.status === 'Killed') .length / mutationResult.mutants.length) * 100 || 0); } buildMutantResult(mutation, testResult) { const token = mutation.token; // TODO Handle NoCoverage const mutationStatus = testResult.summary.outcome === 'Pass' ? 'Survived' : 'Killed'; return { id: `${this.apexClassName}-${token.symbol.line}-${token.symbol.charPositionInLine}-${token.symbol.tokenIndex}-${Date.now()}`, mutatorName: mutation.mutationName, status: mutationStatus, location: { start: { line: token.symbol.line, column: token.symbol.charPositionInLine, }, end: { line: token.symbol.line, column: token.symbol.charPositionInLine + mutation.replacement.length, }, }, replacement: mutation.replacement, original: token.text, }; } } //# sourceMappingURL=mutationTestingService.js.map