UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

103 lines (90 loc) 3.79 kB
import { CoverageData } from '@stryker-mutator/api/core'; import { Logger } from '@stryker-mutator/api/logging'; import { commonTokens } from '@stryker-mutator/api/plugin'; import { CompleteDryRunResult, TestResult } from '@stryker-mutator/api/test-runner'; import { notEmpty } from '@stryker-mutator/util'; import { coreTokens } from '../di/index.js'; export class TestCoverage { readonly #testsByMutantId; readonly #testsById; readonly #staticCoverage; readonly #hitsByMutantId; constructor( testsByMutantId: Map<string, Set<TestResult>>, testsById: Map<string, TestResult>, staticCoverage: CoverageData | undefined, hitsByMutantId: Map<string, number>, ) { this.#testsByMutantId = testsByMutantId; this.#testsById = testsById; this.#staticCoverage = staticCoverage; this.#hitsByMutantId = hitsByMutantId; } public get testsByMutantId(): ReadonlyMap<string, Set<TestResult>> { return this.#testsByMutantId; } public get testsById(): ReadonlyMap<string, TestResult> { return this.#testsById; } public get hitsByMutantId(): ReadonlyMap<string, number> { return this.#hitsByMutantId; } public get hasCoverage(): boolean { // Since static coverage should always be reported when coverage analysis succeeded (albeit an empty object), // we can use that to determine if there is any coverage at all return !!this.#staticCoverage; } public hasStaticCoverage(mutantId: string): boolean { return !!(this.#staticCoverage && this.#staticCoverage[mutantId] > 0); } public addTest(testResult: TestResult): void { this.#testsById.set(testResult.id, testResult); } public addCoverage(mutantId: string, testIds: string[]): void { const tests = this.#testsByMutantId.get(mutantId) ?? new Set(); this.#testsByMutantId.set(mutantId, tests); testIds .map((testId) => this.#testsById.get(testId)) .filter(notEmpty) .forEach((test) => tests.add(test)); } public forMutant(mutantId: string): ReadonlySet<TestResult> | undefined { return this.#testsByMutantId.get(mutantId); } public static from = testCoverageFrom; } function testCoverageFrom({ tests, mutantCoverage }: CompleteDryRunResult, logger: Logger): TestCoverage { const hitsByMutantId = new Map<string, number>(); const testsByMutantId = new Map<string, Set<TestResult>>(); const testsById = tests.reduce((acc, test) => acc.set(test.id, test), new Map<string, TestResult>()); if (mutantCoverage) { Object.entries(mutantCoverage.perTest).forEach(([testId, coverage]) => { const foundTest = testsById.get(testId); if (!foundTest) { logger.warn( `Found test with id "${testId}" in coverage data, but not in the test results of the dry run. Not taking coverage data for this test into account.`, ); return; } Object.entries(coverage).forEach(([mutantId, count]) => { if (count > 0) { let cov = testsByMutantId.get(mutantId); if (!cov) { cov = new Set(); testsByMutantId.set(mutantId, cov); } cov.add(foundTest); } }); }); // We don't care about the exact tests in this case, just the total number of hits const coverageResultsPerMutant = [mutantCoverage.static, ...Object.values(mutantCoverage.perTest)]; coverageResultsPerMutant.forEach((coverageByMutantId) => { Object.entries(coverageByMutantId).forEach(([mutantId, count]) => { hitsByMutantId.set(mutantId, (hitsByMutantId.get(mutantId) ?? 0) + count); }); }); } return new TestCoverage(testsByMutantId, testsById, mutantCoverage?.static, hitsByMutantId); } testCoverageFrom.inject = [coreTokens.dryRunResult, commonTokens.logger] as const;