Skip to content

Commit ab080ad

Browse files
committed
test(ci): cover mutation result classification
1 parent 5e40fae commit ab080ad

2 files changed

Lines changed: 88 additions & 19 deletions

File tree

‎scripts/stryker-diff.mjs‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,15 @@ export function evaluateReport(report, packageEntry) {
665665
return { ...counts, advisories }
666666
}
667667

668-
export function runManifest(repoRoot, manifest, reportRoot) {
668+
export function runManifest(
669+
repoRoot,
670+
manifest,
671+
reportRoot,
672+
{
673+
runMutation = runStryker,
674+
readMutationReport = (reportPath) => JSON.parse(fs.readFileSync(reportPath, "utf8")),
675+
} = {},
676+
) {
669677
const rows = []
670678
const advisories = [...(manifest.advisories ?? [])]
671679
const annotationState = { total: 0, perFile: new Map() }
@@ -681,7 +689,7 @@ export function runManifest(repoRoot, manifest, reportRoot) {
681689
if (packageEntry.discoverRelatedTests) {
682690
packageEntry.testFiles = discoverRelatedTestFiles(repoRoot, packageEntry, reportDirectory)
683691
}
684-
const preflightOutput = stripAnsi(runStryker(repoRoot, packageEntry, reportRoot, true))
692+
const preflightOutput = stripAnsi(runMutation(repoRoot, packageEntry, reportRoot, true))
685693
const mutantMatch = /Instrumented \d+ source file\(s\) with (\d+) mutant\(s\)/.exec(preflightOutput)
686694
if (!mutantMatch) throw new Error(`${packageEntry.id} preflight did not report a mutant count`)
687695
const generatedMutants = Number(mutantMatch[1])
@@ -711,9 +719,9 @@ export function runManifest(repoRoot, manifest, reportRoot) {
711719
continue
712720
}
713721

714-
runStryker(repoRoot, packageEntry, reportRoot, false)
722+
runMutation(repoRoot, packageEntry, reportRoot, false)
715723
const jsonReportPath = path.join(reportRoot, packageEntry.id, "mutation.json")
716-
const report = JSON.parse(fs.readFileSync(jsonReportPath, "utf8"))
724+
const report = readMutationReport(jsonReportPath)
717725
packageEntry.testFiles = testsFromMutationReport(report, packageEntry.testFiles)
718726
counts = evaluateReport(report, packageEntry)
719727
for (const annotation of formatAnnotations(

‎scripts/stryker-diff.test.mjs‎

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -530,23 +530,40 @@ describe("failure output", () => {
530530
})
531531

532532
it("emits one distinguishable annotation per source location", () => {
533-
const annotations = formatAnnotations(
534-
[
535-
...blocking,
536-
{
537-
filePath: "utils/other.ts",
538-
status: "NoCoverage",
539-
mutatorName: "ConditionalExpression",
540-
replacement: "true",
541-
location: { start: { line: 9 } },
542-
},
543-
],
544-
"src",
545-
)
533+
const mutants = [
534+
blocking[2],
535+
blocking[1],
536+
{
537+
filePath: "utils/other.ts",
538+
status: "NoCoverage",
539+
mutatorName: "ConditionalExpression",
540+
replacement: "true",
541+
location: { start: { line: 9 } },
542+
},
543+
blocking[0],
544+
]
545+
const originalOrder = [...mutants]
546+
const annotations = formatAnnotations(mutants, "src")
546547

547548
assert.equal(annotations.length, 2)
548-
assert.match(annotations[0].message, /^src\/core\/value\.ts:4: 2 mutation test gaps; example:/)
549-
assert.match(annotations[1].message, /^src\/utils\/other\.ts:9: 2 mutation test gaps; example:/)
549+
assert.deepEqual(mutants, originalOrder)
550+
assert.match(
551+
annotations[0].message,
552+
/^src\/core\/value\.ts:4: 2 mutation test gaps; example: NoCoverage StringLiteral mutant \(replacement: "left \| right"\)/,
553+
)
554+
assert.match(
555+
annotations[1].message,
556+
/^src\/utils\/other\.ts:9: 2 mutation test gaps; example: Survived BooleanLiteral mutant \(replacement: false\)/,
557+
)
558+
})
559+
560+
it("prefixes singleton annotations with their source location", () => {
561+
const [annotation] = formatAnnotations([blocking[2]], "src")
562+
563+
assert.equal(
564+
annotation.message,
565+
"src/utils/other.ts:9: Survived BooleanLiteral mutant (replacement: false). See the job summary for the complete list and resolution guidance.",
566+
)
550567
})
551568

552569
it("shares annotation limits across packages", () => {
@@ -625,6 +642,50 @@ describe("failure output", () => {
625642
}
626643
})
627644

645+
it("classifies successful package rows from blocking mutants", () => {
646+
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "stryker-success-"))
647+
const packageEntry = {
648+
id: "core",
649+
root: "packages/core",
650+
vitestConfig: "vitest.unit.config.ts",
651+
selectors: ["src/value.ts:1-1"],
652+
changedExecutableLines: 1,
653+
}
654+
const execute = (report) =>
655+
runManifest(repo, { packages: [{ ...packageEntry }] }, path.join(repo, "reports"), {
656+
runMutation: (_repoRoot, _entry, _reportRoot, dryRunOnly) =>
657+
dryRunOnly ? "Instrumented 1 source file(s) with 1 mutant(s)" : "",
658+
readMutationReport: () => report,
659+
})[0]
660+
661+
try {
662+
const advisoryRow = execute({
663+
files: {
664+
"src/value.ts": {
665+
mutants: [
666+
{
667+
status: "Survived",
668+
mutatorName: "BooleanLiteral",
669+
replacement: "false",
670+
location: { start: { line: 1 } },
671+
},
672+
],
673+
},
674+
},
675+
})
676+
assert.equal(advisoryRow.result, "Advisory findings")
677+
assert.deepEqual(advisoryRow.advisories, [])
678+
679+
const passedRow = execute({
680+
files: { "src/value.ts": { mutants: [{ status: "Killed", location: { start: { line: 1 } } }] } },
681+
})
682+
assert.equal(passedRow.result, "Passed")
683+
assert.deepEqual(passedRow.advisories, [])
684+
} finally {
685+
fs.rmSync(repo, { recursive: true, force: true })
686+
}
687+
})
688+
628689
it("does not fail when the GitHub job summary cannot be written", () => {
629690
const previousSummary = process.env.GITHUB_STEP_SUMMARY
630691
const summaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "stryker-summary-"))

0 commit comments

Comments
 (0)